diff --git a/.gitignore b/.gitignore index 065afbb7..1b63d531 100644 --- a/.gitignore +++ b/.gitignore @@ -10,3 +10,4 @@ doc/src/_build/* doc/html/* doc/psycopg2.txt env +.tox diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 00000000..1aa25416 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,13 @@ +language: python + +python: + - 2.6 + - 2.7 + +before_script: + - psql -c 'create database psycopg2_test;' -U postgres + +install: + - python setup.py install + +script: make check diff --git a/ChangeLog b/ChangeLog deleted file mode 100644 index 57b803b6..00000000 --- a/ChangeLog +++ /dev/null @@ -1,2455 +0,0 @@ -2010-12-18 Daniele Varrazzo - - * connection.h: added codec attribute to avoid repeated codec name - lookups during unicode query/params manipulations. - - * setup.py: bumped to version 2.3.2.dev0 - - * psycopg/connection_int.c: applied patch from Marti Raudsepp to close - ticket #24. Fixed segfault in connection when DateStyle not available - (e.g. pgbouncer appars not passing it to the client) - -2010-12-15 Daniele Varrazzo - - * psycopg/utils.c: Added psycopg_strdup function. - -2010-12-14 Daniele Varrazzo - - * psycopg/connection_type.c: No need to put connection fields to zero. - - * lib/extensions.py: Improved mapping from PG to Py encodings. - - * psycopg/psycopgmodule.c: Added a few missing encodings: EUC_CN, - EUC_JIS_2004, ISO885910, ISO885916, LATIN10, SHIFT_JIS_2004. - -2010-12-04 Daniele Varrazzo - - * setup.py: bumped to version 2.3.1.dev0 - - * datetime modules reorganized to work around CentOS 5.5 x86_64 buld - problem. Closes ticket #23 - - * psycopg/typecast.h: dropped private functions interfaces. - -2010-12-01 Daniele Varrazzo - - * lib/extras.py: DictRow items can be updated. Patch by Alex Aster. - -2010-11-28 Daniele Varrazzo - - * Cancel patch from Jan integrated. - - * psycopg/connection_type.c: can't cancel a prepared connection - -2010-11-22 Daniele Varrazzo - - * psycopg/connection_int.c: dropped notices hack to get COPY errors from - V2 protocol. - -2010-11-18 Daniele Varrazzo - - * psycopg/connection.h: Added enum with possilbe isolation level states. - Also, general isolation levels cleanup and tests added. - - * psycopg/utils.c: compiler warning dropped. - - * typecast.h: functions exported to drop warnings. - - * datetime module initialized at is supposed to be. - - * mx.DateTime module initialized at is supposed to be. - -2010-11-17 Daniele Varrazzo - - * psycopg/connection_type.c: don't clobber exception if - isolation_level_switch fails. - -2010-11-16 Daniele Varrazzo - - * psycopg/connection_int.c: abort connection to protocol 2 server. - - * psycopg/pqpath.c - * psycopg/connection_int.c: dropped support for protocol 2 at compile - time and protocol 2-specific code. - - * psycopg/connection_int.c: don't run a query at every connection to detect - client encoding: use PQparameterStatus() instead. - - * psycopg/connection_int.c: don't run a query at every connection to set - the datestyle to ISO if PQparameterStatus() reports it already is. - -2010-11-11 Daniele Varrazzo - - * lib/extras.py: build the namedtuple only once per execution, not once - per fetch. - - * lib/extras.py: don't change the exception raised when fetching without a - result from NamedTupleCursor. - - * psycopg/connection_int.c: fixed notices order (ticket #9). - -2010-11-10 Daniele Varrazzo - - * psycopg/green.c: functions unused outside the module marked static. - -2010-11-09 Daniele Varrazzo - - * Replaced PyObject_CallFunction() with *ObjArgs() where more efficient. - - * Dropped PyArg_ParseTuple() calls in functions taking no arguments. - - * psycopg/microprotocols.c: small optimizations. - - * Avoid pointless string manipulation in NamedTupleCursor (ticket #10) - - * psycopg/microprotocols.c: Check the presence of a mro. - -2010-11-08 Daniele Varrazzo - - * psycopg/microprotocols.c: use faster function to build tuples. - - * psycopg/microprotocols.c: fixed refcount bug. - - * psycopg/microprotocols.c: use the adapter of an object superclass if - available. - - * psycopg/adapter_pdecimal.c: fixed crash in pdecimal_str with Python - 2.5.x releases in which is_finite() is not available. - -2010-11-06 Daniele Varrazzo - - * lib/extras.py: added NamedTupleCursor. - -2010-11-05 Daniele Varrazzo - - * setup.py: bumped to version 2.3.dev0 - - * merged dev branches for 2 phase commit, notify payload, hstore adapter - -2010-10-22 Daniele Varrazzo - - * MANIFEST.in: Dropped reference to removed TODO file: it breaks 'pip'. - -2010-10-08 Daniele Varrazzo - - * psycopg/typecast_binary.c: use PQfreemem to free memory allocated by - the libpq. Bug reported by Anton Kovalev. - - * dropped PSYCOPG_OWN_QUOTING. - - * psycopg/connection_int.c: Fixed access to freed memory in - conn_get_isolation_level(). Bug reported by Anton Kovalev. - -2010-10-06 Daniele Varrazzo - - * Merged James Henstridge work on two-phase commit support. - - 2008-07-24 James Henstridge - - * tests/test_psycopg2_dbapi20.py (Psycopg2TPCTests): hook up two - phase commit tests. - - * psycopg/xid_type.c (xid_len, xid_getitem): implement sequence - behaviour, as required for transaction IDs. - (XidType): There is no point in allowing subclasses of Xid. - - 2008-07-23 James Henstridge - - * psycopg/connection_type.c (psyco_conn_xid): add a - Connection.xid() method that instantiates Xid objects. - - * psycopg/psycopgmodule.c (init_psycopg): initialise the Xid - object type. - - * psycopg/xid.h: - * psycopg/xid_type.c: Implement a basic transaction ID object for - use in two phase commit. - - 2008-05-12 James Henstridge - - * beginnings of a TPC test harness - -2010-10-05 Daniele Varrazzo - - * psycopg/cursor_type.c: Common code in execute() and mogrify() merged. - - * psycopg/cursor_type.c: cursor.mogrify() accepts unicode queries. - -2010-09-23 Daniele Varrazzo - - * lib/errorcodes.py: Added PostgreSQL 9.0 error codes. - -2010-08-05 Daniele Varrazzo - - * psycopg/connection_int.c: don't execute a ROLLBACK on close()/GC. - The command wasn't sent since 2.2.0 due to a bug, but after a ML - discussion this behaviour proved more correct so the bug has become a - feature. - -2010-07-18 Federico Di Gregorio - - * Release 2.2.2. - -2010-07-09 Daniele Varrazzo - - * psycopg/cursor_type.c: executemany() propagates exceptions raised by the - iterable to the caller. - - * lib/pool.py: dropped logging.basicConfig() call. It messes up with - projects using logging but where no handler is installed on the root - logger. Bug reported by Joe Abbate. - - * psycopg/cursor_type.c: exceptions raised in the columns iterator of the - copy methods propagated to the caller. - -2010-05-20 Daniele Varrazzo - - * psycopg/typecast_datetime.c: Round seconds in historical timezones to - the nearest minute. - - * lib/extras.py: register_tstz_w_secs() is now no-op. - -2010-05-17 Federico Di Gregorio - - * Release 2.2.1. - - * Builds again on Windows. - -2010-05-16 Federico Di Gregorio - - * Release 2.2.0. - -2010-05-15 Federico Di Gregorio - - * typecast.c: Fixed problem related to receiving None from Python - when a string was expected. - -2010-05-07 Daniele Varrazzo - - * psycopg/adapter_datetime.c: Fixed TimestampFromTicks for second - values > 59.5. - - Bug reported and fixed by Jozsef Szalay on 2010-05-06 at 14:11:59.999920. - - * psycopg/adapter_datetime.c: Fixed same bug for TimeFromTicks. - -2010-05-04 Daniele Varrazzo - - * Added typecasters for arrays of specific MX/Py time-related types. - - * psycopg/adapter_[mx]datetime.c: Explicit cast of the SQL representation - of time-related objects. - -2010-05-03 Daniele Varrazzo - - * psycopg/adapter_binary.c: Adapt buffer objects using an explicit cast on - the string literal (bug reported by Peter Eisentraut) - -2010-04-20 Daniele Varrazzo - - * lib/pqpath.c: Fixed reference leak in notify reception. - - * Notifies are collected if available after every query execution. - -2010-04-13 Daniele Varrazzo - - * lib/extensions.py: DECIMAL typecaster imported from _psycopg. - - * lib/extensions.py: PY* and MX* time typecaster imported from _psycopg. - -2010-04-11 Daniele Varrazzo - - * psycopg/connection_type.c: Correctly parse keywords in connect(). - -2010-04-07 Daniele Varrazzo - - * psycopg/pqpath.c: Ensure running COPY in blocking mode. - - * psycopg/pqpath.c: Free the GIL in blocking operations in V2 COPY FROM. - - * psycopg/pqpath.c: Evaluate Python objects only once outside the COPY I/O - loops. - -2010-04-05 Federico Di Gregorio - - * Fixed problem with asynchronous NOTIFYs. - - * Integrated async pacthes from Jan's git tree. - -2010-03-13 Federico Di Gregorio - - * Release 2.0.14. - -2010-03-11 Federico Di Gregorio - - * setup.py: one-liner from Devrim GÜNDÜZ to build with PostgreSQL - alpha. - -2010-02-28 Federico Di Gregorio - - * psycopg/adapt_decimal.c: Python 2.4 decimal type does not support - .isfinite() and two different calls to ._isinfinity() and ._isnan() are - required. This fixes both a test failure and a segfault. - -2010-02-15 Federico Di Gregorio - - * Added new Decimal adapter that correctly converts NaN and infinity - to PostgreSQL NaN numeric values. Also added tests. - -2010-02-15 Daniele Varrazzo - - * lib/errorcodes.py: Updated to PostgreSQL 8.4; added lookup() function. - -2010-02-12 Daniele Varrazzo - - * Stop the loop variable used to create __all__ leaking in the module. - - * Fixed Inet constructor. - - * Fixed docstring for 'QueryCanceledError' exception. - -2010-02-10 Federico Di Gregorio - - * lib/extensions.py: Binary was not imported from _psycopg; now it is - - * lib/__init__: SQL_IN adapter is now automatically registered. - - * ZPsycopgDA/db.py: removed logging debug calls; psycopg does - not depend on the logger module. - -2010-02-12 Federico Di Gregorio - - * License migration: psycopg2 is now LGPL3 + OpenSSL exception. - - * TODO file was never updated so lets remove it. - -2010-02-10 Federico Di Gregorio - - * lib/extras.py: fixed register_tstz_w_secs() error as reported by - Karsten Hilbert. - -2009-11-25 Federico Di Gregorio - - * psycopg/microprotocols.c: "can't adapt" message now includes full - type information (adapted patch from Eric Chamberlain). - - * tests/types_basic.py: fixed test broken by float precision fix. - -2009-11-09 Federico Di Gregorio - - * psycopg/adapter_pfloat.c: applied patch from Remy Blankto fix float - loss of precision. - -2009-10-04 Federico Di Gregorio - - * Release 2.0.13. - - * setup.py: applied patch from Christian Jacobsen to link to static - version of libpq. - - * lib/extras.py: added support for UUID arrays. - - * psycopg/connection_int.c: applied patch from Richard Davies to avoid - deadlocks with multiple threads using the same connection. - -2009-08-08 Federico Di Gregorio - - * Release 2.0.12. - - * psycopg/lobject_int.c: fixed problem with writing large data using - lo_write: apparently the large objects code does not like non-blocking - connections. - - * setup.py: fixed version detection for PostgreSQL rc, as - suggested by Sok Ann Yap. - - * ZPsycopgDA/db.py: applied serialization error retry from Brian - Sutherland. - - * Implemented connection.reset() method to reset the connection to - well-know default parameters. This is much faster than closing and - reopening the connection. (Suggested by a bug report by Glenn - Maynard.) - - * psycopg/cursor_type.c: unified size macro definitions in COPY TO - and COPY FROM operations: now the buffer for column names is 8192 - bytes that should be enough even for very large tables. - -2009-05-19 Federico Di Gregorio - - * Applied patch from Robert Munro to fix version check - in ZPsycopgDA. - -2009-05-10 Federico Di Gregorio - - * Release 2.0.11. - - * lib/extras.py: fixed crash in fetchone() when prefetching using - a RealDictCursor. - - * psycopg/cursor_ext.c: now raise correct exception when fetching - using a custom row factory results in an error. - - * lib/extras.py: applied DictRow "diet" patch from Marko Kreen. - -2009-04-21 Federico Di Gregorio - - * setup.py: applied patch from Elvis Pranskevichus to make - PostgreSQL version detection more robust. - -2009-04-20 Federico Di Gregorio - - * Release 2.0.10. - - * psycopg/cursor_type.c: patch from Gangadharan to avoid double - free of the cursor when the connection that creates it is closed - implicitly by dealloc. - -2009-04-19 Federico Di Gregorio - - * psycopg/connection_type.c: patch from Gangadharan to avoid double - free of the connection object when calling close() implicitly. - - * psycopg/connection_type.c: patch from Marko Kreen to implement - get_parameter_status(). - - * psycopg/connection.*: exposed protocol_version and - server_version attributes on the connection object. - - * lib/extras.py: patch from Marko Kreen to implement missing dict - methods in DictRow. - -2009-04-04 Federico Di Gregorio - - * connection_int.c(conn_notice_callback): removed all Python - calls because conn_notice_callback() can be called without a lock - on the GIL. Moved processing and cleanup of notices into their - own functions that are called while holding the GIL. - -2009-04-01 Federico Di Gregorio - - * Applied patch from Menno Smits to fix failures in test_dates - when executed in older version of Python. - - * Applied patch from Menno Smits to fix lobject test failures. - -2009-03-08 Federico Di Gregorio - - * microprotocols.c: None is always adapted to NULL to avoid - errors when calling adapt on nested types without first checking - for None. - -2009-03-02 Federico Di Gregorio - - * Applied modified patch from Karsten Hilbert to provide a - type-caster able to parse times with seconds in the time zone. - - * Applied patch from Menno Smits to avoid problems with DictCursor - when the query is executed by a named cursor. Also added Menno's - tests and uniformed other DictCursor tests. - - * psycopg/psycopgmodule.c: fixed unwanted exception when passing an - explicit None to register_type(). - - * psycopg/config.h: applied patch from Jason Erickson to build - with MSVC. Solaris version of isinf() is now a macro. - -2009-02-27 Federico Di Gregorio - - * psycopg/config.h: added check to provide compatible isinf() - for Solaris (thanks to Jeremy Mason.) - -2009-02-23 Federico Di Gregorio - - * Release 2.0.9. - -2009-02-17 James Henstridge - - * psycopg/utils.c (psycopg_escape_string): same here. - - * psycopg/adapter_binary.c (binary_escape): simplify PostgreSQL - version check. - - * setup.py (psycopg_build_ext.finalize_options): use a single - define of the PostgreSQL version in a form that can easily be used - by #ifdefs. - - * tests/test_dates.py (DatetimeTests, mxDateTimeTests): full test - coverage for datetime and time strings with and without time zone - information. - - * psycopg/typecast_datetime.c (typecast_PYDATETIME_cast): adjust - to handle the changes in typecast_parse_time. - (typecast_PYTIME_cast): add support for time zone aware time - values. - - * psycopg/typecast_mxdatetime.c (typecast_MXDATE_cast): make sure - that values with time zones are correctly processed (even though - that means ignoring the time zone value). - (typecast_MXTIME_cast): same here. - - * psycopg/typecast.c (typecast_parse_time): Update method to parse - second resolution timezone offsets. - - * psycopg/typecast.c (typecast_parse_time): Fix up handling of - negative timezone offsets with a non-zero minutes field. - - * tests/test_dates.py (DatetimeTests): Add tests for time zone - parsing. The test for HH:MM:SS time zones is disabled because we - don't currently support it. - -2009-02-16 Federico Di Gregorio - - * FreeBSD now has round(). Modified config.h as suggested by - Jeroen Ruigrok van der Werven. - -2009-02-06 Federico Di Gregorio - - * Applied patch by Markus Demleitner to make executemany() return - the sum of modified rows by executed statements or -1 if any one - statement returns it. This is still DBAPI-2.0 compliant. - -2009-01-23 Federico Di Gregorio - - * Fixed problem mailed by Markus Demleitner about Python to - PostgreSQL conversions of "nan" and "inf" floats resulting in - backend errors. Added the new psycopg2.extensions.Float adapter - that correctly handle both values and unit tests. (The opposite - conversion was already working.) - -2009-01-20 Federico Di Gregorio - - * Fixed problem reported by Lawrence Oluyede where - register_type() didn't work on connection and cursors - suclasses. - -2009-01-10 Federico Di Gregorio - - * psycopg/cursor_type.c: cursor.isready() now raise an exception - when the libpq PQconsumeInput() call fails. - -2008-12-04 Federico Di Gregorio - - * psycopg/lobject_type.c: fixed memory leak. The patch was kindly - sent from a psycopg user but I wrongly deleted the email so no - kudos (and I had to fix the problem by myself!) - -2008-11-25 Federico Di Gregorio - - * psycopg/cursor_type.c: integrated patch from Alejandro Dubrovsky. - Note that the statically allocated buffer should probably go away - in favor of always allocating the buffer dinamically. - - * psycopg/utils.c: modified patch from Alejandro Dubrovsky to - support quoted separators in COPY queries: now all the string - quoting code is in utils.c and the same function is used by - qstrings and everything else (like the COPY code.) - -2008-09-24 Federico Di Gregorio - - * lib/extras.py: added inet support and related tests. - -2008-09-23 James Henstridge - - * psycopg/psycopg.h (NotSupportedError_doc): clean up - spelling/grammar a bit, using exception description from the PEP. - -2008-09-23 Federico Di Gregorio - - * Applied patch from Brian Sutherland that fixes NULL - valus in UUID support. - -2008-09-19 Federico Di Gregorio - - * lib/extras.py: added UUID support, modeled after the code - sent by Brian Sutherland. - -2008-09-16 Federico Di Gregorio - - * Release 2.0.8. - -2008-07-26 Federico Di Gregorio - - * psycopg/connection_type.c: merged get_backend_pid() method - by Casey Duncan. - -2008-07-23 James Henstridge - - * psycopg/lobject_type.c (lobject_setup): use - FORMAT_CODE_PY_SSIZE_T in Dprintf() call for 64-bit compatibility - when using Python 2.5 or later. - (lobject_dealloc): same here. - -2008-07-18 James Henstridge - - * psycopg/adapter_qstring.c (qstring_traverse): add cyclic GC - traversal for quoted string adapters. - - * psycopg/adapter_pboolean.c (pboolean_traverse): add cyclic GC - traversal for boolean adapters. - - * psycopg/adapter_mxdatetime.c (mxdatetime_traverse): add cyclic - GC traversal for mxdatetime adapters. - - * psycopg/adapter_datetime.c (pydatetime_traverse): add cyclic GC - traversal for datetime adapters. - -2008-07-01 James Henstridge - - * psycopg/adapter_binary.c (binary_traverse): add cyclic GC - traversal for binary adapters. - - * psycopg/adapter_asis.c (asis_traverse): add cyclic GC traversal - for AsIs adapters. - - * psycopg/adapter_list.c (list_traverse): add cyclic GC traversal - for list adapters. - -2008-05-28 James Henstridge - - * psycopg/cursor_type.c (cursor_setup): incref before setting - attributes, to make things GC-safe. - - * psycopg/cursor_int.c (curs_reset): make clearing of description - and casts attributes GC-safe. - - * psycopg/typecast.c (typecast_traverse): implement cyclic GC - traversal for typecasters. - - * psycopg/connection_type.c: - * psycopg/cursor_type.c: add support for cyclic GC traversal (no - support for clearing). - - * psycopg/python.h: add definitions for Py_CLEAR() and Py_VISIT() - for compatibility with old versions of Python. - -2008-06-28 Federico Di Gregorio - - * setup.py: fixed problem with spaces in pg_config path. - -2008-05-27 Federico Di Gregorio - - * psycopg/pqpath.c: better error checks in _pq_copy_in_v3 to - avoid calling blocking libpq functions when the connection to - the server has been broken - -2008-05-19 Federico Di Gregorio - - * psycopg/cursor_type.c: fixed memory leak in .executemany(); on - error "iter" was not dec'reffed. - -2008-05-06 James Henstridge - - * psycopg/lobject.h (lobjectObject): remove "mode" struct member, - since it was unused. - - * psycopg/lobject_*.c: replace uses of the closed struct member, - and change the Python level attribute to a getset. - - * psycopg/lobject.h (lobjectObject): remove the closed member, - since "fd < 0" gives us the same information. Reorder the struct - members for better packing. - - * psycopg/lobject*: const'ify the code. - - * tests/test_lobject.py (LargeObjectTests): add more tests, - including behaviour on closed lobjects and stale lobjects. - - * psycopg/lobject_type.c (psyco_lobj_close): don't mark the - connection closed here because it is done by - lobject_close_locked(). - - * psycopg/lobject_int.c (lobject_open): mark objects as not closed - if we successfully open them. - (lobject_close_locked): mark the lobject closed here. - (lobject_export): ensure we are in a transaction, since - lo_export() issues multiple queries. - - * psycopg/lobject_type.c (lobject_setup): make lobjects start closed. - -2008-05-05 James Henstridge - - * psycopg/lobject.h: don't export the lobjectType symbol. - -2008-05-05 Federico Di Gregorio - - * Fixed sun build problem by adding "sun" to config.h checks. - -2008-05-05 James Henstridge - - * psycopg/pqpath.c (pq_complete_error): get rid of double free - error in case when pgres is NULL. - - * tests/test_lobject.py: add some basic tests for large object - code. - - * psycopg/lobject*.[ch]: port the large object code to work with - current psycopg code. - -2006-09-01 Federico Di Gregorio - - * psycopg/connection_type.c: merged in double mutex destroy patch - from Joerg Sonnenberger. - - * Implemented large objects support. - - * psycopg/connection_int.c: removed increment of self->mark, - now it is done directly in pqpath.c to make sure even the - large object support gets it. - - * Starting 2.1 development. - -2008-04-21 James Henstridge - - * tests/test_quote.py (QuotingTestCase.test_unicode): If the - server encoding is not UTF8, skip the unicode test and emit a - warning. - -2008-04-21 Jorgen Austvik - - * tests/*.py: use the DSN constructed in tests/__init__.py. - - * tests/__init__.py: allow setting the host, port and user for the - DSN used by the tests through the environment. - -2008-03-17 Federico Di Gregorio - - * Release 2.0.7. - -2008-03-31 James Henstridge - - * psycopg/connection_type.c (connection_dealloc): free - connection->encoding with free() instead of PyMem_Free(). - - * psycopg/connection_int.c (conn_connect): use malloc() to - allocate connection->encoding instead of PyMem_Malloc(), since it - is freed in other places with free() and assigned to with - strdup(). - -2008-03-26 James Henstridge - - * psycopg/typecast.c (typecast_from_c): fix up some reference - leaks. This leak affected a bounded set of objects, so doesn't - account for any gradual leaks. - -2008-03-19 James Henstridge - - * psycopg/connection_int.c (conn_notice_callback): don't leak - notice messages. - -2008-03-17 Federico Di Gregorio - - * psycopg/adapter_datetime.c: fixed double decref when using - a PyObject as a parameter in a nested call (line 415). - -2008-03-17 James Henstridge - - * psycopg/typecast.c (typecast_parse_time): give the correct - return value for partially parsed time values. - - * psycopg/typecast_mxdatetime.c (typecast_MXDATE_cast): return - NULL after setting DataError. Also, don't treat it as an error if - typecast_parse_time() returns 0 (as might happen if the remainder - of the string is " BC"). - - * psycopg/typecast_datetime.c (typecast_PYDATE_cast): return NULL - after setting DataError. - (typecast_PYDATETIME_cast): same here. - (typecast_PYTIME_cast): same here. - - * tests/test_dates.py - (CommonDatetimeTestsMixin.test_parse_incomplete_date): test that - parsing incomplete date values results in DataError. - (CommonDatetimeTestsMixin.test_parse_incomplete_time): same for - times. - (CommonDatetimeTestsMixin.test_parse_incomplete_time): same for - datetimes. - -2008-03-07 Jason Erickson - - * psycopg/pqpath.c (pq_raise): if PSYCOPG_EXTENSIONS is not - defined, raise OperationalError rather than - TransactionRollbackError for deadlock or serialisation errors for - protocol versions less than 3. - - * psycopg/psycopgmodule.c (psyco_connect): fix off by one error in - calculating the length of the DSN. - -2008-03-07 James Henstridge - - * psycopg/pqpath.c (_pq_fetch_tuples): Don't call Python APIs - without holding the GIL. - -2008-02-27 James Henstridge - - * NEWS: add some draft NEWS items for a 2.0.7 release. - - * runtests.py: add a harness to run all the psycopg tests against - the version built by distutils. - -2008-01-22 James Henstridge - - * psycopg/typecast.c (typecast_pydatetime): make array static. - (typecast_mxdatetime): same here. - - * psycopg/typecast_builtins.c (typecast_builtins): make array - static. - - * psycopg/psycopgmodule.c: add hidden visibility to a bunch of - global variables here. - - * psycopg/psycopg.h: add set QueryCanceledError and - TransactionRollbackError to hidden visibility. - - * psycopg/*.[ch]: add const qualifier to various string arguments - to functions (typecast functions and conn_switch_isolation_level). - -2008-01-21 James Henstridge - - * setup.cfg (define): remove PSYCOPG_DISPLAY_SIZE from default - list of defines, as discussed on mailing list. It slows down - queries with very little benefit. - - * psycypg/*.h: apply HIDDEN to all global variables and functions - that should not be exported from the module. This results in a 5% - reduction in code size and shortens the dynamic symbol table. - - * psycopg/config.h: If GCC >= 4.0 is installed, define the HIDDEN - symbol to apply the "hidden" visibility attribute. - -2008-01-19 James Henstridge - - * tests/test_connection.py (ConnectionTests): add simple tests for - the Connection and Cursor "closed" attributes. - - * psycopg/cursor_type.c (psyco_curs_get_closed): add a "closed" - attribute to cursors. It will be True if either the cursor or its - associated connection are closed. This fixes bug #164. - - * psycopg/pqpath.c (pq_raise): remove unused arguments to - function, and simplify. - (pq_resolve_critical): make function static, since it isn't being - used outside of pqpath.c any more. - -2008-01-17 James Henstridge - - * ZPsycopgDA/DA.py (Connection.__init__): Default the encoding to - UTF-8, fixing bug #190. - (App.ImageFile): simplify ImageFile import using patch from - chrism, fixing bug #198. - -2008-01-16 James Henstridge - - * tests/test_transaction.py (DeadlockSerializationTestCase): port - over some tests for serialisation and deadlock errors, - demonstrating that TransactionRollbackError is generated. - (QueryCancelationTests): add a test to show that - QueryCanceledError is raised on statement timeouts. - - * psycopg2da/adapter.py (_handle_psycopg_exception): rather than - checking exception messages, check for TransactionRollbackError. - - * psycopg/pqpath.c (exception_from_sqlstate): return - TransactionRollbackError for 40xxx errors, and QueryCanceledError - for 57014 errors. - (pq_raise): If we are using an old server, use - TransactionRollbackError if the error message contains "could not - serialize" or "deadlock detected". - - * psycopg/psycopgmodule.c (_psyco_connect_fill_exc): remove - function, since we no longer need to store pointers to the - exceptions in the connection. This also fixes a reference leak. - (psyco_connect): remove _psyco_connect_fill_exc() function call. - - * psycopg/connection.h (connectionObject): remove exception - members from struct. - - * psycopg/connection_type.c (connectionObject_getsets): modify the - exception attributes on the connection object from members to - getsets. This reduces the size of the struct. - - * lib/extensions.py: import the two new extensions. - - * psycopg/psycopgmodule.c (exctable): add new QueryCanceledError - and TransactionRollbackError exceptions. - -2008-01-16 James Henstridge - - * tests/__init__.py (test_suite): add date tests to test suite. - - * tests/test_dates.py: add tests for date/time typecasting and - adaption. - - * psycopg/adapter_mxdatetime.c (mxdatetime_str): add support for - outputting BC dates (which involves switching them to one-based - dates). Also remove broken handling of microseconds. - - * psycopg/typecast.c (typecast_parse_date): if the string ends - with "BC" adjust the year value to be a zero-based BC value as - used by mx.DateTime (datetime doesn't support BC dates). - (typecast_parse_time): ignore ' ', 'B' and 'C' in time strings - rather than treating them as part of the seconds part of the time. - -2008-01-14 James Henstridge - - * psycopg/typecast_array.c (typecast_array_scan): set an initial - value for quotes to keep gcc happy. - - * psycopg/*.c: add missing static modifier on many functions. - -2008-01-12 James Henstridge - - * tests/test_transaction.py - (TransactionTestCase.test_failed_commit): Expect IntegrityError - instead of OperationalError. - - * psycopg/pqpath.c (exception_from_sqlstate): new function that - converts an SQLSTATE error code to the corresponding exception - class. - (pq_raise): use exception_from_sqlstate() to pick which exception - to use when working with protocol version 3. - (pq_complete_error): Let pq_raise() pick an appropriate exception - rather than forcing OperationalError. - -2008-01-11 James Henstridge - - * psycopg/adapter_binary.c (binary_quote): apply Brandon Rhodes' - patch from ticket #209 to check return value from - PyObject_AsCharBuffer(). This fixes the segfault. - (binary_quote): switch from PyObject_AsCharBuffer() to - PyObject_AsReadBuffer() to support buffer objects that don't - implement the bf_getcharbuf protocol. - - * tests/types_basic.py (TypesBasicTests.testBinary): Test round - tripping of bytea buffers. Currently segfaults. - - * psycopg/connection_int.c (conn_close): fix for new - pq_abort_locked() prototype. - (conn_switch_isolation_level): fix for new pq_abort_locked() - prototype, and use pq_complete_error() to show error message. - (conn_set_client_encoding): same here. - - * psycopg/pqpath.c (pq_execute_command_locked): remove static - modifier. - (pq_complete_error): same here. - (pq_abort_locked): add pgres and error arguments. - (pq_abort): call pq_abort_locked() to reduce code duplication. - -2007-12-23 James Henstridge - - * psycopg/pqpath.c (pq_execute_command_locked): add an error - argument to hold an error when no PGresult is returned by PQexec, - rather than using pq_set_critical(). - (pq_complete_error): new function that converts the error returned - by pq_execute_command_locked() to a Python exception. - (pq_begin_locked): add error argument. - (pq_commit): use pq_complete_error(). - (pq_abort): use pq_complete_error(). - (pq_abort_locked): always call pq_set_critical() on error, and - clear the error message from pq_execute_command_locked(). - (pq_execute): use pq_complete_error() to handle the error from - pq_begin_locked(). - - * psycopg/pqpath.c (pq_begin): remove unused function. - - * psycopg/connection_type.c (psyco_conn_commit): if conn_commit() - raises an error, just return NULL, since it is now setting an - exception itself. - (psyco_conn_rollback): same here. - - * psycopg/connection_int.c (conn_commit): don't drop GIL and lock - connection before calling pq_commit(). - (conn_rollback): same here. - (conn_close): use pq_abort_locked(). - (conn_switch_isolation_level): same here. - (conn_set_client_encoding): same here. - - * psycopg/pqpath.h: add prototype for pq_abort_locked(). - - * psycopg/pqpath.c (pq_commit): convert function to run with GIL - held, and handle errors appropriately. - (pq_abort): same here. - (pq_abort_locked): new function to abort a locked connection. - -2007-12-22 James Henstridge - - * psycopg/pqpath.c (pq_raise): add a "pgres" argument so we can - generate nice errors not related to a particular cursor. - (pq_execute): use pq_begin_locked() rather than pq_begin(). Use - pq_raise() to handle any errors from it. - - * psycopg/pqpath.c (pq_execute_command_locked): helper function - used to execute a command-style query on a locked connection. - (pq_begin_locked): a variant of pq_begin() that uses - pq_execute_command_locked(). - (pq_begin): rewrite to use pq_begin_locked(). - -2007-12-22 James Henstridge - - * psycopg/config.h: only print debug messages if - psycopg_debug_enabled is true. - - * psycopg/psycopgmodule.c (init_psycopg): set - psycopg_debug_enabled to true if the $PSYCOPG_DEBUG environment - variable is set. - -2007-12-21 Federico Di Gregorio - - * Applied win32 patch from Jason Erickson. - - * Applied patch from Thomas Lotze to use correct version - for new Zopes. - -2007-12-20 James Henstridge - - * psycopg/pqpath.c (pq_execute): uncomment the "curs->pgres == - NULL" error handler after the PQexec() call. This is needed to - catch database disconnects (and probably other errors). According - to Federico, it was commented out to avoid a spurious error, so we - should watch for problems. - -2007-12-20 James Henstridge - - * psycopg/pqpath.c (pq_raise): only remove the first 8 characters - of the exception message if it actually gives the severity. - - * psycopg/pqpath.h (pq_resolve_critical): add prototype, since - this function is being used from connection_int.c. - - * psycopg/psycopg.h: update psyco_set_error() prototype. - - * psycopg/psycopgmodule.c (psyco_errors_init): set pgerror, pgcode - and cursor class attributes to None on psycopg2.Error so that the - attributes will always be available (simplifies error handling). - (psyco_set_error): add const qualifiers to msg, pgerror and pgcode - arguments. - Don't bother setting pgerror, pgcode or cursor to None if they are - not provided -- the class defaults take care of this. - -2007-11-11 Daniele Varrazzo - - * Use escape string syntax for string escape if connected to a - server requiring it. - -2007-11-09 Daniele Varrazzo - - * Use escape string syntax for binary escape if connected to a - server requiring it. - - * Put a limit on the number of notices stored in the connection. - -2007-10-21 Daniele Varrazzo - - * Fixed bug #192 (Decimal support not safe for use with multiple sub - interpreters) as proposed by Graham Dumpleton. - -2007-09-08 Federico Di Gregorio - - * Added MonoDevelop project, yahi! - -2007-09-06 Federico Di Gregorio - - * Fixed bug #194. - -2007-09-01 Federico Di Gregorio - - * Added "name" parameter to all .cursor() calls in extras.py. - -2007-05-29 Federico Di Gregorio - - * Release 2.0.6. - - * ZPsycopgDA/DA.py: removed call to now obsolete .set_type_casts(). - - * Applied patch from mkz (ticket #187) to add error handling when - calling conn_commit() and conn_rollback(). Fixes #187. - - * cursor.copy_expert() implementation by David Rushby (copy_expert - set 5/5.) - - * SQL validation refactor patch from David Rushby (copy_expert - set 4/5.) - - * Reference count leak fix from David Rushby (copy_expert set 3/5.) - - * 64 bit fix patch from David Rushby (copy_expert set 2/5.) - - * Applied whitespace normalization patch from David Rushby (copy_expert - set 1/5.) - -2007-04-25 Federico Di Gregorio - - * psycopg/connection_type.c: added support for a new method - .get_transaction_status() that returns the backend transaction - status as libpq knows. Also added some symbolic constants to - psycopg.extensions module (TRANSACTION_STATUS_XXX). - -2007-04-14 Federico Di Gregorio - - * psycopg/psycopg.h: fixed probable typo in definition of - CONV_CODE_PY_SSIZE_T: d -> i for Python < 2.5. - - * Fixed some of the examples. - -2007-04-13 Federico Di Gregorio - - * Applied slighly modified patch from daniel (#176). Made - buffer size a compile-time parameter. - - * Applied patch from David Rushby: typecast_binary.c cleanup. - - * Applied patch from David Rushby for int->size_t transition. - -2007-04-12 Federico Di Gregorio - - * Applied patch from Jason Erickson to fix win32 build glitches. - -2007-04-11 Federico Di Gregorio - - * Release 2.0.6b2. - - * psycopg/cursor_type.c: added check to raise an error when - some crazy programmer tries to use different argument formats - in the same query string. Fixes #162. - - * Applied patch from David Rushby to fix win32 builds. - -2007-04-10 Federico Di Gregorio - - * Applied patch from David Rushby to fix mem and ref leaks in - psycopg2.connect(). - - * Applied super-patch from David Rushby to fix Python 2.5 and 64 - bit problems (all of them, kudos!) - -2007-02-22 Federico Di Gregorio - - * Added support for per-connection and per-cursor typecasters. - -2007-02-11 Federico Di Gregorio - - * psycopg/pqpath.c: ported psycopg1 patch from #135. - - * psycopg/connection_type.c: now the password is obfuscated after we try - to connect to the backend. This really fixes #147. - -2007-01-20 Federico Di Gregorio - - * Release 2.0.6b1. - - * Added encodings patch from Karsten Hilbert. - -2007-01-19 Federico Di Gregorio - - * psycopg/adapt_list.c: added support for None. And this closes - the other half. - - * psycopg/typecast_array.c: added support for detecting the NULL - special string and converting it as a real NULL if not enclosed in - quotes. This closes half of #154. - -2007-01-16 Federico Di Gregorio - - * psycopg/connection_type.c: .set_client_encoding() now converts the - argument to upper case to make sure it has the same case of the entries - in the PostgreSQL -> Python encoding conversion table. - - * lib/extras.py: merged DictCursor from #143 and renamed it - RealDictCursor because allows access by cursor keys _only_. - Also cleaned up a little bit the implementation of both DictCursor - and RealDictCursor by introducing DictCursorBase. - - * psycopg/pqpath.cs: new checks for NULL values (meaning an - exception was raised) in all method calls (fixes #134). - - * psycopg/psycopgmodule.c: applied LATIN patch from #148. - - * psycopg/connection_type.c: obfuscate password after using it. - - * lib/extras.py: moved SQL_IN to extensions.py; we're now officially - adapting tuples. - - * psycopg/adapt_mxdatetime.c: fixed #137 by accessing the 'date' and - 'time' attributes of the mx.DateTime instance: they are always in ISO - format. - - * psycopg/typecast_datetime.c: fixed problem with year > 9999. - -2006-10-29 Federico Di Gregorio - - * Applied patch from Jason Erickson to make psycopg build - again on win32 (closes: #132). - -2006-09-30 Federico Di Gregorio - - * ZpsycopgDA/DA.py: applied the infinity patch from 1.1 (fixes #122). - - * psycopg/adapter_datetime.py: fixed conversion problem with seconds - in the (59,60) range (fixes #131). - - * ZpsycopgDA/DA.py: we now split on GMT+, GMT-, + or -. This should - fix bug #129. - - * psycopg/adapter_datetime.py: now TimeFromTicks and - TimestampFromTicks both accept fractionary seconds (fixes #130). - -2006-09-23 Federico Di Gregorio - - * lib/errorcodes.py: added list of all PostgreSQL error codes - compiled by Johan Dahlin. - - * psycopg/psycopg.h: applied compatibility macros from PEP 353. - - * Applied patch 1/3 from Piet Delport; from his email: - - psycopg2-Py_ssize_t-input.diff adjusts variables used for parameters - and return values. These changes only prevent overflowing on values - greater than 32-bits, so they're not as critical as the other two - patches. I tried to leave places unchanged where the input size is - already constrained to sizeof(int), but i might have missed a few - either way, not being too familiar with the codebase. - - * Applied patch 2/3 from Piet Delport; from his email: - - psycopg2-Py_ssize_t-output.diff adjusts variables used as outputs - from CPython API calls: without it the calls try to write 64 bits - to 32 bit locations, trampling over adjacent values/pointers, - and segfaulting later. - - * Applied patch 1/3 from Piet Delport; from his email: - - psycopg2-PyObject_HEAD.diff adds missing underscores to several - "PyObject_HEAD" declarations. As far as i can tell from gdb, the - "PyObject HEAD" versions end up accidentally meaning almost exactly - the same, but get aligned differently on AMD64, resulting in wrong - size calculation and memory corruption later. - -2006-09-11 Federico Di Gregorio - - * Fixed syntax error in lib/extras.py (see #123) - -2006-09-02 Federico Di Gregorio - - * psycopg/adapter_mxdatetime.c, psycopg/psycopgmodule.c: fixed last - problem with non-constant initializers. - - * psycopg/connection_type.c: applied patch from Joerg Sonnenberger - to fix a double mutex destroy. - - * Release 2.0.5.1. - - * psycopg/cursor_type.c: applied patch from Jason Erickson to - build on MSVC and older gcc. - -2006-09-01 Federico Di Gregorio - - * Release 2.0.5. - - * Fixed patch from #119, see tracker for details. - - * Preparing release 2.0.5. - - * psycopg/psycopgmodule.c: fixed filling of connection errors - to include OperationalError. - - * setup.py: removed pydatetime option from initialize_options - to make sure that the value in setup.cfg is used. - - * psycopg/psycopgmodule.c: applied patch from jdahlin (#120) - to have .connect() accept either a string or int as the port - parameter. - - * psycopg/adapter_binary.c: applied patch from jdahlin (#119) - to fix the segfault on empty binary buffers. - - * psycopg/connection_type.c: added .status attribute to expose - the internal status. - - * psycopg/pqpath.c: applied patch from intgr (#117) to fix - segfault on null queries. - - * psycopg/cursor_type.c: applied patch from intgr (#116) to - fix bad keyword naming and segfault in .executemany(). - - * ZPsycopgDA/DA.py: applied ImageFile patch from Charlie - Clark. - - * lib/pool.py: applied logging patch from Charlie Clark. - It will probably get a makeup and be moved to the top-level - module later. - -2006-08-02 Federico Di Gregorio - - * Release 2.0.4. - - * Fixed bug in float conversion (check for NULL string was - erroneously removed in 2.0.3!) - -2006-07-31 Federico Di Gregorio - - * Release 2.0.3. - - * psycopg/cursor_type.c: applied patch from jbellis (#113) to - allow column selection in .copy_from(). - - * psycopg/psycopgmodule.c: fixed memory leak in custom exceptions - (applied patch from #114). - -2006-07-26 Federico Di Gregorio - - * psycopg/adapter_datetime.c (pydatetime_str): fixed error - in conversion of microseconds for intervals and better algo - (thanks to Mario Frasca.) - -2006-06-18 Federico Di Gregorio - - * psycopg/adapter_binary.c: same as below. - - * psycopg/adapter_qstring.c: does not segfault anymore if - .getquoted() is called without preparing the qstring with - the connection. - -2006-06-15 Federico Di Gregorio - - * psycopg/typecast_basic.c: fixed problem with bogus - conversion when importing gtk (that was crazy, I didn't - understand why it happened but the new code just fixes it.) - - * ZPsycopgDA/db.py: better type analisys, using an hash - instead of a series of if (variation on patch from Charlie - Clark.) - -2006-06-11 Federico Di Gregorio - - * Release 2.0.2. - - * psycopg/typecast_array.c (typecast_array_cleanup): fixed a - problem with typecast_array_cleanup always returning the new - string length shorter by 1 (Closes: #93). - - * psycopg/adapter_binary.c: as below. - - * psycopg/adapter_qstring.c: wrapped #warning in #ifdef __GCC__ - because other compilers don't have it and it will just break - compilation (patch from jason, our great win32 builder). - - * psycopg/adapter_list.c: applied patch to adapt an empty list - into an empty array and not to NULL (from iGGy, closes: #108). - - * psycopg/cursor_type.c: applied patch from wkv to avoid - under-allocating query space when the parameters are not of the - right type (Closes: #110). - - * psycopg/connection_int.c: applied patch from wkv to avoid off - by one allocation of connection encoding string (Closes: #109). - -2006-06-09 Federico Di Gregorio - - * Release 2.0.1. - - * Fixed some buglets in ZPsycopgDA (was unable to load due - to shorter version number in psycopg module.) - -2006-06-08 Federico Di Gregorio - - * Release 2.0. - - * ZPsycopgDA/DA.py: removed Browse table for 2.0 release; we'll - add it back later. - -2006-05-26 Federico Di Gregorio - - * Applied better PostgreSQL patch from AA. - -2006-05-24 Federico Di Gregorio - - * Enabled 8.1.4 security fix only when the version is >= 8.1.4, fall - back to old code otherwise. - - * psycopg/adapter_qstring.c: now quote using PQescapeStringConn if - available. - - * psycopg/adapter_binary.c: now quote using PQescapeByteaConn if - available. - -2006-04-38 Federico Di Gregorio - - * setup.py: fixed little problem with mx_include_dir as suggested - by kvc (this closes #102). - -2006-04-24 Federico Di Gregorio - - * psycopg/adapter_pboolean.c: added the possibility to format boolean - values as "true" and "false" instead of "'t'" and "'f'". - -2006-03-08 Federico Di Gregorio - - * lib/extras.py: added .next() to DictCursot to support iteration. - -2006-03-02 Federico Di Gregorio - - * psycopg/typecast_array.c (typecast_array_tokenize): removed cast - to build without warnings on 64 bit arches. - -2006-02-11 Federico Di Gregorio - - * Release 2.0 beta 8. - - * psycopg/config.h: applied patch from Jason to fix handle leak on - win32, as documented in #92. - -2006-02-11 Federico Di Gregorio - - * Release 2.0 beta 7. - - * psycopg/psycopgmodule.c: applied fix for memory overflow in - connect() (reported by solt, #91.) - - * setup.py: applied patch from lbruno. - -2006-01-11 Federico Di Gregorio - - * setup.py: does not report an error in pg_config unless the pg_config - was explicitly set (allows for building with old options.) - -2006-01-06 Daniele Varrazzo - - * setup.py: libpq.dll not used anymore. win32 setup uses pg_config too. - -2006-01-05 Federico Di Gregorio - - * psycopg/psycopgmodule.c (psyco_set_error): added function to set extra - parameters on ProgrammingError instances. Also modified all occurances of - PyErr_SetString(ProgrammingError,...) to psycopg_set_error(). - - * setup.{cfg,py}: we now use pg_config to locate PostgreSQL libraries - and headers (modified patch from lbruno, see #70.) - -2006-01-01 Federico Di Gregorio - - * Preparing release 2 beta 7. - - * MANIFEST.in: we now distrbute pre-built documentation (still need - to add to setup.py the code necessary to build docs as part of the - build process.) - - * psycopg/connection_int.c: PostgreSQL encoding names are now force - uppercase (after all PostgreSQL documentation reports them this way.) - -2005-12-11 Federico Di Gregorio - - * psycopg/typecast_array.c (typecast_array_cleanup): added functio - to cleanup the "[...]=" part of an array result. This probably will - need some more work for nested arrays but it fixed every test I was - able to write. (Closes: #80) - -2005-12-11 Federico Di Gregorio - - * setup.py: half-applied patch from Tavis to specify mx_include_dir - in setup.cfg. - - * psycopg/typecast.c (typecast_parse_time): cz limit in the while - loop is 6, not 5. This solve the problem with "fractionary" time zones - and fixes #78. - -2005-12-06 Federico Di Gregorio - - * lib/extras.py: added .callproc() to DictCursor as suggested - by Philip Semanchuk. - -2005-11-29 Federico Di Gregorio - - * MANIFEST.in: added docs/async.txt. (Closes: #75) - -2005-11-26 Daniele Varrazzo - - * psycopg/psycopgmodule.c: fixed exceptions refcount. - - * Fixed lots of doctrings and added Epydoc-generated docs support. - -2005-11-24 Federico Di Gregorio - - * sandbox: added all the test and creash-me files to the repository. - - * psycopg/typecast.c (typecast_dealloc): now directly calls - PyObject_Del to avoid to segfault. - -2005-11-20 Federico Di Gregorio - - * psycopg/typecast.c: fixed problem with microseconds conversion by - applying slightly modified patch from Ronnie Mackay. - -2005-11-19 Federico Di Gregorio - - * lib/extensions.py: COMMITED -> COMMITTED. (Closes: #73) - - * doc/extensions.rst: included Daniele's work after minor cosmetic changes - like using the new constants instead of numbers for transaction isolation - levels. - -2005-11-17 Federico Di Gregorio - - * ZPsycopgDA/pool.py: fixed connections leak by using the new name - (PersistentConnectionPool) for the old connection pool class. - -2005-11-16 Federico Di Gregorio - - * Preparing release 2.0 beta 6. - - * psycopg/adapter_mxdatetime.c: fixed all problems with mx conversions. - - * psycopg/typecast.c: now the timezone is set correctly even if there - are no microseconds and/or the offset is 0; - - * examples/encoding.py: fixed example by using python utf8 encoding for - the whole file. - - * lib/__init__.py: very nice hack from Harald Armin Massa to allow - py2exe and similar tools to do their work without problems. - -2005-11-15 Federico Di Gregorio - - * psycopg/psycopgmodule.c: now bails out with correct exception when one - of the needed modules can't be imported (should fix #32.) - -2005-11-14 Federico Di Gregorio - - * psycopg/typecast.c: added typecast_parse_date and typecast_parse_time - functions to do locale-safe date/time parsing. This would probably also - speed-up psycopg a little bit. - -2005-11-07 Federico Di Gregorio - - * psycopg/pqpath.c: fixed problem with uninitialized value (all this was - started by replacing calloc() calls with PyMem_Malloc().) - -2005-11-04 Federico Di Gregorio - - * psycopg/typecast.c: a lot of changes: - - made typecast a new-style type - - removed coerce code and implemented the richcompare protocol that - allows to compare objects of different types - - much better __cmp__ method that allows to compare two typecast - objects and returns True if any two of the mapped oids match - - any object that can be used as an int works as right-hand operand - in __cmp__ operations - - * psycopg/typecast_datetime.c: now typecast_PYINTERVAL_cast limit the - scan to 'len' characters in the string (should fix #65.) - -2005-11-03 Federico Di Gregorio - - * Applied patch from Daniele Varazzo to enable Decimal on Python - 2.3 when the module is available (run-time check, nice.) - -2005-10-26 Federico Di Gregorio - - * setup.cfg: added include_dirs line for SUSE 9.3. - -2005-10-22 Federico Di Gregorio - - * psycopg/cursor_type.c: added support for named cursors: - - .fetchXXX() methods now execute a FETCH if the cursor is named - - .execute() executes a DECLARE if the cursor is named - - .execute() fails if a named cursor is used in autocommit - - .executemany() can't be called on named cursors - - .scroll() executes a MOVE if the cursor is named - - .close() executes a CLOSE if the cursor is named - Also, a "transaction mark" was added to both the connection and the - cursor and an exception is raised when using a named cursor unless the - two marks correspond. - - * psycopg/connection_int.c: snprintf->PyOS_snprintf. - - * psycopg/psycopgmodule.c: snprintf->PyOS_snprintf. - - * psycopg/cursor_type.c: changed self->query type from C string to - PyObject* to better manage queries in named cursors. - - * psycopg/psycopgmodule.c: cleaned up exception names (now the errors - is printed as psycopg2.Error and not as the confusing - psycopg2._psycopg.Error.) - -2005-10-20 Federico Di Gregorio - - * lib/pool.py: renamed ThreadedConnectionPool to PersistentConnectionPool - and added a connection pool that allows multiple connections per thread - as ThreadedConnectionPool (courtesy of Daniele Varrazzo.) - -2005-10-19 Federico Di Gregorio - - * Releasing 2.0 beta 5. - - * psycopg/adapter_mxdatetime.c: reverted to old strftime method to format - mx.DateTime objects; the new method didn't worked in some corner-cases. - This makes impossible to have more than 2 decimal places for seconds but - at least we get the time right every time. - -2005-10-18 Federico Di Gregorio - - * NOTIFY is back end working. - - * psycopg/connection_type.c: fixed problem with initialization of - notifies list (also fixed small memory leak in connection dealloc.) - - * examples/notify.py: added NOTIFY example. - - * psycopg/cursor_type.c: added per-cursor type-casters dictionaries. - -2005-10-18 Federico Di Gregorio - - * psycopg/typecast.c: temporary fix to typecasting objects to return - False for any comparaison except an integer in self.values (i.e., we - don't raise an exception anymore on a coerce error.) Epydoc is now - happy. - - * psycopg/config.h: ZETA config.h patch from Charlie Clark. - - * examples/threads.py: fixed small typo: psycopg -> psycopg2. - - * Big cleanup of unsigned chars to tame gcc 4. - - * Big cleanup of module names (i.e., psycopg2._psycopg everywhere.) - - * psycopg/config.h: added fake localtime_r for platforms missing it - - * psycopg/cursor_type.c: cursors now have a FixedOffsetTimezone - tzinfo_factory by default. - - * psycopg/adapter_datetime.c: added tzinfo argument to psycopg2.Time and - psycopg2.Timestamp. Also now TimestampFromTicks sets the tzinfo object - to psycopg2.tz.LOCAL. - -2005-10-17 Federico Di Gregorio - - * psycopg/adapter_datetime.c: we now use localtime() instead of gmtime() - to accound for the local time-zone in timestamps. - - * psycopg/connection_type.c: fixed docstring for .cursor(). - - * psycopg/psycopgmodule.c: added useful docstring for .connect(). - -2005-10-08 Federico Di Gregorio - - * psycopg/connection_type.c: isolation level upper bound set to 2. - - * lib/psycopg1.py: explicitly set isolation level to 2 on .connect() - to mimic psycopg 1 behaviour. - - * psycopg/connection_int.c: now set isolation level from - default_transaction_isolation backend environment value. - - * psycopg/pqpath.c: removed serialization level 3: now everybody - (except me) has to use the mnemonics defined in psycopg2.extensions. - - * lib/extensions.py: Added mnemonics for serialization levels. - -2005-10-02 Federico Di Gregorio - - * psycopg/cursor_type.c (psyco_curs_callproc): applied callproc - patch from Matt Goodall (added a check on _psyco_curs_execute - return value and substituted malloc/free with PyMem versions.) - -2005-10-01 Federico Di Gregorio - - * psycopg/connection_int.c: fixed segfault by moving PyErr_Format - after GIL acquisition (closes: #50). - - * psycopg/connection_type.c: applied patch from Matt Goodall to - fix some doc strings. - -2005-09-23 Federico Di Gregorio - - * lib/pool.py: applied patch from piro to avoid the scan of the - whole connection array on getconn(). - -2005-09-12 Federico Di Gregorio - - * lib/pool.py: Applied psycopg->psycopg2 patch to from bug #35. - - * ZpsycopgDA/db.py: fixed problem with OperationalError that - resulted in cryptic message to Zope users ("'OperationalError' is - not defined".) - -2005-08-23 Federico Di Gregorio - - * setup.py: applied patch from Daniele Varrazzo to avoid segfaults - when compiling with migw for Python 2.4.x. - - * psycopg/adapter_mxdatetime.c (mxdatetime_str): ported code from 1.1.x - to convert mxDateTime object preserving the precision of fractional - seconds. - -2005-08-22 Federico Di Gregorio - - * ZPsycopgDA/*.py: psycopg -> psycopg2. - - * setup.py: modified to install the module components under - psycopg2 on windows too (thanks to Daniele Varrazzo.) - -2005-08-07 Federico Di Gregorio - - * psycopg/config.h: added __sun__ to the symbols checked for round() - -2005-07-21 Federico Di Gregorio - - * psycopg/adapter_datetime.c (psyco_XXXFromTicks): fixed the 1900 - years offset reported by Jeroen van Dongen (see ticket #33). - -2005-07-17 Federico Di Gregorio - - * Release 2.0 beta 4. - - * lib/extras.py (DictConnection.cursor): added DictConnection to - make easier to retrieve data in DictRows. - -2005-06-24 Federico Di Gregorio - - * psycopg/typecast_datetime.c (typecast_PYINTERVAL_cast): applied patch - from Geert Jansen to fix interval bug due to overflow. - -2005-06-18 Federico Di Gregorio - - * setup.cfg: some clarifications and include_dirs example for Mandrake. - - * ZPsycopgDA/DA.py: DTMLFile -> HTMLFile everywhere to fix zope - cut&paste problems. - - * MANIFEST.in: added missing files to do bdist_rpm. - - * lib/psycopg1.py: fixed .dictfetchrow() to return None if fetchone() - returns None instead of raising an exception. - - * ZPsycopgDA/icons: replaced corrupted icons with good ones. - -2005-06-13 Federico Di Gregorio - - * psycopg/psycopgmodule.c (psyco_connect): changed the port keyword - parameter type to int (instead of string); this should fix #23. - - * psycopg/cursor_type.c (_psyco_curs_execute): now checks for - empty queries and raise a ProgrammingError if appropriate (closes: - #24). - - * setup.py: psycopg module renamed to psycopg2. - -2005-06-02 Federico Di Gregorio - - * psycopg/cursor_type.c (_psyco_curs_execute): fixed segfault when - not passing string or unicode to .execute(). - -2005-06-01 Federico Di Gregorio - - * examples/fetch.py: added example about using DECLARE CURSOR. - - * psycopg/adapter_datetime.c (psyco_TimestampFromTicks): "Hmmm, - looks like someone forgot that C expects months to start counting - from 0, but the Python date routines start counting from 1." That - was me: fixed. - -2005-05-31 Federico Di Gregorio - - * psycopg/cursor_type.c (_psyco_curs_execute): if a - UnicodeEncodeError is raised during the converion of a unicode - query we let it propagate insead of segfaulting. - -2005-5-27 Federico Di Gregorio, - - * tests/types_basic.py: fixed float and binary tests. - -2005-05-26 Federico Di Gregorio - - * Release 2.0b3. - - * ZPsycopgDA/db.py (DB.convert_description): isolated description - conversion (and fixed the conversion as per #18). - - * ZPsycopgDA/DA.py: fixed again; this time Zope should work for - real. :/ Also fixed the type-casters (psycopg 2 added the extra - cursor parameter) as reported in #18. - - * psycopg/psycopgmodule.c (init_psycopg): fixed Python 2.2 build. - -2005-05-19 Federico Di Gregorio - - * Release 2.0b2. - - * lib/extras.py (DictRow): Some extra methods for DictRow. - - * psycopg/cursor_type.c (_psyco_curs_execute): added explict check - to avoid using None as bound variables (very importand for cursor - subclasses calling cursor.execute(self, query, None). - -2005-05-18 Federico Di Gregorio - - * ZPsycopgDA/DA.py (ALLOWED_PSYCOPG_VERSIONS): updated to work - with 2.0b2 only (will support only the exact version untill final - 2.0 release.) - - * setup.py: Applied combined patch from Daniele Varrazzo and Jason - Erickson to build on win32 using MSVC or mingw. - -2005-05-15 Federico Di Gregorio - - * psycopg/microprotocols.c (microprotocols_adapt): fixed memory - leak on None as suggested by gh (closes: #16). - -2005-05-10 Federico Di Gregorio - - * lib/extras.py (DictRow): we now save a reference to the index - itself and not to the cursor to avoid problems while accessing - DictRow objects after reusing the cursor for a different query - (using Kevin Jacobs db_row would be much better but DictRow is - just an example, right?) - -2005-05-09 Federico Di Gregorio - - * Release 2.0 beta 1. - - * psycopg/typecast_datetime.c (typecast_PYDATETIME_cast): fixed a - typo (pyDateTimeModuleP->pyDateTimeTypeP) that was causing errors - with infinite datetime values. - - * psycopg/adapter_binary.c (binary_str): Py_XINCREF on the buffer - that can be NULL on error. - - * psycopg/typecast_binary.*: applied slightly modified - chunk/buffer object patch to allow round-trip of buffer objects - (BYTEA columns.) - - * psycopg/cursor_type.c (psyco_curs_executemany): applied slightly - fixed patch from wrobell to allow iterators in .executemany(). - -2005-04-18 Federico Di Gregorio - - * MANIFEST.in: included debian directory. - -2005-04-10 Federico Di Gregorio - - * psycopg/adapter_list.*: added list adapter. - - * psycopg/microprotocols.c (microprotocol_getquoted): moved - _mogrify_getquoted into utility function in the microprotocols - library. - - * setup.py: Added extensive error message on missing datetime - headers. - - * Applied mingw patch from Daniele Varazzo. - -2005-04-03 Federico Di Gregorio - - * lib/psycopg1.py (connection.autocommit): added compatibility - .autocommit() method. - - * psycopg/psycopgmodule.c (psyco_connect): factory -> - connection_factory. - - * lib/psycopg1.py: added psycopg 1.1.x compatibility module. - -2005-03-29 Federico Di Gregorio - - * Applied patch to fix tuple count. - - * psycopg/pqpath.c (pq_is_busy): Staring from bug report from - Jason Erickson fixed segfaults due to calling Python function - without holding the GIL. - -2005-03-24 Federico Di Gregorio - - * psycopg/adapter_binary.c (binary_escape): propagated Andrea's - fix to binary adapter. - - * psycopg/adapter_qstring.c (qstring_quote): applied patch from - Andrea Arcangeli to fix allocation failures (>4Gb) on 64 bit - arches. - - * psycopg/typecast_array.c (typecast_array_tokenize): much better - tokenization code. - -2005-03-23 Federico Di Gregorio - - * psycopg/typecast_basic.c: all the basic casters now respect the - passed string length. - - * psycopg/typecast.c (typecast_cast): set curs->caster to self - during the type-casting. - - * psycopg/cursor_type.c: added "typecaster" attribute to the - cursor (this is safe, cursors can't be shared among threads and - the attribute is RO.) - -2005-03-22 Federico Di Gregorio - - * psycopg/typecast_array.c: added some more structure to implement - array typecasting. - - * scripts/buildtypes.py: new version to include array data. - -2005-03-15 Federico Di Gregorio - - * lib/extensions.py: Added AsIs import. - -2005-03-12 Federico Di Gregorio - - * psycopg/cursor.h: removed "qattr", not used anymore and added - "cast", holding the typecaster currently in use. - - * Release 1.99.13. - - * psycopg/cursor_type.c (psyco_curs_executemany): implemented as a - wrapper to extract python arguments and then call - _psyco_curs_execute(). - - * psycopg/cursor_type.c (_psyco_curs_execute): splitted away - python argument parsing from the real execute code, to later allow - for .executemany(). - - * psycopg/cursor_type.c (_psyco_curs_buildrow_fill): modified to - call typecast_cast(). - - * psycopg/typecast.c (typecast_call/typecast_cast): modified - typecast_call to use the new typecast_cast that avoids one string - conversion on every cast. - -2005-03-04 Federico Di Gregorio - - * Release 1.99.12.1. - - * psycopg/adapter_asis.c (asis_str): changed call to PyObject_Repr - to PyObject_Str to avoid problems with long integers. - -2005-03-03 Federico Di Gregorio - - * psycopg/typecast.h: added array casting functions. - - * scripts/maketypes.sh: does not generate pgversion.h anymore. - - * Updated all examples for the release. - -2005-03-02 Federico Di Gregorio - - * Release 1.99.12. - - * psycopg/adapter_*.c: added __conform__ to all adapters. - - * psycopg/adapter_qstring.c (qstring_quote): we now use - PyString_AsStringAndSize() instead of strlen() that would stop at - the first embedded \0 (but note that libpq quoting function will - truncate the string anyway!) - - * COPY TO implemented using both old and new (v3) protocol. - - * psycopg/pqpath.c (_pq_copy_out_v3): implemented and working. - - * psycopg/cursor_type.c (psyco_curs_copy_to): added cursor object - interface for copy_to. - - * COPY FROM implemented using both old and new (v3) protocol. - - * psycopg/config.h (Dprintf): declaration for asprintf is gone. - - * psycopg/pqpath.c (_pq_copy_in_v3): implemented. - -2005-03-01 Federico Di Gregorio - - * setup.py: now we generate a slighly more verbose version string - that embeds some of the compile options, to facilitate users' bug - reports. - - * psycopg/cursor_type.c (psyco_curs_copy_from): we now use - PyOS_snprintf instead of asprintf. On some platforms this can be - bad (win32).. if that's your case, get a better platform. :/ - - * psycopg/microprotocols.c (microprotocols_adapt): fixed small - typo that made adaptation using __conform__ impossible. - -2005-02-28 Federico Di Gregorio - - * lib/extras.py: removed AsIs adapter (now a built-in); also - removed prepare() method from the adapters that don't use it to - avoid an extra method call at mogrification time. - - * psycopg/psycopgmodule.c (psyco_adapters_init): added - initialization of the AsIs adapter (adapts int, long, float and - *wonder* None!) - - * psycopg/cursor_type.c (_mogrify_getquoted): reorganized the code - to adapt and then call .getquoted() to obtain the quoted data into - this new function. - -2005-2-27 Federico Di Gregorio - - * examples/myfirstrecipe.py: fixed adapter registration. - -2005-2-7 Federico Di Gregorio - - * setup.py: added patch by Valentino Volonghi to build on MacOS X. - -2005-01-29 Federico Di Gregorio - - * psycopg/pqpath.c (_pq_fetch_tuples): fixed scale-related - segfault (*fourth* mail from Andrea. Another couple like this and - psycopg 2 will exit alpha at warp speed.) - - * psycopg/pqpath.c (pq_fetch): _pq_copy_out_3 -> _pq_copy_out_v3 - (second and third mail from Andrea. :/) - - * psycopg/cursor_type.c (_psyco_curs_has_write_check): added check - on .write() attribute, fixed compilation problems (first mail from - Andrea Arcangeli.) - -2005-01-20 Federico Di Gregorio - - * lib/extensions.py (register_adapter): added register_adapter - function, exported ISQLQuote in psycopg.extensions. - -2005-01-18 Federico Di Gregorio - - * psycopg/pqpath.c (_pq_fetch_tuples): ported scale/precision fix - from psycopg 1.1. - - * LICENSE: detailed licensing information. Re-licensed some parts - under BSD-like to allow integration is pysqlite. - -2005-01-13 Federico Di Gregorio - - * ZPsycopgDA/db.py (DB.query - ): ported ZPsycopgDA connection fix - from psycopg 1.1. - - * lib/*.py: added pydoc-friendly messages. - -2005-01-12 Federico Di Gregorio - - * Added debian directory (thanks to W. Borgert who sent initial - patch based on cdbs.) - -2004-12-20 Federico Di Gregorio - - * psycopg/pqpath.c (pq_execute): removed multiple calls to - pq_fetch in syncronous DBAPI compatibility mode to solve rowcount - problem. - -2004-12-14 Federico Di Gregorio - - * Mm.. release 1.99.11. - - * psycopg/cursor_type.c (_psyco_curs_prefetch): fixed bug in - interaction between the .isready() method and - _psyco_curs_prefetch: isready now store away the pgres but leave - prefetch do its work. - - * psycopg/*.c: changed the names of most of the psycopg's built-in - types to replect their position in the psycopg._psycopg module. - -2004-12-10 Federico Di Gregorio - - * psycopg/cursor_type.c: now *all* write or async accesses to the - connection object are arbitrated using the connection lock. - - * psycopg/cursor_type.c (psyco_curs_isready): now we reset the - current async cursor if it is ready, to allow other cursors to - .execute() without raising the "transaction in progress" error. - - * psycopg/pqpath.c (pq_is_busy): gained status of high-level - function with its own blocking and locking. - - * psycopg/cursor.h (EXC_IF_CURS_CLOSED): also checks the - connection (a closed connection implies a closed cursor.) - - * psycopg/cursor_type.c: cursor's connection is correctly - INCREFfed and DECREFfed. - - * psycopg/connection_type.c: removed the cursors list from the - connection object. It is not necessary anymore for the connection - to know about the cursors and the reference counting will keep the - connection alive (but possibly closed) until all cursors are - garbage collected. - -2004-11-20 Federico Di Gregorio - - * psycopg/cursor_type.c (_mogrify): ported %% fix from 1.1.15. - -2004-11-20 Federico Di Gregorio - - * psycopg/cursor_type.c (psyco_curs_execute): added check to raise an - exception if a cursor tries to .execute() while an async query is - already in execution froma different cursor. - -2004-11-20 Federico Di Gregorio - - * psycopg/connection_type.c (psyco_conn_cursor): renamed 'cursor' - argument to 'cursor_factory'. - -2004-11-19 Federico Di Gregorio - - * psycopg/cursor_type.c (_psyco_curs_buildrow_fill): now standard - tuples are filled using PyTuple_SET_ITEM while extended types - (created via row_factory) are filled using PySequence_SetItem. - - * psycopg/cursor_type.c: changed cursor attribute name from - tuple_factory to row_factory. - -2004-10-14 Federico Di Gregorio - - * psycopg/cursor_type.c (_psyco_curs_buildrow_fill): now we use - PySequence_SetItem to avoid problems with containers created from - cursor's .tuple_factory attribute. - - * lib/extras.py (DictCursor.execute): fixed stupid bug with cursor - setting self.tuplefactory instead of self.tuple_factory. - -2004-10-02 Federico Di Gregorio - - * Release 1.99.10. - - * psycopg/cursor_type.c (_psyco_curs_buildrow_*): unified normal - and factory code into the _psyco_curs_buildrow_fill function; no - more memory leaks here. - - * psycopg/config.h (round): added check for __FreeBSD__ (that - should be defined when compiling with gcc, I hope.) - - * setup.py: removed a lot of code now in setup.cfg. - -2004-09-24 Federico Di Gregorio - - * psycopg/cursor_type.c (cursor_dealloc): fixed small memory leak - due to missing disposal of self->pgres. - -2004-9-14 Federico Di Gregorio - - * examples/dialtone.py: Added adapt() example by Valentino - Volonghi. - -2004-09-14 Federico Di Gregorio - - * psycopg/microprotocols.c (microprotocols_adapt): lots of changes - to the microprotocols layer (it is not micro anymore); - implementing almost all the PEP 246. The adapter registry is now - indexed by (type, protocol) and not by type alone. - -2004-09-13 Federico Di Gregorio - - * psycopg/cursor_type.c (_mogrify): and qattr is gone. - -2004-09-05 Federico Di Gregorio - - * Release 1.99.9 (or, the "twisting by the pool" release). - - * psycopg/pqpath.c (_pq_fetch_tuples): changed to "static void" - instead of "static int", no ways for this function to fail. - -2004-09-04 Federico Di Gregorio - - * psycopg/pqpath.c (_pq_fetch_tuples): ported rowcount fix from - 1.1.15. - - * ZPsycopgDA/*: ZPsycopgDA back in action, using the new pooling - code. - -2004-08-29 Federico Di Gregorio - - * psycopg/typecast_basic.c (typecast_DECIMAL_cast): added DECIMAL - typecaster; it even works :). - - * scripts/buildtypes.py (basic_types): added DECIMAL typecaster - for the NUMERIC oid. - - * examples/threads.py: updated threads example to use pooling code. - - * lib/pool.py: added very simple and thread-safe connection - pooling class. - - * psycopg/cursor_type.c (psyco_curs_fetchmany): fixed problem with - .fetchall() and .fetchmany() returning None instead of [] on empty - result sets. - - * Release 1.99.8. - -2004-08-28 Federico Di Gregorio - - * psycopg/cursor_type.c (psyco_curs_execute): added processing of - unicode queries. - - * examples/encoding.py: much better encoding example, also using - the new UNICODE typecaster. - - * psycopg/typecast_basic.c (typecast_UNICODE_cast): added UNICODE - typecaster. - - * lib/extensions.py: the encodings dictionary is not available by - default but can be accessed from the psycopg.extensions module. - - * psycopg/adapter_qstring.h: remove encoding information from - qstring adapter and moved it into psycopg module. - -2004-08-26 Federico Di Gregorio - - * psycopg/cursor_type.c (_psyco_curs_prefetch): added check for - asynchronous fetch by wrong cursor. - - * psycopg/pqpath.c (pq_fetch): fixed backend status message (bug - reported by Daniele Varrazzo.) - -2004-07-29 Federico Di Gregorio - - * psycopg/typecast_basic.c (typecast_BINARY_cast): reverted to - using strings instead of buffers when converting postgresql binary - objects (should *temporarily* fix corruption bug reported on - win32.) - -2004-07-21 Federico Di Gregorio - - * psycopg/cursor_type.c: removed __iter__ and next methods from - object methods and moved them where they do belong (tp_iter and - tp_iternext.) Bug reported by Daniele Varrazzo (again!) - -2004-07-19 Federico Di Gregorio - - * psycopg/typecast_datetime.c (typecast_PYINTERVAL_cast): replaced - round() with micro() when rounding seconds (fixes bugs reported by - Daniele Varrazzo.) - -2004-07-16 Federico Di Gregorio - - * psycopg/pqpath.c (pq_set_critical): allow for a custom message - insted of the one from PQerrorMessage. - (pq_resolve_critical): added argument to specify if connection is - to be closed (used to not close it during COPY FROM/TO criticals.) - - * psycopg/cursor_type.c (psyco_curs_fileno, psyco_curs_isready): - added extension methods related to async queries. - -2004-07-15 Federico Di Gregorio - - * Release 1.99.7. - - * examples/tz.py: added example about time zones. - - * psycopg/typecast_datetime.c (typecast_PYDATETIME_cast): create - FixedOffsetTimezone for postgresql "timestamp with time zone" - types. - - * lib/tz.py: added (even more than) needed tzinfo classes. - - * psycopg/typecast.c (typecast_call): changed typecast call code - to take the additional cursor parameter, needed for - cursor-dependent type casting (tzinfo & friends.) - - * psycopg/cursor_type.c (_psyco_curs_buildrow_with_factory): added - use of tuple factories to fetcXXX methods. - - * lib/extras.py: little extra goodies for psycopg. - -2004-07-14 Federico Di Gregorio - - * Release 1.99.6. - - * psycopg/connection_type.c: added .dsn attribute to connection - objects. - - * psycopg/cursor_type.c (psyco_curs_mogrify): added .mogrify() - method. - - * psycopg/adapter_qstring.c: copy the connection encoding only if - wrapped object is unicode and added table of encodings. - -2004-07-13 Federico Di Gregorio - - * psycopg/cursor_type.c (_mogrify): moved Dprintf statement to - avoid dereferencing empty pointer (from 1.1.x) - (psyco_curs_execute): now we save the query in self->query instead - of freeing the memory ASAP. - (cursorObject_members): and we finally export the saved query - through the cursor members interface. that's all folks. - - * lib/extensions.py: added extensions module to clearly separate - psycopg own extensions from DBAPI-2.0 - -2004-07-10 Federico Di Gregorio - - * psycopg/typecast_datetime.c: ported interval fix from 1.1.x. - -2004-05-16 Federico Di Gregorio - - * psycopg/typecast_datetime.c (typecast_*_cast): fixed Value error - when seconds > 59 by setting minutes += 1 and seconds -= 60 - (reported by Marcel Gsteiger.) - -2004-04-24 Federico Di Gregorio - - * ported time interval patch by Ross Cohen from 1.1.12. - -2004-04-19 Federico Di Gregorio - - * psycopg/typecast_datetime.c (typecast_PYDATE_cast): applied - patch from Jason Erickson: min and max taken from datetime.Date - type. - -2004-04-18 Federico Di Gregorio - - * Applied changes from Jason Erickson to build on win32; see his - (slightly edited) entry below. (Still builds on Linux :) - - * psycopg/*.c: removed inclusion of pthread.h from all files - except psycopg/config.h to build on win32 without faking the file. - -2004-04-15 Jason Erickson - - * setup.py: Various changes. The critical ones: - - Make an empty pthread.h file so all the code doing an - #include will find something. - - Appended the winsock2 library and the PostgreSQL library to - the library path. - - Setup the include path. - - Have the PSYCOPG_VERSION macro be included with quotes. - - * config.h: Added/Cleaned up Win32 includes, defines, stub functions. - - * typecast.h: Removed ';' after PyObject_HEAD in the - typecastObject structure since Microsoft Visual Studio does not - like it. - -2004-04-15 Federico Di Gregorio - - * Release 1.99.5 (bug-fixing and reorganization) - - * setup.py et al.: moved psycopg to psycopg._psycopg to make - easier to provide high level python-only utilities (like the - promised pooling code). psycopg/__init__.py imports _psycopg and - make all the default DBAPI-2.0 stuff available. - -2004-04-14 Federico Di Gregorio - - * psycopg/psycopgmodule.c (initpsycopg): wrapped initialization of - date/time adapters in #ifdefs to have psycopg compile without mx - or builtin datetime. - -2004-04-10 Federico Di Gregorio - - * Release 1.99.4. - -2004-04-09 Federico Di Gregorio - - * psycopg/typecast_builtins.c: changed DATE to not include - DATETIME types anymore. - - * psycopg/adapter_datetime.c (pydatetime_str): switched from - strftime to isoformat to preserve fractional seconds. - -2004-04-08 Federico Di Gregorio - - * psycopg/psycopgmodule.c (psyco_connect): ported sslmode - parameter from 1.1 branch. - - * psycopg/adapter_datetime.*: added python built-in datetime - adapters. also added the datetime typecasters (still using mx as - default). - - * psycopg/typecast.h: removed aliases, they now live in the right - typecast_xxx.c file. - -2004-03-08 Federico Di Gregorio - - * Release 1.99.3 (alpha 4). - - * examples/lastrowid.py: and the .lastrowid example is in. - - * psycopg/cursor_type.c (_mogrify): added call to .prepare() - method in both dict and sequence path. - - * psycopg/connection_int.c (conn_set_client_encoding): added - encoding-change code. - - * psycopg/adapter_qstring.c (qstring_quote): added hard-coded - support for utf8 and latin1 encodings. - -2004-03-01 Federico Di Gregorio - - * psycopg/connection_int.c (conn_close): does not use libpq - functions on NULL pgconn (this can happen when conn_close is - called after a failed PQconnect.) - -2004-02-29 Federico Di Gregorio - - * Release 1.99.2 (alpha 3). - - * psycopg/cursor_type.c: added .rownumber and .connection - attributes. Also added .scroll(), .next() and .__iter__() methods - (see DBAPI2-.0 extensions on PEP.) - - * psycopg/connection_type.c (psyco_conn_set_isolation_level): - added connection method .set_isolation_level(). Also added all - error objects to the connection (see DBAPI2-.0 extensions on PEP.) - - * psycopg/connection_int.c (conn_switch_isolation_level): added - isolation level switching code. - - * setup.py: removed all references to PSYCOPG_NEWSTYLE: support - for python < 2.2 has been dropped. - - * typecast_basic.c (typecast_BINARY_cast): now binary objects are - returned as true buffers. - - * adapter_binary.*: added adapter for buffers and binary (bytea) - objects. - - * Release 1.99.1 (alpha 2). - - * adapter_mxdatetime.*: added adapters for all mx.DateTime types. - -2004-02-28 Federico Di Gregorio - - * cursor_type.c (_mogrify): complete rework of the mogrification - code to use the microprotocols_adapt function. - - * typecast_basic.c (typecast_BOOLEAN_cast): we now return real - Py_True and Py_False values. - - * microprotocols.h: added very simple microprotocols - implementation to allow for python->postgresql types registry. - -2004-01-05 Federico Di Gregorio - - * connection_int.c (conn_commit/conn_rollback): added code to - commit/rollback and connection methods. - -2004-01-04 Federico Di Gregorio - - * cursor_type.c (psyco_curs_fetchone): added fetchone method. - -2004-01-03 Federico Di Gregorio - - * added (empty) INSTALL file. - - * cursor_type.c (cursor_dealloc): added qattr for custom object - quoting using a callable attribute. - (_mogrify): ported new, fixed mogrification code from 1.1.12. - -2003-08-01 Federico Di Gregorio - - * cursor_type.c (_mogrify_sequence): added sequence mogrification, - can be done better, on the dict model. - -2003-07-28 Federico Di Gregorio - - * typeobj_qstring.c: added quoted strings (can use both own code, - like psycopg 1.x or PQescapeString from lipq.) - -2003-07-21 Federico Di Gregorio - - * connection_type.c (psyco_conn_close): added .close() - method. wow. - - * cursor_*.c: added basic cursor interface (new-style.) - -2003-07-20 Federico Di Gregorio - - * psycopg/*: beginning of new source layout. if you think this - changelog is somewhat empty, you're right. look at - doc/ChangeLog-1.x for psycopg 1.x changelog just before the - branch. - - diff --git a/NEWS b/NEWS index 2dfe49d5..ca013f1b 100644 --- a/NEWS +++ b/NEWS @@ -1,264 +1,302 @@ -What's new in psycopg 2.4.6 ---------------------------- +What's new in psycopg 2.5 +------------------------- - - Fixed 'cursor()' arguments propagation in connection subclasses - and overriding of the 'cursor_factory' argument. Thanks to - Corry Haines for the report and the initial patch (ticket #105). - - Dropped GIL release during string adaptation around a function call - invoking a Python API function, which could cause interpreter crash. - Thanks to Manu Cupcic for the report (ticket #110). - - Close a green connection if there is an error in the callback. - Maybe a harsh solution but it leaves the program responsive - (ticket #113). - - 'register_hstore()', 'register_composite()', 'tpc_recover()' work with - RealDictConnection and Cursor (ticket #114). - - Fixed broken pool for Zope and connections re-init across ZSQL methods - in the same request (tickets #123, #125, #142). - - connect() raises an exception instead of swallowing keyword arguments - when a connection string is specified as well (ticket #131). - - Discard any result produced by 'executemany()' (ticket #133). - - Fixed pickling of FixedOffsetTimezone objects (ticket #135). - - Release the GIL around PQgetResult calls after COPY (ticket #140). - - Fixed empty strings handling in composite caster (ticket #141). - - Fixed pickling of DictRow and RealDictRow objects. +New features: + +- Added :ref:`JSON adaptation `. +- Added :ref:`support for PostgreSQL 9.2 range types `. +- `connection` and `cursor` objects can be used in ``with`` statements + as context managers as specified by recent |DBAPI|_ extension. +- Added `~psycopg2.extensions.Diagnostics` object to get extended info + from a database error. Many thanks to Matthew Woodcraft for the + implementation (:ticket:`#149`). +- Added `connection.cursor_factory` attribute to customize the default + object returned by `~connection.cursor()`. +- Added support for backward scrollable cursors. Thanks to Jon Nelson + for the initial patch (:ticket:`#108`). +- Added a simple way to :ref:`customize casting of composite types + ` into Python objects other than namedtuples. + Many thanks to Ronan Dunklau and Tobias Oberstein for the feature + development. +- `connection.reset()` implemented using :sql:`DISCARD ALL` on server + versions supporting it. + +Bug fixes: + +- Properly cleanup memory of broken connections (:ticket:`#148`). +- Fixed bad interaction of ``setup.py`` with other dependencies in + Distribute projects on Python 3 (:ticket:`#153`). + +Other changes: + +- Added support for Python 3.3. +- Dropped support for Python 2.4. Please use Psycopg 2.4.x if you need it. +- `~psycopg2.errorcodes` map updated to PostgreSQL 9.2. +- Dropped Zope adapter from source repository. ZPsycopgDA now has its own + project at . + + +What's new in psycopg 2.4.6 +^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +- Fixed 'cursor()' arguments propagation in connection subclasses + and overriding of the 'cursor_factory' argument. Thanks to + Corry Haines for the report and the initial patch (:ticket:`#105`). +- Dropped GIL release during string adaptation around a function call + invoking a Python API function, which could cause interpreter crash. + Thanks to Manu Cupcic for the report (:ticket:`#110`). +- Close a green connection if there is an error in the callback. + Maybe a harsh solution but it leaves the program responsive + (:ticket:`#113`). +- 'register_hstore()', 'register_composite()', 'tpc_recover()' work with + RealDictConnection and Cursor (:ticket:`#114`). +- Fixed broken pool for Zope and connections re-init across ZSQL methods + in the same request (tickets #123, #125, #142). +- connect() raises an exception instead of swallowing keyword arguments + when a connection string is specified as well (:ticket:`#131`). +- Discard any result produced by 'executemany()' (:ticket:`#133`). +- Fixed pickling of FixedOffsetTimezone objects (:ticket:`#135`). +- Release the GIL around PQgetResult calls after COPY (:ticket:`#140`). +- Fixed empty strings handling in composite caster (:ticket:`#141`). +- Fixed pickling of DictRow and RealDictRow objects. What's new in psycopg 2.4.5 ---------------------------- +^^^^^^^^^^^^^^^^^^^^^^^^^^^ - - The close() methods on connections and cursors don't raise exceptions - if called on already closed objects. - - Fixed fetchmany() with no argument in cursor subclasses - (ticket #84). - - Use lo_creat() instead of lo_create() when possible for better - interaction with pgpool-II (ticket #88). - - Error and its subclasses are picklable, useful for multiprocessing - interaction (ticket #90). - - Better efficiency and formatting of timezone offset objects thanks - to Menno Smits (tickets #94, #95). - - Fixed 'rownumber' during iteration on cursor subclasses. - Regression introduced in 2.4.4 (ticket #100). - - Added support for 'inet' arrays. - - Fixed 'commit()' concurrency problem (ticket #103). - - Codebase cleaned up using the GCC Python plugin's static analysis - tool, which has revealed several unchecked return values, possible - NULL dereferences, reference counting problems. Many thanks to David - Malcolm for the useful tool and the assistance provided using it. +- The close() methods on connections and cursors don't raise exceptions + if called on already closed objects. +- Fixed fetchmany() with no argument in cursor subclasses + (:ticket:`#84`). +- Use lo_creat() instead of lo_create() when possible for better + interaction with pgpool-II (:ticket:`#88`). +- Error and its subclasses are picklable, useful for multiprocessing + interaction (:ticket:`#90`). +- Better efficiency and formatting of timezone offset objects thanks + to Menno Smits (tickets #94, #95). +- Fixed 'rownumber' during iteration on cursor subclasses. + Regression introduced in 2.4.4 (:ticket:`#100`). +- Added support for 'inet' arrays. +- Fixed 'commit()' concurrency problem (:ticket:`#103`). +- Codebase cleaned up using the GCC Python plugin's static analysis + tool, which has revealed several unchecked return values, possible + NULL dereferences, reference counting problems. Many thanks to David + Malcolm for the useful tool and the assistance provided using it. What's new in psycopg 2.4.4 ---------------------------- +^^^^^^^^^^^^^^^^^^^^^^^^^^^ - - 'register_composite()' also works with the types implicitly defined - after a table row, not only with the ones created by 'CREATE TYPE'. - - Values for the isolation level symbolic constants restored to what - they were before release 2.4.2 to avoid breaking apps using the - values instead of the constants. - - Named DictCursor/RealDictCursor honour itersize (ticket #80). - - Fixed rollback on error on Zope (ticket #73). - - Raise 'DatabaseError' instead of 'Error' with empty libpq errors, - consistently with other disconnection-related errors: regression - introduced in release 2.4.1 (ticket #82). +- 'register_composite()' also works with the types implicitly defined + after a table row, not only with the ones created by 'CREATE TYPE'. +- Values for the isolation level symbolic constants restored to what + they were before release 2.4.2 to avoid breaking apps using the + values instead of the constants. +- Named DictCursor/RealDictCursor honour itersize (:ticket:`#80`). +- Fixed rollback on error on Zope (:ticket:`#73`). +- Raise 'DatabaseError' instead of 'Error' with empty libpq errors, + consistently with other disconnection-related errors: regression + introduced in release 2.4.1 (:ticket:`#82`). What's new in psycopg 2.4.3 ---------------------------- +^^^^^^^^^^^^^^^^^^^^^^^^^^^ - - connect() supports all the keyword arguments supported by the - database - - Added 'new_array_type()' function for easy creation of array - typecasters. - - Added support for arrays of hstores and composite types (ticket #66). - - Fixed segfault in case of transaction started with connection lost - (and possibly other events). - - Fixed adaptation of Decimal type in sub-interpreters, such as in - certain mod_wsgi configurations (ticket #52). - - Rollback connections in transaction or in error before putting them - back into a pool. Also discard broken connections (ticket #62). - - Lazy import of the slow uuid module, thanks to Marko Kreen. - - Fixed NamedTupleCursor.executemany() (ticket #65). - - Fixed --static-libpq setup option (ticket #64). - - Fixed interaction between RealDictCursor and named cursors - (ticket #67). - - Dropped limit on the columns length in COPY operations (ticket #68). - - Fixed reference leak with arguments referenced more than once - in queries (ticket #81). - - Fixed typecasting of arrays containing consecutive backslashes. - - 'errorcodes' map updated to PostgreSQL 9.1. +- connect() supports all the keyword arguments supported by the + database +- Added 'new_array_type()' function for easy creation of array + typecasters. +- Added support for arrays of hstores and composite types (:ticket:`#66`). +- Fixed segfault in case of transaction started with connection lost + (and possibly other events). +- Fixed adaptation of Decimal type in sub-interpreters, such as in + certain mod_wsgi configurations (:ticket:`#52`). +- Rollback connections in transaction or in error before putting them + back into a pool. Also discard broken connections (:ticket:`#62`). +- Lazy import of the slow uuid module, thanks to Marko Kreen. +- Fixed NamedTupleCursor.executemany() (:ticket:`#65`). +- Fixed --static-libpq setup option (:ticket:`#64`). +- Fixed interaction between RealDictCursor and named cursors + (:ticket:`#67`). +- Dropped limit on the columns length in COPY operations (:ticket:`#68`). +- Fixed reference leak with arguments referenced more than once + in queries (:ticket:`#81`). +- Fixed typecasting of arrays containing consecutive backslashes. +- 'errorcodes' map updated to PostgreSQL 9.1. What's new in psycopg 2.4.2 ---------------------------- +^^^^^^^^^^^^^^^^^^^^^^^^^^^ - - Added 'set_session()' method and 'autocommit' property to the - connection. Added support for read-only sessions and, for PostgreSQL - 9.1, for the "repeatable read" isolation level and the "deferrable" - transaction property. - - Psycopg doesn't execute queries at connection time to find the - default isolation level. - - Fixed bug with multithread code potentially causing loss of sync - with the server communication or lock of the client (ticket #55). - - Don't fail import if mx.DateTime module can't be found, even if its - support was built (ticket #53). - - Fixed escape for negative numbers prefixed by minus operator - (ticket #57). - - Fixed refcount issue during copy. Reported and fixed by Dave - Malcolm (ticket #58, Red Hat Bug 711095). - - Trying to execute concurrent operations on the same connection - through concurrent green thread results in an error instead of a - deadlock. - - Fixed detection of pg_config on Window. Report and fix, plus some - long needed setup.py cleanup by Steve Lacy: thanks! +- Added 'set_session()' method and 'autocommit' property to the + connection. Added support for read-only sessions and, for PostgreSQL + 9.1, for the "repeatable read" isolation level and the "deferrable" + transaction property. +- Psycopg doesn't execute queries at connection time to find the + default isolation level. +- Fixed bug with multithread code potentially causing loss of sync + with the server communication or lock of the client (:ticket:`#55`). +- Don't fail import if mx.DateTime module can't be found, even if its + support was built (:ticket:`#53`). +- Fixed escape for negative numbers prefixed by minus operator + (:ticket:`#57`). +- Fixed refcount issue during copy. Reported and fixed by Dave + Malcolm (:ticket:`#58`, Red Hat Bug 711095). +- Trying to execute concurrent operations on the same connection + through concurrent green thread results in an error instead of a + deadlock. +- Fixed detection of pg_config on Window. Report and fix, plus some + long needed setup.py cleanup by Steve Lacy: thanks! What's new in psycopg 2.4.1 ---------------------------- +^^^^^^^^^^^^^^^^^^^^^^^^^^^ - - Use own parser for bytea output, not requiring anymore the libpq 9.0 - to parse the hex format. - - Don't fail connection if the client encoding is a non-normalized - variant. Issue reported by Peter Eisentraut. - - Correctly detect an empty query sent to the backend (ticket #46). - - Fixed a SystemError clobbering libpq errors raised without SQLSTATE. - Bug vivisectioned by Eric Snow. - - Fixed interaction between NamedTuple and server-side cursors. - - Allow to specify --static-libpq on setup.py command line instead of - just in 'setup.cfg'. Patch provided by Matthew Ryan (ticket #48). +- Use own parser for bytea output, not requiring anymore the libpq 9.0 + to parse the hex format. +- Don't fail connection if the client encoding is a non-normalized + variant. Issue reported by Peter Eisentraut. +- Correctly detect an empty query sent to the backend (:ticket:`#46`). +- Fixed a SystemError clobbering libpq errors raised without SQLSTATE. + Bug vivisectioned by Eric Snow. +- Fixed interaction between NamedTuple and server-side cursors. +- Allow to specify --static-libpq on setup.py command line instead of + just in 'setup.cfg'. Patch provided by Matthew Ryan (:ticket:`#48`). What's new in psycopg 2.4 ------------------------- -* New features and changes: +New features and changes: - - Added support for Python 3.1 and 3.2. The conversion has also - brought several improvements: +- Added support for Python 3.1 and 3.2. The conversion has also + brought several improvements: - - Added 'b' and 't' mode to large objects: write can deal with both - bytes strings and unicode; read can return either bytes strings - or decoded unicode. - - COPY sends Unicode data to files implementing 'io.TextIOBase'. - - Improved PostgreSQL-Python encodings mapping. - - Added a few missing encodings: EUC_CN, EUC_JIS_2004, ISO885910, - ISO885916, LATIN10, SHIFT_JIS_2004. - - Dropped repeated dictionary lookups with unicode query/parameters. + - Added 'b' and 't' mode to large objects: write can deal with both + bytes strings and unicode; read can return either bytes strings + or decoded unicode. + - COPY sends Unicode data to files implementing 'io.TextIOBase'. + - Improved PostgreSQL-Python encodings mapping. + - Added a few missing encodings: EUC_CN, EUC_JIS_2004, ISO885910, + ISO885916, LATIN10, SHIFT_JIS_2004. + - Dropped repeated dictionary lookups with unicode query/parameters. - - Improvements to the named cusors: +- Improvements to the named cusors: - - More efficient iteration on named cursors, fetching 'itersize' - records at time from the backend. - - The named cursors name can be an invalid identifier. + - More efficient iteration on named cursors, fetching 'itersize' + records at time from the backend. + - The named cursors name can be an invalid identifier. - - Improvements in data handling: +- Improvements in data handling: - - Added 'register_composite()' function to cast PostgreSQL - composite types into Python tuples/namedtuples. - - Adapt types 'bytearray' (from Python 2.6), 'memoryview' (from - Python 2.7) and other objects implementing the "Revised Buffer - Protocol" to 'bytea' data type. - - The 'hstore' adapter can work even when the data type is not - installed in the 'public' namespace. - - Raise a clean exception instead of returning bad data when - receiving bytea in 'hex' format and the client libpq can't parse - them. - - Empty lists correctly roundtrip Python -> PostgreSQL -> Python. + - Added 'register_composite()' function to cast PostgreSQL + composite types into Python tuples/namedtuples. + - Adapt types 'bytearray' (from Python 2.6), 'memoryview' (from + Python 2.7) and other objects implementing the "Revised Buffer + Protocol" to 'bytea' data type. + - The 'hstore' adapter can work even when the data type is not + installed in the 'public' namespace. + - Raise a clean exception instead of returning bad data when + receiving bytea in 'hex' format and the client libpq can't parse + them. + - Empty lists correctly roundtrip Python -> PostgreSQL -> Python. - - Other changes: +- Other changes: - - 'cursor.description' is provided as named tuples if available. - - The build script refuses to guess values if 'pg_config' is not - found. - - Connections and cursors are weakly referenceable. + - 'cursor.description' is provided as named tuples if available. + - The build script refuses to guess values if 'pg_config' is not + found. + - Connections and cursors are weakly referenceable. -* Bug fixes: +Bug fixes: - - Fixed adaptation of None in composite types (ticket #26). Bug - report by Karsten Hilbert. - - Fixed several reference leaks in less common code paths. - - Fixed segfault when a large object is closed and its connection no - more available. - - Added missing icon to ZPsycopgDA package, not available in Zope - 2.12.9 (ticket #30). Bug report and patch by Pumukel. - - Fixed conversion of negative infinity (ticket #40). Bug report and - patch by Marti Raudsepp. +- Fixed adaptation of None in composite types (:ticket:`#26`). Bug + report by Karsten Hilbert. +- Fixed several reference leaks in less common code paths. +- Fixed segfault when a large object is closed and its connection no + more available. +- Added missing icon to ZPsycopgDA package, not available in Zope + 2.12.9 (:ticket:`#30`). Bug report and patch by Pumukel. +- Fixed conversion of negative infinity (:ticket:`#40`). Bug report and + patch by Marti Raudsepp. What's new in psycopg 2.3.2 ---------------------------- +^^^^^^^^^^^^^^^^^^^^^^^^^^^ - - Fixed segfault with middleware not passing DateStyle to the client - (ticket #24). Bug report and patch by Marti Raudsepp. +- Fixed segfault with middleware not passing DateStyle to the client + (:ticket:`#24`). Bug report and patch by Marti Raudsepp. What's new in psycopg 2.3.1 ---------------------------- +^^^^^^^^^^^^^^^^^^^^^^^^^^^ - - Fixed build problem on CentOS 5.5 x86_64 (ticket #23). +- Fixed build problem on CentOS 5.5 x86_64 (:ticket:`#23`). -What's new in psycopg 2.3.0 ---------------------------- +What's new in psycopg 2.3 +------------------------- psycopg 2.3 aims to expose some new features introduced in PostgreSQL 9.0. -* Main new features: +Main new features: - - `dict` to `hstore` adapter and `hstore` to `dict` typecaster, using both - 9.0 and pre-9.0 syntax. - - Two-phase commit protocol support as per DBAPI specification. - - Support for payload in notifications received from the backed. - - `namedtuple`-returning cursor. - - Query execution cancel. +- `dict` to `hstore` adapter and `hstore` to `dict` typecaster, using both + 9.0 and pre-9.0 syntax. +- Two-phase commit protocol support as per DBAPI specification. +- Support for payload in notifications received from the backed. +- `namedtuple`-returning cursor. +- Query execution cancel. -* Other features and changes: +Other features and changes: - - Dropped support for protocol 2: Psycopg 2.3 can only connect to PostgreSQL - servers with version at least 7.4. - - Don't issue a query at every connection to detect the client encoding - and to set the datestyle to ISO if it is already compatible with what - expected. - - `mogrify()` now supports unicode queries. - - Subclasses of a type that can be adapted are adapted as the superclass. - - `errorcodes` knows a couple of new codes introduced in PostgreSQL 9.0. - - Dropped deprecated Psycopg "own quoting". - - Never issue a ROLLBACK on close/GC. This behaviour was introduced as a bug - in release 2.2, but trying to send a command while being destroyed has been - considered not safe. +- Dropped support for protocol 2: Psycopg 2.3 can only connect to PostgreSQL + servers with version at least 7.4. +- Don't issue a query at every connection to detect the client encoding + and to set the datestyle to ISO if it is already compatible with what + expected. +- `mogrify()` now supports unicode queries. +- Subclasses of a type that can be adapted are adapted as the superclass. +- `errorcodes` knows a couple of new codes introduced in PostgreSQL 9.0. +- Dropped deprecated Psycopg "own quoting". +- Never issue a ROLLBACK on close/GC. This behaviour was introduced as a bug + in release 2.2, but trying to send a command while being destroyed has been + considered not safe. -* Bug fixes: +Bug fixes: - - Fixed use of `PQfreemem` instead of `free` in binary typecaster. - - Fixed access to freed memory in `conn_get_isolation_level()`. - - Fixed crash during Decimal adaptation with a few 2.5.x Python versions - (ticket #7). - - Fixed notices order (ticket #9). +- Fixed use of `PQfreemem` instead of `free` in binary typecaster. +- Fixed access to freed memory in `conn_get_isolation_level()`. +- Fixed crash during Decimal adaptation with a few 2.5.x Python versions + (:ticket:`#7`). +- Fixed notices order (:ticket:`#9`). What's new in psycopg 2.2.2 ---------------------------- +^^^^^^^^^^^^^^^^^^^^^^^^^^^ -* Bux fixes: +Bux fixes: - - the call to logging.basicConfig() in pool.py has been dropped: it was - messing with some projects using logging (and a library should not - initialize the logging system anyway.) - - psycopg now correctly handles time zones with seconds in the UTC offset. - The old register_tstz_w_secs() function is deprecated and will raise a - warning if called. - - Exceptions raised by the column iterator are propagated. - - Exceptions raised by executemany() interators are propagated. +- the call to logging.basicConfig() in pool.py has been dropped: it was + messing with some projects using logging (and a library should not + initialize the logging system anyway.) +- psycopg now correctly handles time zones with seconds in the UTC offset. + The old register_tstz_w_secs() function is deprecated and will raise a + warning if called. +- Exceptions raised by the column iterator are propagated. +- Exceptions raised by executemany() interators are propagated. What's new in psycopg 2.2.1 ---------------------------- +^^^^^^^^^^^^^^^^^^^^^^^^^^^ -* Bux fixes: +Bux fixes: - - psycopg now builds again on MS Windows. +- psycopg now builds again on MS Windows. -What's new in psycopg 2.2.0 ---------------------------- +What's new in psycopg 2.2 +------------------------- This is the first release of the new 2.2 series, supporting not just one but two different ways of executing asynchronous queries, thanks to Jan and Daniele @@ -269,262 +307,288 @@ psycopg now supports both classic select() loops and "green" coroutine libraries. It is all in the documentation, so just point your browser to doc/html/advanced.html. -* Other new features: +Other new features: - - truncate() method for lobjects. - - COPY functions are now a little bit faster. - - All builtin PostgreSQL to Python typecasters are now available from the - psycopg2.extensions module. - - Notifications from the backend are now available right after the execute() - call (before client code needed to call isbusy() to ensure NOTIFY - reception.) - - Better timezone support. - - Lots of documentation updates. +- truncate() method for lobjects. +- COPY functions are now a little bit faster. +- All builtin PostgreSQL to Python typecasters are now available from the + psycopg2.extensions module. +- Notifications from the backend are now available right after the execute() + call (before client code needed to call isbusy() to ensure NOTIFY + reception.) +- Better timezone support. +- Lots of documentation updates. -* Bug fixes: +Bug fixes: - - Fixed some gc/refcounting problems. - - Fixed reference leak in NOTIFY reception. - - Fixed problem with PostgreSQL not casting string literals to the correct - types in some situations: psycopg now add an explicit cast to dates, times - and bytea representations. - - Fixed TimestampFromTicks() and TimeFromTicks() for seconds >= 59.5. - - Fixed spurious exception raised when calling C typecasters from Python - ones. +- Fixed some gc/refcounting problems. +- Fixed reference leak in NOTIFY reception. +- Fixed problem with PostgreSQL not casting string literals to the correct + types in some situations: psycopg now add an explicit cast to dates, times + and bytea representations. +- Fixed TimestampFromTicks() and TimeFromTicks() for seconds >= 59.5. +- Fixed spurious exception raised when calling C typecasters from Python + ones. What's new in psycopg 2.0.14 ----------------------------- +^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -* New features: - - Support for adapting tuples to PostgreSQL arrays is now enabled by - default and does not require importing psycopg2.extensions anymore. - - "can't adapt" error message now includes full type information. - - Thank to Daniele Varrazzo (piro) psycopg2's source package now includes - full documentation in HTML and plain text format. +New features: -* Bug fixes: - - No loss of precision when using floats anymore. - - decimal.Decimal "nan" and "infinity" correctly converted to PostgreSQL - numeric NaN values (note that PostgreSQL numeric type does not support - infinity but just NaNs.) - - psycopg2.extensions now includes Binary. +- Support for adapting tuples to PostgreSQL arrays is now enabled by + default and does not require importing psycopg2.extensions anymore. +- "can't adapt" error message now includes full type information. +- Thank to Daniele Varrazzo (piro) psycopg2's source package now includes + full documentation in HTML and plain text format. -* It seems we're good citizens of the free software ecosystem and that big - big big companies and people ranting on the pgsql-hackers mailing list - we'll now not dislike us. *g* (See LICENSE file for the details.) +Bug fixes: + +- No loss of precision when using floats anymore. +- decimal.Decimal "nan" and "infinity" correctly converted to PostgreSQL + numeric NaN values (note that PostgreSQL numeric type does not support + infinity but just NaNs.) +- psycopg2.extensions now includes Binary. + +It seems we're good citizens of the free software ecosystem and that big +big big companies and people ranting on the pgsql-hackers mailing list +we'll now not dislike us. *g* (See LICENSE file for the details.) What's new in psycopg 2.0.13 ----------------------------- +^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -* New features: - - Support for UUID arrays. - - It is now possible to build psycopg linking to a static libpq - library. +New features: -* Bug fixes: - - Fixed a deadlock related to using the same connection with - multiple cursors from different threads. - - Builds again with MSVC. +- Support for UUID arrays. +- It is now possible to build psycopg linking to a static libpq + library. + +Bug fixes: + +- Fixed a deadlock related to using the same connection with + multiple cursors from different threads. +- Builds again with MSVC. What's new in psycopg 2.0.12 ----------------------------- +^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -* New features: - - The connection object now has a reset() method that can be used to - reset the connection to its default state. +New features: -* Bug fixes: - - copy_to() and copy_from() now accept a much larger number of columns. - - Fixed PostgreSQL version detection. - - Fixed ZPsycopgDA version check. - - Fixed regression in ZPsycopgDA that made it behave wrongly when - receiving serialization errors: now the query is re-issued as it - should be by propagating the correct exception to Zope. - - Writing "large" large objects should now work. +- The connection object now has a reset() method that can be used to + reset the connection to its default state. + +Bug fixes: + +- copy_to() and copy_from() now accept a much larger number of columns. +- Fixed PostgreSQL version detection. +- Fixed ZPsycopgDA version check. +- Fixed regression in ZPsycopgDA that made it behave wrongly when + receiving serialization errors: now the query is re-issued as it + should be by propagating the correct exception to Zope. +- Writing "large" large objects should now work. What's new in psycopg 2.0.11 ----------------------------- +^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -* New features: - - DictRow and RealDictRow now use less memory. If you inherit on them - remember to set __slots__ for your new attributes or be prepare to - go back to old memory usage. +New features: -* Bug fixes: - - Fixed exeception in setup.py. - - More robust detection of PostgreSQL development versions. - - Fixed exception in RealDictCursor, introduced in 2.0.10. +- DictRow and RealDictRow now use less memory. If you inherit on them + remember to set __slots__ for your new attributes or be prepare to + go back to old memory usage. + +Bug fixes: + +- Fixed exeception in setup.py. +- More robust detection of PostgreSQL development versions. +- Fixed exception in RealDictCursor, introduced in 2.0.10. What's new in psycopg 2.0.10 ----------------------------- +^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -* New features: - - A specialized type-caster that can parse time zones with seconds is - now available. Note that after enabling it (see extras.py) "wrong" - time zones will be parsed without raising an exception but the - result will be rounded. - - DictCursor can be used as a named cursor. - - DictRow now implements more dict methods. - - The connection object now expose PostgreSQL server version as the - .server_version attribute and the protocol version used as - .protocol_version. - - The connection object has a .get_parameter_status() methods that - can be used to obtain useful information from the server. +New features: -* Bug fixes: - - None is now correctly always adapted to NULL. - - Two double memory free errors provoked by multithreading and - garbage collection are now fixed. - - Fixed usage of internal Python code in the notice processor; this - should fix segfaults when receiving a lot of notices in - multithreaded programs. - - Should build again on MSVC and Solaris. - - Should build with development versions of PostgreSQL (ones with - -devel version string.) - - Fixed some tests that failed even when psycopg was right. +- A specialized type-caster that can parse time zones with seconds is + now available. Note that after enabling it (see extras.py) "wrong" + time zones will be parsed without raising an exception but the + result will be rounded. +- DictCursor can be used as a named cursor. +- DictRow now implements more dict methods. +- The connection object now expose PostgreSQL server version as the + .server_version attribute and the protocol version used as + .protocol_version. +- The connection object has a .get_parameter_status() methods that + can be used to obtain useful information from the server. + +Bug fixes: + +- None is now correctly always adapted to NULL. +- Two double memory free errors provoked by multithreading and + garbage collection are now fixed. +- Fixed usage of internal Python code in the notice processor; this + should fix segfaults when receiving a lot of notices in + multithreaded programs. +- Should build again on MSVC and Solaris. +- Should build with development versions of PostgreSQL (ones with + -devel version string.) +- Fixed some tests that failed even when psycopg was right. What's new in psycopg 2.0.9 ---------------------------- +^^^^^^^^^^^^^^^^^^^^^^^^^^^ -* New features: - - "import psycopg2.extras" to get some support for handling times - and timestamps with seconds in the time zone offset. - - DictCursors can now be used as named cursors. - -* Bug fixes: - - register_type() now accept an explicit None as its second parameter. - - psycopg2 should build again on MSVC and Solaris. +New features: + +- "import psycopg2.extras" to get some support for handling times + and timestamps with seconds in the time zone offset. +- DictCursors can now be used as named cursors. + +Bug fixes: + +- register_type() now accept an explicit None as its second parameter. +- psycopg2 should build again on MSVC and Solaris. What's new in psycopg 2.0.9 ---------------------------- +^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +New features: + +- COPY TO/COPY FROM queries now can be of any size and psycopg will + correctly quote separators. +- float values Inf and NaN are now correctly handled and can + round-trip to the database. +- executemany() now return the numer of total INSERTed or UPDATEd + rows. Note that, as it has always been, executemany() should not + be used to execute multiple SELECT statements and while it will + execute the statements without any problem, it will return the + wrong value. +- copy_from() and copy_to() can now use quoted separators. +- "import psycopg2.extras" to get UUID support. + +Bug fixes: + +- register_type() now works on connection and cursor subclasses. +- fixed a memory leak when using lobjects. -* New features: - - COPY TO/COPY FROM queries now can be of any size and psycopg will - correctly quote separators. - - float values Inf and NaN are now correctly handled and can - round-trip to the database. - - executemany() now return the numer of total INSERTed or UPDATEd - rows. Note that, as it has always been, executemany() should not - be used to execute multiple SELECT statements and while it will - execute the statements without any problem, it will return the - wrong value. - - copy_from() and copy_to() can now use quoted separators. - - "import psycopg2.extras" to get UUID support. - -* Bug fixes: - - register_type() now works on connection and cursor subclasses. - - fixed a memory leak when using lobjects. - What's new in psycopg 2.0.8 ---------------------------- +^^^^^^^^^^^^^^^^^^^^^^^^^^^ -* New features: - - The connection object now has a get_backend_pid() method that - returns the current PostgreSQL connection backend process PID. - - The PostgreSQL large object API has been exposed through the - Cursor.lobject() method. +New features: + +- The connection object now has a get_backend_pid() method that + returns the current PostgreSQL connection backend process PID. +- The PostgreSQL large object API has been exposed through the + Cursor.lobject() method. + +Bug fixes: + +- Some fixes to ZPsycopgDA have been merged from the Debian package. +- A memory leak was fixed in Cursor.executemany(). +- A double free was fixed in pq_complete_error(), that caused crashes + under some error conditions. -* Bug fixes: - - Some fixes to ZPsycopgDA have been merged from the Debian package. - - A memory leak was fixed in Cursor.executemany(). - - A double free was fixed in pq_complete_error(), that caused crashes - under some error conditions. What's new in psycopg 2.0.7 ---------------------------- +^^^^^^^^^^^^^^^^^^^^^^^^^^^ -* Improved error handling: - - All instances of psycopg2.Error subclasses now have pgerror, - pgcode and cursor attributes. They will be set to None if no - value is available. - - Exception classes are now chosen based on the SQLSTATE value from - the result. (#184) - - The commit() and rollback() methods now set the pgerror and pgcode - attributes on exceptions. (#152) - - errors from commit() and rollback() are no longer considered - fatal. (#194) - - If a disconnect is detected during execute(), an exception will be - raised at that point rather than resulting in "ProgrammingError: - no results to fetch" later on. (#186) +Improved error handling: -* Better PostgreSQL compatibility: - - If the server uses standard_conforming_strings, perform - appropriate quoting. - - BC dates are now handled if psycopg is compiled with mxDateTime - support. If using datetime, an appropriate ValueError is - raised. (#203) +- All instances of psycopg2.Error subclasses now have pgerror, + pgcode and cursor attributes. They will be set to None if no + value is available. +- Exception classes are now chosen based on the SQLSTATE value from + the result. (#184) +- The commit() and rollback() methods now set the pgerror and pgcode + attributes on exceptions. (#152) +- errors from commit() and rollback() are no longer considered + fatal. (#194) +- If a disconnect is detected during execute(), an exception will be + raised at that point rather than resulting in "ProgrammingError: + no results to fetch" later on. (#186) -* Other bug fixes: - - If multiple sub-interpreters are in use, do not share the Decimal - type between them. (#192) - - Buffer objects obtained from psycopg are now accepted by psycopg - too, without segfaulting. (#209) - - A few small changes were made to improve DB-API compatibility. - All the dbapi20 tests now pass. +Better PostgreSQL compatibility: + +- If the server uses standard_conforming_strings, perform + appropriate quoting. +- BC dates are now handled if psycopg is compiled with mxDateTime + support. If using datetime, an appropriate ValueError is + raised. (#203) + +Other bug fixes: + +- If multiple sub-interpreters are in use, do not share the Decimal + type between them. (#192) +- Buffer objects obtained from psycopg are now accepted by psycopg + too, without segfaulting. (#209) +- A few small changes were made to improve DB-API compatibility. + All the dbapi20 tests now pass. + +Miscellaneous: + +- The PSYCOPG_DISPLAY_SIZE option is now off by default. This means + that display size will always be set to "None" in + cursor.description. Calculating the display size was expensive, + and infrequently used so this should improve performance. +- New QueryCanceledError and TransactionRollbackError exceptions + have been added to the psycopg2.extensions module. They can be + used to detect statement timeouts and deadlocks respectively. +- Cursor objects now have a "closed" attribute. (#164) +- If psycopg has been built with debug support, it is now necessary + to set the PSYCOPG_DEBUG environment variable to turn on debug + spew. -* Miscellaneous: - - The PSYCOPG_DISPLAY_SIZE option is now off by default. This means - that display size will always be set to "None" in - cursor.description. Calculating the display size was expensive, - and infrequently used so this should improve performance. - - New QueryCanceledError and TransactionRollbackError exceptions - have been added to the psycopg2.extensions module. They can be - used to detect statement timeouts and deadlocks respectively. - - Cursor objects now have a "closed" attribute. (#164) - - If psycopg has been built with debug support, it is now necessary - to set the PSYCOPG_DEBUG environment variable to turn on debug - spew. What's new in psycopg 2.0.6 ---------------------------- +^^^^^^^^^^^^^^^^^^^^^^^^^^^ -* Better support for PostgreSQL, Python and win32: - - full support for PostgreSQL 8.2, including NULLs in arrays - - support for almost all existing PostgreSQL encodings - - full list of PostgreSQL error codes available by importing the - psycopg2.errorcodes module - - full support for Python 2.5 and 64 bit architectures - - better build support on win32 platform +Better support for PostgreSQL, Python and win32: -* Support for per-connection type-casters (used by ZPsycopgDA too, this - fixes a long standing bug that made different connections use a random - set of date/time type-casters instead of the configured one.) +- full support for PostgreSQL 8.2, including NULLs in arrays +- support for almost all existing PostgreSQL encodings +- full list of PostgreSQL error codes available by importing the + psycopg2.errorcodes module +- full support for Python 2.5 and 64 bit architectures +- better build support on win32 platform -* Better management of times and dates both from Python and in Zope. +Support for per-connection type-casters (used by ZPsycopgDA too, this +fixes a long standing bug that made different connections use a random +set of date/time type-casters instead of the configured one.) -* copy_to and copy_from now take an extra "columns" parameter. +Better management of times and dates both from Python and in Zope. -* Python tuples are now adapted to SQL sequences that can be used with - the "IN" operator by default if the psycopg2.extensions module is - imported (i.e., the SQL_IN adapter was moved from extras to extensions.) +copy_to and copy_from now take an extra "columns" parameter. + +Python tuples are now adapted to SQL sequences that can be used with +the "IN" operator by default if the psycopg2.extensions module is +imported (i.e., the SQL_IN adapter was moved from extras to extensions.) + +Fixed some small buglets and build glitches: + +- removed double mutex destroy +- removed all non-constant initializers +- fixed PyObject_HEAD declarations to avoid memory corruption + on 64 bit architectures +- fixed several Python API calls to work on 64 bit architectures +- applied compatibility macros from PEP 353 +- now using more than one argument format raise an error instead of + a segfault -* Fixed some small buglets and build glitches: - - removed double mutex destroy - - removed all non-constant initializers - - fixed PyObject_HEAD declarations to avoid memory corruption - on 64 bit architectures - - fixed several Python API calls to work on 64 bit architectures - - applied compatibility macros from PEP 353 - - now using more than one argument format raise an error instead of - a segfault What's new in psycopg 2.0.5.1 -­---------------------------- +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ * Now it really, really builds on MSVC and older gcc versions. What's new in psycopg 2.0.5 -­-------------------------- +^^^^^^^^^^^^^^^^^^^^^^^^^^^ * Fixed various buglets such as: + - segfault when passing an empty string to Binary() - segfault on null queries - segfault and bad keyword naming in .executemany() @@ -535,17 +599,17 @@ What's new in psycopg 2.0.5 * connect() now accept both integers and strings as port parameter What's new in psycopg 2.0.4 ---------------------------- +^^^^^^^^^^^^^^^^^^^^^^^^^^^ * Fixed float conversion bug introduced in 2.0.3. What's new in psycopg 2.0.3 ---------------------------- +^^^^^^^^^^^^^^^^^^^^^^^^^^^ * Fixed various buglets and a memory leak (see ChangeLog for details) What's new in psycopg 2.0.2 ---------------------------- +^^^^^^^^^^^^^^^^^^^^^^^^^^^ * Fixed a bug in array typecasting that sometimes made psycopg forget about the last element in the array. @@ -556,7 +620,7 @@ What's new in psycopg 2.0.2 version is issued only if __GCC__ is defined.) What's new in psycopg 2.0.1 ---------------------------- +^^^^^^^^^^^^^^^^^^^^^^^^^^^ * ZPsycopgDA now actually loads. @@ -572,7 +636,7 @@ What's new in psycopg 2.0 so that you all stop grumbling about psycopg 2 is still in beta.. :) What's new in psycopg 2.0 beta 7 --------------------------------- +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ * Ironed out last problems with times and date (should be quite solid now.) @@ -580,18 +644,18 @@ What's new in psycopg 2.0 beta 7 * Slightly better ZPsycopgDA (no more double connection objects in the menu and other minor fixes.) - + * ProgrammingError exceptions now have three extra attributes: .cursor (it is possible to access the query that caused the exception using error.cursor.query), .pgerror and .pgcode (PostgreSQL original error text and code.) - + * The build system uses pg_config when available. - + * Documentation in the doc/ directory! (With many kudos to piro.) What's new in psycopg 2.0 beta 6 --------------------------------- +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ * Support for named cursors (see examples/fetch.py). @@ -604,13 +668,13 @@ What's new in psycopg 2.0 beta 6 * The "decimal" module is now used if available under Python 2.3. What's new in psycopg 2.0 beta 5 --------------------------------- +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ * Fixed all known bugs. * The initial isolation level is now read from the server and .set_isolation_level() now takes values defined in psycopg2.extensions. - + * .callproc() implemented as a SELECT of the given procedure. * Better docstrings for a few functions/methods. @@ -619,20 +683,20 @@ What's new in psycopg 2.0 beta 5 local timezone into account. Also a tzinfo object (as per datetime module specifications) can be passed to the psycopg2.Time and psycopg2.Datetime constructors. - + * All classes have been renamed to exist in the psycopg2._psycopg module, to fix problems with automatic documentation generators like epydoc. - + * NOTIFY is correctly trapped (see examples/notify.py for example code.) What's new in psycopg 2.0 beta 4 --------------------------------- +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ * psycopg module is now named psycopg2. * No more segfaults when a UNICODE query can't be converted to the backend encoding. - + * No more segfaults on empty queries. * psycopg2.connect() now takes an integer for the port keyword parameter. @@ -642,14 +706,14 @@ What's new in psycopg 2.0 beta 4 * Fixed lots of small bugs, see ChangeLog for details. What's new in psycopg 2.0 beta 3 --------------------------------- +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ * ZPsycopgDA now works (except table browsing.) * psycopg build again on Python 2.2. What's new in psycopg 2.0 beta 2 --------------------------------- +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ * Fixed ZPsycopgDA version check (ZPsycopgDA can now be imported in Zope.) @@ -662,7 +726,7 @@ What's new in psycopg 2.0 beta 2 * Generic fixed and memory leaks plugs. What's new in psycopg 2.0 beta 1 --------------------------------- +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ * Officially in beta (i.e., no new features will be added.) @@ -689,9 +753,9 @@ What's new in psycopg 2.0 beta 1 * Internal changes that allow much better user-defined type casters. * A lot of bugfixes (binary, datetime, 64 bit arches, GIL, .executemany()) - + What's new in psycopg 1.99.13 ------------------------------ +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ * Added missing .executemany() method. @@ -699,7 +763,7 @@ What's new in psycopg 1.99.13 faster than before.) What's new in psycopg 1.99.12 ------------------------------ +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ * .rowcount should be ok and in sync with psycopg 1. @@ -712,9 +776,9 @@ What's new in psycopg 1.99.12 * getquoted() called for real by the mogrification code. What's new in psycopg 1.99.11 ------------------------------ +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -* 'cursor' argument in .cursor() connection method renamed to +* 'cursor' argument in .cursor() connection method renamed to 'cursor_factory'. * changed 'tuple_factory' cursor attribute name to 'row_factory'. @@ -725,7 +789,7 @@ What's new in psycopg 1.99.11 * fixes to the async core. What's new in psycopg 1.99.10 ------------------------------ +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ * The adapt() function now fully supports the adaptation protocol described in PEP 246. Note that the adapters registry now is indexed @@ -738,7 +802,7 @@ What's new in psycopg 1.99.10 fetching (.fetchXXX() methods.) What's new in psycopg 1.99.9 ----------------------------- +^^^^^^^^^^^^^^^^^^^^^^^^^^^^ * Added simple pooling code (psycopg.pool module); see the reworked examples/threads.py for example code. @@ -753,69 +817,54 @@ What's new in psycopg 1.99.9 * Isn't that enough? :) What's new in psycopg 1.99.8 ----------------------------- +^^^^^^^^^^^^^^^^^^^^^^^^^^^^ * added support for UNICODE queries. +* added UNICODE typecaster; to activate it just do:: + + psycopg.extensions.register_type(psycopg.extensions.UNICODE) -* added UNICODE typecaster; to activate it just do: - - psycopg.extensions.register_type(psycopg.extensions.UNICODE) - Note that the UNICODE typecaster override the STRING one, so it is not activated by default. * cursors now really support the iterator protocol. - * solved the rounding errors in time conversions. - * now cursors support .fileno() and .isready() methods, to be used in select() calls. - * .copy_from() and .copy_in() methods are back in (still using the old protocol, will be updated to use new one in next releasae.) - * fixed memory corruption bug reported on win32 platform. What's new in psycopg 1.99.7 ----------------------------- +^^^^^^^^^^^^^^^^^^^^^^^^^^^^ * added support for tuple factories in cursor objects (removed factory argument in favor of a .tuple_factory attribute on the cursor object); see the new module psycopg.extras for a cursor (DictCursor) that return rows as objects that support indexing both by position and column name. - * added support for tzinfo objects in datetime.timestamp objects: the - PostgreSQL type "timestamp with time zone" is converted to + PostgreSQL type "timestamp with time zone" is converted to datetime.timestamp with a FixedOffsetTimezone initialized as necessary. What's new in psycopg 1.99.6 ----------------------------- +^^^^^^^^^^^^^^^^^^^^^^^^^^^^ * sslmode parameter from 1.1.x - * various datetime conversion improvements. - * now psycopg should compile without mx or without native datetime (not both, obviously.) - * included various win32/MSVC fixes (pthread.h changes, winsock2 library, include path in setup.py, etc.) - * ported interval fixes from 1.1.14/1.1.15. - * the last query executed by a cursor is now available in the .query attribute. - * conversion of unicode strings to backend encoding now uses a table (that still need to be filled.) - * cursors now have a .mogrify() method that return the query string instead of executing it. - * connection objects now have a .dsn read-only attribute that holds the connection string. - * moved psycopg C module to _psycopg and made psycopg a python module: this allows for a neat separation of DBAPI-2.0 functionality and psycopg extensions; the psycopg namespace will be also used to provide @@ -823,36 +872,33 @@ What's new in psycopg 1.99.6 functions and the like.) What's new in psycopg 1.99.3 ----------------------------- +^^^^^^^^^^^^^^^^^^^^^^^^^^^^ * added support for python 2.3 datetime types (both ways) and made datetime the default set of typecasters when available. - * added example: dt.py. What's new in psycopg 1.99.3 ----------------------------- +^^^^^^^^^^^^^^^^^^^^^^^^^^^^ * initial working support for unicode bound variables: UTF-8 and latin-1 backend encodings are natively supported (and the encoding.py example even works!) - * added .set_client_encoding() method on the connection object. - * added examples: encoding.py, binary.py, lastrowid.py. What's new in psycopg 1.99.2 ----------------------------- +^^^^^^^^^^^^^^^^^^^^^^^^^^^^ * better typecasting: + - DateTimeDelta used for postgresql TIME (merge from 1.1) - BYTEA now is converted to a real buffer object, not to a string * buffer objects are now adapted into Binary objects automatically. - * ported scroll method from 1.1 (DBAPI-2.0 extension for cursors) - * initial support for some DBAPI-2.0 extensions: + - .rownumber attribute for cursors - .connection attribute for cursors - .next() and .__iter__() methods to have cursors support the iterator @@ -860,7 +906,7 @@ What's new in psycopg 1.99.2 - all exception objects are exported to the connection object What's new in psycopg 1.99.1 ----------------------------- +^^^^^^^^^^^^^^^^^^^^^^^^^^^^ * implemented microprotocols to adapt arbitrary types to the interface used by psycopg to bind variables in execute; @@ -870,7 +916,7 @@ What's new in psycopg 1.99.1 What's new in psycopg 1.99.0 ----------------------------- +^^^^^^^^^^^^^^^^^^^^^^^^^^^^ * reorganized the whole source tree; diff --git a/ZPsycopgDA/DA.py b/ZPsycopgDA/DA.py deleted file mode 100644 index f5489ceb..00000000 --- a/ZPsycopgDA/DA.py +++ /dev/null @@ -1,360 +0,0 @@ -# ZPsycopgDA/DA.py - ZPsycopgDA Zope product: Database Connection -# -# Copyright (C) 2004-2010 Federico Di Gregorio -# -# psycopg2 is free software: you can redistribute it and/or modify it -# under the terms of the GNU Lesser General Public License as published -# by the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# psycopg2 is distributed in the hope that it will be useful, but WITHOUT -# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or -# FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public -# License for more details. - -# Import modules needed by _psycopg to allow tools like py2exe to do -# their work without bothering about the module dependencies. - - -ALLOWED_PSYCOPG_VERSIONS = ('2.4', '2.4.1', '2.4.4', '2.4.5', '2.4.6') - -import sys -import time -import db -import re - -import Acquisition -import Shared.DC.ZRDB.Connection - -from db import DB -from Globals import HTMLFile -from ExtensionClass import Base -from App.Dialogs import MessageDialog -from DateTime import DateTime - -# ImageFile is deprecated in Zope >= 2.9 -try: - from App.ImageFile import ImageFile -except ImportError: - # Zope < 2.9. If PIL's installed with a .pth file, we're probably - # hosed. - from ImageFile import ImageFile - -# import psycopg and functions/singletons needed for date/time conversions - -import psycopg2 -from psycopg2 import NUMBER, STRING, ROWID, DATETIME -from psycopg2.extensions import INTEGER, LONGINTEGER, FLOAT, BOOLEAN, DATE -from psycopg2.extensions import TIME, INTERVAL -from psycopg2.extensions import new_type, register_type - - -# add a new connection to a folder - -manage_addZPsycopgConnectionForm = HTMLFile('dtml/add',globals()) - -def manage_addZPsycopgConnection(self, id, title, connection_string, - zdatetime=None, tilevel=2, - encoding='', check=None, REQUEST=None): - """Add a DB connection to a folder.""" - self._setObject(id, Connection(id, title, connection_string, - zdatetime, check, tilevel, encoding)) - if REQUEST is not None: return self.manage_main(self, REQUEST) - - -# the connection object - -class Connection(Shared.DC.ZRDB.Connection.Connection): - """ZPsycopg Connection.""" - _isAnSQLConnection = 1 - - id = 'Psycopg2_database_connection' - database_type = 'Psycopg2' - meta_type = title = 'Z Psycopg 2 Database Connection' - icon = 'misc_/conn' - - def __init__(self, id, title, connection_string, - zdatetime, check=None, tilevel=2, encoding='UTF-8'): - self.zdatetime = zdatetime - self.id = str(id) - self.edit(title, connection_string, zdatetime, - check=check, tilevel=tilevel, encoding=encoding) - - def factory(self): - return DB - - ## connection parameters editing ## - - def edit(self, title, connection_string, - zdatetime, check=None, tilevel=2, encoding='UTF-8'): - self.title = title - self.connection_string = connection_string - self.zdatetime = zdatetime - self.tilevel = tilevel - self.encoding = encoding - - if check: self.connect(self.connection_string) - - manage_properties = HTMLFile('dtml/edit', globals()) - - def manage_edit(self, title, connection_string, - zdatetime=None, check=None, tilevel=2, encoding='UTF-8', - REQUEST=None): - """Edit the DB connection.""" - self.edit(title, connection_string, zdatetime, - check=check, tilevel=tilevel, encoding=encoding) - if REQUEST is not None: - msg = "Connection edited." - return self.manage_main(self,REQUEST,manage_tabs_message=msg) - - def connect(self, s): - try: - self._v_database_connection.close() - except: - pass - - # check psycopg version and raise exception if does not match - if psycopg2.__version__.split(' ')[0] not in ALLOWED_PSYCOPG_VERSIONS: - raise ImportError("psycopg version mismatch (imported %s)" % - psycopg2.__version__) - - self._v_connected = '' - dbf = self.factory() - - # TODO: let the psycopg exception propagate, or not? - self._v_database_connection = dbf( - self.connection_string, self.tilevel, self.get_type_casts(), self.encoding) - self._v_database_connection.open() - self._v_connected = DateTime() - - return self - - def get_type_casts(self): - # note that in both cases order *is* important - if self.zdatetime: - return ZDATETIME, ZDATE, ZTIME - else: - return DATETIME, DATE, TIME - - ## browsing and table/column management ## - - manage_options = Shared.DC.ZRDB.Connection.Connection.manage_options - # + ( - # {'label': 'Browse', 'action':'manage_browse'},) - - #manage_tables = HTMLFile('dtml/tables', globals()) - #manage_browse = HTMLFile('dtml/browse', globals()) - - info = None - - def table_info(self): - return self._v_database_connection.table_info() - - - def __getitem__(self, name): - if name == 'tableNamed': - if not hasattr(self, '_v_tables'): self.tpValues() - return self._v_tables.__of__(self) - raise KeyError, name - - def tpValues(self): - res = [] - conn = self._v_database_connection - for d in conn.tables(rdb=0): - try: - name = d['TABLE_NAME'] - b = TableBrowser() - b.__name__ = name - b._d = d - b._c = c - try: - b.icon = table_icons[d['TABLE_TYPE']] - except: - pass - r.append(b) - except: - pass - return res - - -## database connection registration data ## - -classes = (Connection,) - -meta_types = ({'name':'Z Psycopg 2 Database Connection', - 'action':'manage_addZPsycopgConnectionForm'},) - -folder_methods = { - 'manage_addZPsycopgConnection': manage_addZPsycopgConnection, - 'manage_addZPsycopgConnectionForm': manage_addZPsycopgConnectionForm} - -__ac_permissions__ = ( - ('Add Z Psycopg Database Connections', - ('manage_addZPsycopgConnectionForm', 'manage_addZPsycopgConnection')),) - -# add icons - -misc_={'conn': ImageFile('icons/DBAdapterFolder_icon.gif', globals())} - -for icon in ('table', 'view', 'stable', 'what', 'field', 'text', 'bin', - 'int', 'float', 'date', 'time', 'datetime'): - misc_[icon] = ImageFile('icons/%s.gif' % icon, globals()) - - -## zope-specific psycopg typecasters ## - -# convert an ISO timestamp string from postgres to a Zope DateTime object -def _cast_DateTime(iso, curs): - if iso: - if iso in ['-infinity', 'infinity']: - return iso - else: - return DateTime(iso) - -# convert an ISO date string from postgres to a Zope DateTime object -def _cast_Date(iso, curs): - if iso: - if iso in ['-infinity', 'infinity']: - return iso - else: - return DateTime(iso) - -# Convert a time string from postgres to a Zope DateTime object. -# NOTE: we set the day as today before feeding to DateTime so -# that it has the same DST settings. -def _cast_Time(iso, curs): - if iso: - if iso in ['-infinity', 'infinity']: - return iso - else: - return DateTime(time.strftime('%Y-%m-%d %H:%M:%S', - time.localtime(time.time())[:3]+ - time.strptime(iso[:8], "%H:%M:%S")[3:])) - -# NOTE: we don't cast intervals anymore because they are passed -# untouched to Zope. -def _cast_Interval(iso, curs): - return iso - -ZDATETIME = new_type((1184, 1114), "ZDATETIME", _cast_DateTime) -ZINTERVAL = new_type((1186,), "ZINTERVAL", _cast_Interval) -ZDATE = new_type((1082,), "ZDATE", _cast_Date) -ZTIME = new_type((1083,), "ZTIME", _cast_Time) - - -## table browsing helpers ## - -class TableBrowserCollection(Acquisition.Implicit): - pass - -class Browser(Base): - def __getattr__(self, name): - try: - return self._d[name] - except KeyError: - raise AttributeError, name - -class values: - def len(self): - return 1 - - def __getitem__(self, i): - try: - return self._d[i] - except AttributeError: - pass - self._d = self._f() - return self._d[i] - -class TableBrowser(Browser, Acquisition.Implicit): - icon = 'what' - Description = check = '' - info = HTMLFile('table_info', globals()) - menu = HTMLFile('table_menu', globals()) - - def tpValues(self): - v = values() - v._f = self.tpValues_ - return v - - def tpValues_(self): - r=[] - tname=self.__name__ - for d in self._c.columns(tname): - b=ColumnBrowser() - b._d=d - try: b.icon=field_icons[d['Type']] - except: pass - b.TABLE_NAME=tname - r.append(b) - return r - - def tpId(self): return self._d['TABLE_NAME'] - def tpURL(self): return "Table/%s" % self._d['TABLE_NAME'] - def Name(self): return self._d['TABLE_NAME'] - def Type(self): return self._d['TABLE_TYPE'] - - manage_designInput=HTMLFile('designInput',globals()) - def manage_buildInput(self, id, source, default, REQUEST=None): - "Create a database method for an input form" - args=[] - values=[] - names=[] - columns=self._columns - for i in range(len(source)): - s=source[i] - if s=='Null': continue - c=columns[i] - d=default[i] - t=c['Type'] - n=c['Name'] - names.append(n) - if s=='Argument': - values.append("'" % - (n, vartype(t))) - a='%s%s' % (n, boboType(t)) - if d: a="%s=%s" % (a,d) - args.append(a) - elif s=='Property': - values.append("'" % - (n, vartype(t))) - else: - if isStringType(t): - if find(d,"\'") >= 0: d=join(split(d,"\'"),"''") - values.append("'%s'" % d) - elif d: - values.append(str(d)) - else: - raise ValueError, ( - 'no default was given for %s' % n) - -class ColumnBrowser(Browser): - icon='field' - - def check(self): - return ('\t' % - (self.TABLE_NAME, self._d['Name'])) - def tpId(self): return self._d['Name'] - def tpURL(self): return "Column/%s" % self._d['Name'] - def Description(self): - d=self._d - if d['Scale']: - return " %(Type)s(%(Precision)s,%(Scale)s) %(Nullable)s" % d - else: - return " %(Type)s(%(Precision)s) %(Nullable)s" % d - -table_icons={ - 'TABLE': 'table', - 'VIEW':'view', - 'SYSTEM_TABLE': 'stable', - } - -field_icons={ - NUMBER.name: 'i', - STRING.name: 'text', - DATETIME.name: 'date', - INTEGER.name: 'int', - FLOAT.name: 'float', - BOOLEAN.name: 'bin', - ROWID.name: 'int' - } diff --git a/ZPsycopgDA/__init__.py b/ZPsycopgDA/__init__.py deleted file mode 100644 index 118c4fe7..00000000 --- a/ZPsycopgDA/__init__.py +++ /dev/null @@ -1,29 +0,0 @@ -# ZPsycopgDA/__init__.py - ZPsycopgDA Zope product -# -# Copyright (C) 2004-2010 Federico Di Gregorio -# -# psycopg2 is free software: you can redistribute it and/or modify it -# under the terms of the GNU Lesser General Public License as published -# by the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# psycopg2 is distributed in the hope that it will be useful, but WITHOUT -# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or -# FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public -# License for more details. - -# Import modules needed by _psycopg to allow tools like py2exe to do -# their work without bothering about the module dependencies. - -__doc__ = "ZPsycopg Database Adapter Registration." -__version__ = '2.0' - -import DA - -def initialize(context): - context.registerClass( - DA.Connection, - permission = 'Add Z Psycopg 2 Database Connections', - constructors = (DA.manage_addZPsycopgConnectionForm, - DA.manage_addZPsycopgConnection), - icon = 'icons/DBAdapterFolder_icon.gif') diff --git a/ZPsycopgDA/db.py b/ZPsycopgDA/db.py deleted file mode 100644 index b594b3fd..00000000 --- a/ZPsycopgDA/db.py +++ /dev/null @@ -1,209 +0,0 @@ -# ZPsycopgDA/db.py - query execution -# -# Copyright (C) 2004-2010 Federico Di Gregorio -# -# psycopg2 is free software: you can redistribute it and/or modify it -# under the terms of the GNU Lesser General Public License as published -# by the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# psycopg2 is distributed in the hope that it will be useful, but WITHOUT -# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or -# FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public -# License for more details. - -# Import modules needed by _psycopg to allow tools like py2exe to do -# their work without bothering about the module dependencies. - -from Shared.DC.ZRDB.TM import TM -from Shared.DC.ZRDB import dbi_db - -from ZODB.POSException import ConflictError - -import site -import pool - -import psycopg2 -from psycopg2.extensions import INTEGER, LONGINTEGER, FLOAT, BOOLEAN, DATE, TIME -from psycopg2.extensions import TransactionRollbackError, register_type -from psycopg2 import NUMBER, STRING, ROWID, DATETIME - - -# the DB object, managing all the real query work - -class DB(TM, dbi_db.DB): - - _p_oid = _p_changed = _registered = None - - def __init__(self, dsn, tilevel, typecasts, enc='utf-8'): - self.dsn = dsn - self.tilevel = tilevel - self.typecasts = typecasts - if enc is None or enc == "": - self.encoding = "utf-8" - else: - self.encoding = enc - self.failures = 0 - self.calls = 0 - self.make_mappings() - - def getconn(self, init=True): - # if init is False we are trying to get hold on an already existing - # connection, so we avoid to (re)initialize it risking errors. - conn = pool.getconn(self.dsn) - if init: - # use set_session where available as in these versions - # set_isolation_level generates an extra query. - if psycopg2.__version__ >= '2.4.2': - conn.set_session(isolation_level=int(self.tilevel)) - else: - conn.set_isolation_level(int(self.tilevel)) - conn.set_client_encoding(self.encoding) - for tc in self.typecasts: - register_type(tc, conn) - return conn - - def putconn(self, close=False): - try: - conn = pool.getconn(self.dsn, False) - except AttributeError: - pass - pool.putconn(self.dsn, conn, close) - - def getcursor(self): - conn = self.getconn(False) - return conn.cursor() - - def _finish(self, *ignored): - try: - conn = self.getconn(False) - conn.commit() - self.putconn() - except AttributeError: - pass - - def _abort(self, *ignored): - try: - conn = self.getconn(False) - conn.rollback() - self.putconn() - except AttributeError: - pass - - def open(self): - # this will create a new pool for our DSN if not already existing, - # then get and immediately release a connection - self.getconn() - self.putconn() - - def close(self): - # FIXME: if this connection is closed we flush all the pool associated - # with the current DSN; does this makes sense? - pool.flushpool(self.dsn) - - def sortKey(self): - return 1 - - def make_mappings(self): - """Generate the mappings used later by self.convert_description().""" - self.type_mappings = {} - for t, s in [(INTEGER,'i'), (LONGINTEGER, 'i'), (NUMBER, 'n'), - (BOOLEAN,'n'), (ROWID, 'i'), - (DATETIME, 'd'), (DATE, 'd'), (TIME, 'd')]: - for v in t.values: - self.type_mappings[v] = (t, s) - - def convert_description(self, desc, use_psycopg_types=False): - """Convert DBAPI-2.0 description field to Zope format.""" - items = [] - for name, typ, width, ds, p, scale, null_ok in desc: - m = self.type_mappings.get(typ, (STRING, 's')) - items.append({ - 'name': name, - 'type': use_psycopg_types and m[0] or m[1], - 'width': width, - 'precision': p, - 'scale': scale, - 'null': null_ok, - }) - return items - - ## tables and rows ## - - def tables(self, rdb=0, _care=('TABLE', 'VIEW')): - self._register() - c = self.getcursor() - c.execute( - "SELECT t.tablename AS NAME, 'TABLE' AS TYPE " - " FROM pg_tables t WHERE tableowner <> 'postgres' " - "UNION SELECT v.viewname AS NAME, 'VIEW' AS TYPE " - " FROM pg_views v WHERE viewowner <> 'postgres' " - "UNION SELECT t.tablename AS NAME, 'SYSTEM_TABLE\' AS TYPE " - " FROM pg_tables t WHERE tableowner = 'postgres' " - "UNION SELECT v.viewname AS NAME, 'SYSTEM_TABLE' AS TYPE " - "FROM pg_views v WHERE viewowner = 'postgres'") - res = [] - for name, typ in c.fetchall(): - if typ in _care: - res.append({'TABLE_NAME': name, 'TABLE_TYPE': typ}) - self.putconn() - return res - - def columns(self, table_name): - self._register() - c = self.getcursor() - try: - r = c.execute('SELECT * FROM "%s" WHERE 1=0' % table_name) - except: - return () - self.putconn() - return self.convert_description(c.description, True) - - ## query execution ## - - def query(self, query_string, max_rows=None, query_data=None): - self._register() - self.calls = self.calls+1 - - desc = () - res = [] - nselects = 0 - - c = self.getcursor() - - try: - for qs in [x for x in query_string.split('\0') if x]: - try: - if query_data: - c.execute(qs, query_data) - else: - c.execute(qs) - except TransactionRollbackError: - # Ha, here we have to look like we are the ZODB raising conflict errrors, raising ZPublisher.Publish.Retry just doesn't work - #logging.debug("Serialization Error, retrying transaction", exc_info=True) - raise ConflictError("TransactionRollbackError from psycopg2") - except psycopg2.OperationalError: - #logging.exception("Operational error on connection, closing it.") - try: - # Only close our connection - self.putconn(True) - except: - #logging.debug("Something went wrong when we tried to close the pool", exc_info=True) - pass - if c.description is not None: - nselects += 1 - if c.description != desc and nselects > 1: - raise psycopg2.ProgrammingError( - 'multiple selects in single query not allowed') - if max_rows: - res = c.fetchmany(max_rows) - else: - res = c.fetchall() - desc = c.description - self.failures = 0 - - except StandardError, err: - self._abort() - raise err - - return self.convert_description(desc), res diff --git a/ZPsycopgDA/dtml/add.dtml b/ZPsycopgDA/dtml/add.dtml deleted file mode 100644 index 330a001b..00000000 --- a/ZPsycopgDA/dtml/add.dtml +++ /dev/null @@ -1,108 +0,0 @@ - - - - -

-A Zope Psycopg 2 Database Connection is used to connect and execute -queries on a PostgreSQL database. -

- -

-In the form below Connection String (also called the Data Source Name -or DSN for short) is a string... (TODO: finish docs) -

- -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
-
- Id -
-
- -
-
- Title -
-
- -
-
- Connection string -
-
- -
-
- Connect immediately -
-
- -
-
- Use Zope's internal DateTime -
-
- -
-
- Transaction isolation level -
-
- -
-
- Encoding -
-
- -
-
- -
-
-
- - diff --git a/ZPsycopgDA/dtml/browse.dtml b/ZPsycopgDA/dtml/browse.dtml deleted file mode 100644 index deffd0ab..00000000 --- a/ZPsycopgDA/dtml/browse.dtml +++ /dev/null @@ -1,11 +0,0 @@ - - <dtml-var title_or_id >tables - - - - <dtml-var Type> - - - - diff --git a/ZPsycopgDA/dtml/edit.dtml b/ZPsycopgDA/dtml/edit.dtml deleted file mode 100644 index cffb43bf..00000000 --- a/ZPsycopgDA/dtml/edit.dtml +++ /dev/null @@ -1,84 +0,0 @@ - - - -
- - - - - - - - - - - - - - - - - - - - - - - - -
-
- Title -
-
- -
-
- Connection string -
-
- -
-
- Use Zope's internal DateTime -
-
- checked="YES" /> -
-
- Transaction isolation level -
-
- -
-
- Encoding -
-
- -
-
- -
-
-
- - diff --git a/ZPsycopgDA/dtml/table_info.dtml b/ZPsycopgDA/dtml/table_info.dtml deleted file mode 100644 index 639c23fd..00000000 --- a/ZPsycopgDA/dtml/table_info.dtml +++ /dev/null @@ -1,7 +0,0 @@ - - - - owned by -
- - diff --git a/ZPsycopgDA/icons/DBAdapterFolder_icon.gif b/ZPsycopgDA/icons/DBAdapterFolder_icon.gif deleted file mode 100755 index ced0ef26..00000000 Binary files a/ZPsycopgDA/icons/DBAdapterFolder_icon.gif and /dev/null differ diff --git a/ZPsycopgDA/icons/bin.gif b/ZPsycopgDA/icons/bin.gif deleted file mode 100644 index e4691265..00000000 Binary files a/ZPsycopgDA/icons/bin.gif and /dev/null differ diff --git a/ZPsycopgDA/icons/date.gif b/ZPsycopgDA/icons/date.gif deleted file mode 100644 index 0d88a573..00000000 Binary files a/ZPsycopgDA/icons/date.gif and /dev/null differ diff --git a/ZPsycopgDA/icons/datetime.gif b/ZPsycopgDA/icons/datetime.gif deleted file mode 100644 index faa540b1..00000000 Binary files a/ZPsycopgDA/icons/datetime.gif and /dev/null differ diff --git a/ZPsycopgDA/icons/field.gif b/ZPsycopgDA/icons/field.gif deleted file mode 100644 index 9bf8692b..00000000 Binary files a/ZPsycopgDA/icons/field.gif and /dev/null differ diff --git a/ZPsycopgDA/icons/float.gif b/ZPsycopgDA/icons/float.gif deleted file mode 100644 index dd427299..00000000 Binary files a/ZPsycopgDA/icons/float.gif and /dev/null differ diff --git a/ZPsycopgDA/icons/int.gif b/ZPsycopgDA/icons/int.gif deleted file mode 100644 index ef2c5e36..00000000 Binary files a/ZPsycopgDA/icons/int.gif and /dev/null differ diff --git a/ZPsycopgDA/icons/stable.gif b/ZPsycopgDA/icons/stable.gif deleted file mode 100644 index acdd37df..00000000 Binary files a/ZPsycopgDA/icons/stable.gif and /dev/null differ diff --git a/ZPsycopgDA/icons/table.gif b/ZPsycopgDA/icons/table.gif deleted file mode 100644 index cce83bea..00000000 Binary files a/ZPsycopgDA/icons/table.gif and /dev/null differ diff --git a/ZPsycopgDA/icons/text.gif b/ZPsycopgDA/icons/text.gif deleted file mode 100644 index a2e5aab6..00000000 Binary files a/ZPsycopgDA/icons/text.gif and /dev/null differ diff --git a/ZPsycopgDA/icons/time.gif b/ZPsycopgDA/icons/time.gif deleted file mode 100644 index 6d089150..00000000 Binary files a/ZPsycopgDA/icons/time.gif and /dev/null differ diff --git a/ZPsycopgDA/icons/view.gif b/ZPsycopgDA/icons/view.gif deleted file mode 100644 index 71b30de1..00000000 Binary files a/ZPsycopgDA/icons/view.gif and /dev/null differ diff --git a/ZPsycopgDA/icons/what.gif b/ZPsycopgDA/icons/what.gif deleted file mode 100644 index 8b5516e3..00000000 Binary files a/ZPsycopgDA/icons/what.gif and /dev/null differ diff --git a/ZPsycopgDA/pool.py b/ZPsycopgDA/pool.py deleted file mode 100644 index b47f46cc..00000000 --- a/ZPsycopgDA/pool.py +++ /dev/null @@ -1,193 +0,0 @@ -# ZPsycopgDA/pool.py - ZPsycopgDA Zope product: connection pooling -# -# Copyright (C) 2004-2010 Federico Di Gregorio -# -# psycopg2 is free software: you can redistribute it and/or modify it -# under the terms of the GNU Lesser General Public License as published -# by the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# psycopg2 is distributed in the hope that it will be useful, but WITHOUT -# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or -# FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public -# License for more details. - -# Import modules needed by _psycopg to allow tools like py2exe to do -# their work without bothering about the module dependencies. - -# All the connections are held in a pool of pools, directly accessible by the -# ZPsycopgDA code in db.py. - -import threading -import psycopg2 -from psycopg2.pool import PoolError - - -class AbstractConnectionPool(object): - """Generic key-based pooling code.""" - - def __init__(self, minconn, maxconn, *args, **kwargs): - """Initialize the connection pool. - - New 'minconn' connections are created immediately calling 'connfunc' - with given parameters. The connection pool will support a maximum of - about 'maxconn' connections. - """ - self.minconn = minconn - self.maxconn = maxconn - self.closed = False - - self._args = args - self._kwargs = kwargs - - self._pool = [] - self._used = {} - self._rused = {} # id(conn) -> key map - self._keys = 0 - - for i in range(self.minconn): - self._connect() - - def _connect(self, key=None): - """Create a new connection and assign it to 'key' if not None.""" - conn = psycopg2.connect(*self._args, **self._kwargs) - if key is not None: - self._used[key] = conn - self._rused[id(conn)] = key - else: - self._pool.append(conn) - return conn - - def _getkey(self): - """Return a new unique key.""" - self._keys += 1 - return self._keys - - def _getconn(self, key=None): - """Get a free connection and assign it to 'key' if not None.""" - if self.closed: raise PoolError("connection pool is closed") - if key is None: key = self._getkey() - - if key in self._used: - return self._used[key] - - if self._pool: - self._used[key] = conn = self._pool.pop() - self._rused[id(conn)] = key - return conn - else: - if len(self._used) == self.maxconn: - raise PoolError("connection pool exausted") - return self._connect(key) - - def _putconn(self, conn, key=None, close=False): - """Put away a connection.""" - if self.closed: raise PoolError("connection pool is closed") - if key is None: key = self._rused[id(conn)] - - if not key: - raise PoolError("trying to put unkeyed connection") - - if len(self._pool) < self.minconn and not close: - self._pool.append(conn) - else: - conn.close() - - # here we check for the presence of key because it can happen that a - # thread tries to put back a connection after a call to close - if not self.closed or key in self._used: - del self._used[key] - del self._rused[id(conn)] - - def _closeall(self): - """Close all connections. - - Note that this can lead to some code fail badly when trying to use - an already closed connection. If you call .closeall() make sure - your code can deal with it. - """ - if self.closed: raise PoolError("connection pool is closed") - for conn in self._pool + list(self._used.values()): - try: - conn.close() - except: - pass - self.closed = True - - -class PersistentConnectionPool(AbstractConnectionPool): - """A pool that assigns persistent connections to different threads. - - Note that this connection pool generates by itself the required keys - using the current thread id. This means that until a thread puts away - a connection it will always get the same connection object by successive - `!getconn()` calls. This also means that a thread can't use more than one - single connection from the pool. - """ - - def __init__(self, minconn, maxconn, *args, **kwargs): - """Initialize the threading lock.""" - import threading - AbstractConnectionPool.__init__( - self, minconn, maxconn, *args, **kwargs) - self._lock = threading.Lock() - - # we we'll need the thread module, to determine thread ids, so we - # import it here and copy it in an instance variable - import thread - self.__thread = thread - - def getconn(self): - """Generate thread id and return a connection.""" - key = self.__thread.get_ident() - self._lock.acquire() - try: - return self._getconn(key) - finally: - self._lock.release() - - def putconn(self, conn=None, close=False): - """Put away an unused connection.""" - key = self.__thread.get_ident() - self._lock.acquire() - try: - if not conn: conn = self._used[key] - self._putconn(conn, key, close) - finally: - self._lock.release() - - def closeall(self): - """Close all connections (even the one currently in use.)""" - self._lock.acquire() - try: - self._closeall() - finally: - self._lock.release() - - -_connections_pool = {} -_connections_lock = threading.Lock() - -def getpool(dsn, create=True): - _connections_lock.acquire() - try: - if not _connections_pool.has_key(dsn) and create: - _connections_pool[dsn] = \ - PersistentConnectionPool(4, 200, dsn) - finally: - _connections_lock.release() - return _connections_pool[dsn] - -def flushpool(dsn): - _connections_lock.acquire() - try: - _connections_pool[dsn].closeall() - del _connections_pool[dsn] - finally: - _connections_lock.release() - -def getconn(dsn, create=True): - return getpool(dsn, create=create).getconn() - -def putconn(dsn, conn, close=False): - getpool(dsn).putconn(conn, close=close) diff --git a/debian/README.zpsycopgda2 b/debian/README.zpsycopgda2 deleted file mode 100644 index 53028f76..00000000 --- a/debian/README.zpsycopgda2 +++ /dev/null @@ -1,7 +0,0 @@ -ZPsycopgDA (in the Debian zope-psycopgda package) is a Zope Database -Adapter based on the psycopg Python/PostgreSQL driver. You'll find -more information and documentation in the pythonX.Y-psycopg package, -where X.Y is the version of your installed Python. - -Details for ZPsycopgDA for Zope are found in the documentation of -the python2.3-psycopg package. diff --git a/debian/changelog b/debian/changelog deleted file mode 100644 index c7abebf3..00000000 --- a/debian/changelog +++ /dev/null @@ -1,676 +0,0 @@ -psycopg2 (2.0.7-2) unstable; urgency=medium - - * ZPsycopgDA/DA.py: updated the patch. (Closes: #478860) - - -- Fabio Tranchitella Thu, 01 May 2008 17:43:54 +0200 - -psycopg2 (2.0.7-1) unstable; urgency=low - - * New upstream release. (Closes: #476101) - * debian/control: bumped Standard-Versions to 3.7.3. - - -- Fabio Tranchitella Tue, 15 Apr 2008 10:05:51 +0200 - -psycopg2 (2.0.6-4) unstable; urgency=low - - [ Sandro Tosi ] - * debian/control - - uniforming Vcs-Browser field - - [ Fabio Tranchitella ] - * Provides a encoding parameter when adding a ZPsycopgDA instance using the - ZMI. (Closes: #475123) - - -- Fabio Tranchitella Wed, 09 Apr 2008 19:51:10 +0200 - -psycopg2 (2.0.6-3) unstable; urgency=low - - [ Piotr Ożarowski ] - * XS-Vcs-Svn field renamed to Vcs-Svn - * Vcs-Browser field added - - [ Fabio Tranchitella ] - * Mention DB-API 2.0 compatibility in the long description. - (Closes: #430763) - - -- Fabio Tranchitella Thu, 08 Nov 2007 15:07:05 +0100 - -psycopg2 (2.0.6-2) unstable; urgency=low - - * Build a python-psycopg-dbg package - - -- Scott Kitterman Tue, 03 Jul 2007 16:55:48 -0400 - -psycopg2 (2.0.6-1) unstable; urgency=low - - * New upstream release. - * psycopg2da: removed, merged upstream. - - -- Fabio Tranchitella Sat, 09 Jun 2007 22:38:23 +0200 - -psycopg2 (2.0.5.1-7) UNRELEASED; urgency=low - - * debian/watch: added. - - -- Fabio Tranchitella Fri, 9 Feb 2007 12:35:55 +0100 - -psycopg2 (2.0.5.1-6) unstable; urgency=high - - * debian/zope-psycopgda2.dzproduct: requires Zope 2.9 or higher: previous - versions use python2.3 which is not supported anymore in psycopg. - - -- Fabio Tranchitella Mon, 15 Jan 2007 11:39:15 +0100 - -psycopg2 (2.0.5.1-5) unstable; urgency=medium - - * Do not run dh_pycentral on zope-psycopgda2. (Closes: #400846) - - -- Fabio Tranchitella Wed, 29 Nov 2006 09:04:09 +0100 - -psycopg2 (2.0.5.1-4) unstable; urgency=medium - - * Fixed a bug in psycopg2da. - * debian/control: bumped build-dependency on zope-debhelper. - * Added XS-Vcs-Svn field - - -- Fabio Tranchitella Fri, 24 Nov 2006 13:50:11 +0100 - -psycopg2 (2.0.5.1-3) unstable; urgency=low - - * psycopgda: imported upstream psycopg2da database adapter from SVN, which - builds a new binary package, python-psycopg2da. - - -- Fabio Tranchitella Fri, 10 Nov 2006 08:56:05 +0100 - -psycopg2 (2.0.5.1-2) unstable; urgency=low - - * debian/control: added again a dependency on python-egenix-mxdatetime. - (Closes: #389636) - - -- Fabio Tranchitella Tue, 3 Oct 2006 10:25:22 +0200 - -psycopg2 (2.0.5.1-1) unstable; urgency=low - - * New upstream release. - - -- Fabio Tranchitella Tue, 19 Sep 2006 08:22:36 +0200 - -psycopg2 (2.0.4-1) unstable; urgency=low - - * New upstream release. - * debian/control: - + removed dependency on python-egenix-mxdatetime. - + added ${shlibs:Depends} for the python-psycopg2 package. - (Closes: #381462) - - -- Fabio Tranchitella Wed, 9 Aug 2006 10:28:30 +0200 - -psycopg2 (2.0.2-1) unstable; urgency=low - - * New upstream major release, new source package. (Closes: #377956) - - -- Fabio Tranchitella Sun, 16 Jul 2006 21:43:41 +0200 - -psycopg (1.1.21-8) unstable; urgency=low - - * debian/zope-psycopgda.dzproduct: added 2.9 to the list of supported - zope versions. (Closes: #376538) - - -- Fabio Tranchitella Fri, 14 Jul 2006 10:19:54 +0200 - -psycopg (1.1.21-7) unstable; urgency=low - - * Moved dh_installzope within an arch-indep target. (Closes: #373842) - - -- Fabio Tranchitella Fri, 16 Jun 2006 09:37:23 +0200 - -psycopg (1.1.21-6) unstable; urgency=low - - * Python policy transition. (Closes: #373482) - - -- Fabio Tranchitella Thu, 15 Jun 2006 19:09:36 +0200 - -psycopg (1.1.21-5) unstable; urgency=high - - * ypemod.c, new_psyco_bufferobject(): - - Escape quotes psycopg.Binary() results as '', not as \', since the - latter does not work any more with some client encodings with the latest - PostgreSQL (in some multi-byte encodings you can exploit \' escaping to - inject SQL code, see CVE-2006-2314). (Closes: #369230) - Thanks to Martin Pitt and Ubuntu security team for the patch. - - -- Fabio Tranchitella Tue, 30 May 2006 22:15:06 +0200 - -psycopg (1.1.21-4) unstable; urgency=low - - * debian/rules: remove *.o in the clean target. (Closes: #352835) - - -- Fabio Tranchitella Thu, 16 Feb 2006 12:06:53 +0000 - -psycopg (1.1.21-3) unstable; urgency=low - - * debian/control: removed build-dependency on postgresql-server-dev-8.0, - as suggested by Martin Pitt. (Closes: #339640) - - -- Fabio Tranchitella Fri, 18 Nov 2005 08:44:26 +0000 - -psycopg (1.1.21-2) unstable; urgency=low - - * debian/control: zope-psycopgda should depend on the same version of the - psycopg python module. (Closes: #336765) - - -- Fabio Tranchitella Wed, 2 Nov 2005 12:07:33 +0000 - -psycopg (1.1.21-1) unstable; urgency=low - - * New maintainer; Thanks Federico for your work, and be sure that I'll - take care of this package. - * New upstream release (Closes: #321592, #320618, #333638) - * debian/python2.4-psycopg.dirs: added. (Closes: #319509, #329115) - * debian/control: dropped support for python2.1 and - python2.2. (Closes: #333639) - * debian/control: Standards-Version bumped to 3.6.2, no changes required. - * debian/rules: make use of dh_installzope from zope-debhelper to build the - zope-psycopgda package. - (Closes: #158669, #323599, #268975, #292247, #327415) - * debian/control: added build-depends on postgresql-server-dev-8.0. - (Closes: #333638) - * Re-packaged upstream tarball replacing some broken images. - (Closes: #292008, #305392) - - -- Fabio Tranchitella Fri, 28 Oct 2005 11:24:37 +0000 - -psycopg (1.1.19-1) unstable; urgency=low - - * New upstream release. - * Applied patch from Martin Krafft to build Zope 2.7 packages. - * Modified to use the new PostgreSQL packages. - * Added python 2.4 package (Closes: #301403). - * Upstream applied various Ubuntu patches (Closes: #301947, #300893). - - -- Federico Di Gregorio Sat, 16 Jul 2005 20:47:08 +0200 - -psycopg (1.1.18-1) unstable; urgency=low - - * New upstream release. - * 1.1.16 fixed rowcount bug (closes: #266299). - - -- Federico Di Gregorio Wed, 5 Jan 2005 21:05:15 +0100 - -psycopg (1.1.17-1) unstable; urgency=high - - * Urgency is still high because 1.1.16 was never uploaded. - * New upstream release. - - -- Federico Di Gregorio Thu, 19 Nov 2004 01:14:30 +0200 - -psycopg (1.1.16-1) unstable; urgency=high - - * New upstream release. - * Tagged with urgency=high because fix a grave bug (rowcount) introduced - in 1.1.15. - * Upstream fix: does not segfault when using COPY TO/COPY FROM in - .execute() (closes: #279222). - - -- Federico Di Gregorio Sat, 30 Oct 2004 02:35:30 +0200 - -psycopg (1.1.15-1) unstable; urgency=low - - * New upstream release. - * Definitely fixed (ah ah) time interval problems (closes: #259213). - - -- Federico Di Gregorio Thu, 29 Jul 2004 23:43:59 +0200 - -psycopg (1.1.14-1) unstable; urgency=low - - * New upstream release. - * Don't put two copies of changelog in every package anymore - (closes: #256662). - * Updated test script works as expected (closes: #231391). - * Changes from NMU incorporated in 1.1.12: - - zpsycopgda depends on python2.2-psycopg (closes: #227420, #227147). - - compiled with postgresql in unstable (close: #220527). - - -- Federico Di Gregorio Fri, 9 Jul 2004 23:01:40 +0200 - -psycopg (1.1.13-1) unstable; urgency=low - - * New upstream release. - - -- Federico Di Gregorio Fri, 21 May 2004 10:33:54 +0200 - -psycopg (1.1.12-1) unstable; urgency=low - - * New upstream release (the "martin you won't have this package" - release.) - * Integrated changes from NMU releases. - - -- Federico Di Gregorio Sun, 16 May 2004 10:14:47 +0200 - -psycopg (1.1.10-1.2) unstable; urgency=low - - * Non-maintainer upload. Thinking about taking this package over... - * Changed dependency on pyscopgda Python module to Python version 2.2. - (closes: #227147, #227420) - * Added Lintian overrides for image-in-/usr/lib warnings -- Zope needs - these images... - - -- martin f. krafft Thu, 15 Apr 2004 23:30:40 +0200 - -psycopg (1.1.10-1.1) unstable; urgency=low - - * Non-maintainer upload. - * No changes - this upload is simply a rebuild against the current unstable - instead of experimental postgresql-dev. - (closes: #219927, #220141, #220173, #220527) - - -- Peter Hawkins Sun, 28 Dec 2003 10:57:30 +1100 - -psycopg (1.1.10-1) unstable; urgency=low - - * Added download location to debian/copyright file (Closes: #215880). - - -- Federico Di Gregorio Sat, 8 Nov 2003 23:32:40 +0100 - -psycopg (1.1.9-1) unstable; urgency=low - - * New upstream release. - * Bug was agains an old 1.0.x version of psycopg (Closes: #208702). - - -- Federico Di Gregorio Wed, 10 Sep 2003 13:04:42 +0200 - -psycopg (1.1.8-1) unstable; urgency=low - - * New upstream release. - * Integrated NMU from Matthias Klose (closes: #205746). - - -- Federico Di Gregorio Fri, 1 Aug 2003 11:50:57 +0200 - -psycopg (1.1.5.1-1.1) unstable; urgency=low - - * NMU - * Update for python2.3 as the default python version (closes: #205746). - - -- Matthias Klose Fri, 22 Aug 2003 00:02:25 +0200 - -psycopg (1.1.7-1) unstable; urgency=low - - * New upstream release. - - -- Federico Di Gregorio Sat, 26 Jul 2003 15:03:39 +0200 - -psycopg (1.1.6-1) unstable; urgency=low - - * New upstream release. - * Upstream applied patch from BTS (Closes: #200161). - - -- Federico Di Gregorio Sun, 13 Jul 2003 23:36:04 +0200 - -psycopg (1.1.5.1-1) unstable; urgency=low - - * New upstream release. - - -- Federico Di Gregorio Mon, 23 Jun 2003 00:37:33 +0200 - -psycopg (1.1.5-1) unstable; urgency=low - - * New upstream release. - - -- Federico Di Gregorio Sun, 22 Jun 2003 21:30:01 +0200 - -psycopg (1.1.4-1) unstable; urgency=low - - * New upstream release. - - -- Federico Di Gregorio Wed, 7 May 2003 15:21:31 +0200 - -psycopg (1.1.3-1) unstable; urgency=low - - * New upstream release. - * Changed section in debian/control (-> python). - - -- Federico Di Gregorio Wed, 2 Apr 2003 10:33:36 +0200 - -psycopg (1.1.2-1) unstable; urgency=low - - * New upstream release. - * Started to track the 1.1.x branch. - - -- Federico Di Gregorio Tue, 25 Feb 2003 01:06:08 +0100 - -psycopg (1.0.15.1-1) unstable; urgency=low - - * New upstream release. - - -- Federico Di Gregorio Fri, 14 Feb 2003 16:09:50 +0100 - -psycopg (1.0.15-1) unstable; urgency=low - - * New upstream release. - - -- Federico Di Gregorio Wed, 12 Feb 2003 23:49:51 +0100 - -psycopg (1.0.14-1) unstable; urgency=low - - * Applied patch from John Goerzen to fix memory leak in executemany() - and callproc() (Closes: #169284). - * New upstream release (Closes: #170297). - - -- Federico Di Gregorio Mon, 25 Nov 2002 16:50:37 +0100 - -psycopg (1.0.13-1) unstable; urgency=low - - * New upstream release. - * Python 2.3 package added (Closes: #160831) - * IntegrityError raised when needed (upstream, Closes: #165791) - * Packages are lintian clean again. - - -- Federico Di Gregorio Fri, 25 Oct 2002 11:54:19 +0200 - -psycopg (1.0.12-1) unstable; urgency=low - - * New upstream release. - * Fixed wrong url in RELEASE-1.0. (Closes: #153840) - - -- Federico Di Gregorio Fri, 13 Sep 2002 13:16:36 +0200 - -psycopg (1.0.11.1-1) unstable; urgency=low - - * New upstream release. - - -- Federico Di Gregorio Mon, 26 Aug 2002 10:41:54 +0200 - -psycopg (1.0.11-1) unstable; urgency=low - - * New upstream release. - * The dummy python-psycopg package now depends on the new default debian - python (2.2) and on python2.2-psycopg. - * Removed support for python 1.5 (support for 2.3 has to wait for egenix - packages.) - - -- Federico Di Gregorio Fri, 23 Aug 2002 11:25:01 +0200 - -psycopg (1.0.10-1) unstable; urgency=low - - * New upstream release. - - -- Federico Di Gregorio Mon, 22 Jul 2002 02:04:59 +0200 - -psycopg (1.0.9-1) unstable; urgency=low - - * Resolved section override (main->interpreters). - * New upstream release. - - -- Federico Di Gregorio Thu, 20 Jun 2002 14:00:42 +0200 - -psycopg (1.0.8-1) unstable; urgency=low - - * New upstream release. - - -- Federico Di Gregorio Tue, 23 Apr 2002 22:42:22 +0200 - -psycopg (1.0.7.1-2) unstable; urgency=low - - * Moved to main. - - -- Federico Di Gregorio Fri, 19 Apr 2002 10:06:58 +0200 - -psycopg (1.0.7.1-1) unstable; urgency=low - - * New upstream release. - * Fixed a bug in ./configure; closes: #141774. - - -- Federico Di Gregorio Mon, 8 Apr 2002 18:54:24 +0200 - -psycopg (1.0.7-1) unstable; urgency=low - - * New upstream release. - - -- Federico Di Gregorio Fri, 29 Mar 2002 14:24:45 +0100 - -psycopg (1.0.6-1) unstable; urgency=low - - * New upstream release. - * Builds with new libpq libraries and header layout. - - -- Federico Di Gregorio Thu, 7 Mar 2002 11:59:40 +0100 - -psycopg (1.0.5-1) unstable; urgency=low - - * New upstream release. - - -- Federico Di Gregorio Mon, 4 Mar 2002 14:43:13 +0100 - -psycopg (1.0.4-1) unstable; urgency=low - - * New upstream release. - - -- Federico Di Gregorio Wed, 20 Feb 2002 20:37:16 +0100 - -psycopg (1.0.3-1) unstable; urgency=low - - * New upstream release. - - -- Federico Di Gregorio Fri, 8 Feb 2002 15:17:44 +0100 - -psycopg (1.0.2-1) unstable; urgency=low - - * New upstream release. - * Added package for python2.2 (Closes: #132650). - - -- Federico Di Gregorio Fri, 8 Feb 2002 00:45:07 +0100 - -psycopg (1.0.1-1) unstable; urgency=low - - * New upstream release. - - -- Federico Di Gregorio Sun, 20 Jan 2002 18:27:22 +0100 - -psycopg (1.0-4) unstable; urgency=low - - * Added build depend on plain python, to really close the %£$! #121229 - bug this time (Closes: #121229). - - -- Federico Di Gregorio Wed, 28 Nov 2001 10:50:06 +0100 - -psycopg (1.0-3) unstable; urgency=low - - * Added explicit build depends on python 1.5 & 2.1 (Closes: #121229). - * Fixed bad dependency on python1.5-egenix-mxdatetime. - - -- Federico Di Gregorio Mon, 26 Nov 2001 17:18:41 +0100 - -psycopg (1.0-2) unstable; urgency=low - - * Fixed dependencies as per python policy. - * Added default, unversioned psycopg package (python-psycopg). - * Added non-US/main and rebuilt after REJECT. - - -- Federico Di Gregorio Fri, 16 Nov 2001 01:14:54 +0100 - -psycopg (1.0-1) unstable; urgency=low - - * New upstream release. 1.0! - * Now we build versioned packages for python 1.5 and 2.1. - - -- Federico Di Gregorio Tue, 13 Nov 2001 19:24:39 +0100 - -psycopg (0.99.7-1) unstable; urgency=low - - * New upstream release fixing some little bugs. - * This version requires the mx DateTime packages that are not yet in - debian... waiting for them I'll distribute both psycopg and unofficial - packages on the initd psycopg page. - - -- Federico Di Gregorio Tue, 18 Sep 2001 23:28:51 +0200 - -psycopg (0.99.6-2) unstable; urgency=low - - * Added suggested build-depends (Closes: #112112). - * Applied patch by Michael Weber to configure.in, to look for a compiler - (Closes: #112024). - - -- Federico Di Gregorio Thu, 13 Sep 2001 10:49:37 +0200 - -psycopg (0.99.6-1) unstable; urgency=low - - * Added Build-depends line (Closes: #89798). - * Now zope-psycopgda requires python-psycopg, zope on debian still runs - with python 1.x only (Closes: #108890). - * Moved package to non-US (psycopg depends on postgresql that is in - non-US, sic). - - -- Federico Di Gregorio Mon, 3 Sep 2001 13:02:11 +0200 - -psycopg (0.99.5-1) unstable; urgency=low - - * New upstream release with bound variables quoting (Closes: #102843). - * The build process set the correct path to DateTime module - (Closes: #102838). - * Removes .pyc files in prerm (Closes: #104382) - - -- Federico Di Gregorio Thu, 12 Jul 2001 12:56:38 +0200 - -psycopg (0.99.4-1) unstable; urgency=low - - * New upstream release. - - -- Federico Di Gregorio Mon, 2 Jul 2001 15:33:29 +0200 - -psycopg (0.99.3-1) unstable; urgency=low - - * New upstream release. - - -- Federico Di Gregorio Wed, 20 Jun 2001 12:55:47 +0200 - -psycopg (0.99.2-1) unstable; urgency=low - - * New upstream release. - - -- Federico Di Gregorio Tue, 5 Jun 2001 15:37:50 +0200 - -psycopg (0.99.1-1) unstable; urgency=low - - * New upstream release. - - -- Federico Di Gregorio Tue, 5 Jun 2001 12:46:18 +0200 - -psycopg (0.5.5-1) unstable; urgency=low - - * New upstream release (ok, *we* are the upstream authors, but after - putting the -1 in the version i am supposed to say "new upstream - version" when the non-debian versions changes, right? ouch...) - - -- Federico Di Gregorio Fri, 1 Jun 2001 17:18:52 +0200 - -psycopg (0.5.4-1) unstable; urgency=low - - * Another bugfixing release. - * Added debian revision to be able to release multiple versions with the - same upstream version. - - -- Federico Di Gregorio Fri, 18 May 2001 19:32:59 +0200 - -psycopg (0.5.3) unstable; urgency=low - - * Some bugs fixed, new release. - - -- Federico Di Gregorio Fri, 4 May 2001 16:19:09 +0200 - -psycopg (0.5.2) unstable; urgency=low - - * New bugfixing release. - - -- Federico Di Gregorio Fri, 27 Apr 2001 09:52:16 +0200 - -psycopg (0.5.1) unstable; urgency=low - - * New bugfixing release. - - -- Federico Di Gregorio Tue, 3 Apr 2001 11:13:26 +0200 - -psycopg (0.5.0) unstable; urgency=low - - * New release. - - -- Federico Di Gregorio Fri, 30 Mar 2001 12:54:42 +0200 - -psycopg (0.4.7) unstable; urgency=low - - * New release. - * Lots of small bug fixes (see detailed ChangeLog.) - * Includes beginning of DBAPI-2.0 testsuite. - - -- Federico Di Gregorio Fri, 16 Mar 2001 18:29:03 +0100 - -psycopg (0.4.6) unstable; urgency=low - - * New release. - * Fixed a little bug in debian/rules (does not create an examples - directory inside examples.) - - -- Federico Di Gregorio Wed, 14 Mar 2001 01:00:26 +0100 - -psycopg (0.4.5) unstable; urgency=low - - * New upstream (mmm... but one of the upstream authors it is - *me*... mmm...) release. - - -- Federico Di Gregorio Mon, 12 Mar 2001 11:41:42 +0100 - -psycopg (0.4.4) unstable; urgency=low - - * New release. - * Fixed Sections in debian/control. - - -- Federico Di Gregorio Fri, 9 Mar 2001 10:11:02 +0100 - -psycopg (0.4.3) unstable; urgency=low - - * New release. - * Fixed typo in connectionAdd.dtml (Closes: #88817) - - -- Federico Di Gregorio Wed, 7 Mar 2001 15:54:35 +0100 - -psycopg (0.4.2) unstable; urgency=low - - * New release (fixes bugs in ZPsycopgDA.) - - -- Federico Di Gregorio Mon, 5 Mar 2001 13:33:39 +0100 - -psycopg (0.4.1) unstable; urgency=low - - * New release. - * we now create packages for both versions of python in debian - (1.5 and 2.0, packages python-* and python2-*) - - -- Federico Di Gregorio Fri, 2 Mar 2001 12:10:52 +0100 - -psycopg (0.4) unstable; urgency=low - - * News release. - * Now debian/rules build the Zope Database Adapter zope-psycopgda too. - * Source name changed from python-psycopg to psycopg. - - -- Federico Di Gregorio Tue, 27 Feb 2001 15:11:04 +0100 - -python-psycopg (0.3) unstable; urgency=low - - * New release. Tons of bugs fixed and new features, see ChangeLog for - details. - - -- Federico Di Gregorio Mon, 26 Feb 2001 21:22:23 +0100 - -python-psycopg (0.2) unstable; urgency=low - - * New release. Fixed lots of bugs and memory leaks. - - -- Federico Di Gregorio Fri, 16 Feb 2001 11:04:17 +0100 - -python-psycopg (0.1) unstable; urgency=low - - * Initial release. - - -- Federico Di Gregorio Mon, 12 Feb 2001 14:46:53 +0100 - - diff --git a/debian/compat b/debian/compat deleted file mode 100644 index 7ed6ff82..00000000 --- a/debian/compat +++ /dev/null @@ -1 +0,0 @@ -5 diff --git a/debian/control b/debian/control deleted file mode 100644 index 7651de5e..00000000 --- a/debian/control +++ /dev/null @@ -1,64 +0,0 @@ -Source: psycopg2 -Section: python -Priority: optional -Build-Depends: debhelper (>= 5.0.37.2), python-all-dev, python-all-dbg, python-central (>= 0.5.0), python (>= 2.3.5-7), python-egenix-mx-base-dev, autoconf, libpq-dev -Build-Depends-Indep: zope-debhelper (>= 0.3.4) -Maintainer: Fabio Tranchitella -Standards-Version: 3.7.3 -XS-Python-Version: all -Vcs-Svn: svn://svn.debian.org/python-modules/packages/psycopg2/trunk/ -Vcs-Browser: http://svn.debian.org/wsvn/python-modules/packages/psycopg2/trunk/?op=log - -Package: python-psycopg2 -Architecture: any -Section: python -Depends: ${python:Depends}, ${shlibs:Depends}, python-egenix-mxdatetime -Provides: ${python:Provides} -XB-Python-Version: ${python:Versions} -Description: Python module for PostgreSQL - psycopg is a PostgreSQL database adapter for the Python programming language - (just like pygresql and popy.) This is version 2, a complete rewrite of the - original code to provide new-style classes for connection and cursor objects - and other sweet candies. Like the original, psycopg 2 was written with the - aim of being very small and fast, and stable as a rock. - . - psycopg is different from the other database adapter because it was designed - for heavily multi-threaded applications that create and destroy lots of - cursors and make a conspicuous number of concurrent INSERTs or UPDATEs. - psycopg 2 also provides full asycronous operations for the really brave - programmer. - . - The main advantages of psycopg2 are that it supports the full Python DBAPI-2.0 - and being thread safe at level 2. It also includes some extensions to the - standard DBAPI-2.0 to allow for better thread performance. - -Package: python-psycopg2-dbg -Priority: extra -Architecture: any -Section: python -Depends: python-psycopg2 (= ${binary:Version}), python-dbg, python-egenix-mxdatetime-dbg, ${shlibs:Depends} -Description: Python module for PostgreSQL (debug extension) - psycopg is a PostgreSQL database adapter for the Python programming language - (just like pygresql and popy.) This is version 2, a complete rewrite of the - original code to provide new-style classes for connection and cursor objects - and other sweet candies. Like the original, psycopg 2 was written with the - aim of being very small and fast, and stable as a rock. - . - This package contains the extensions built for the Python debug interpreter. - -Package: zope-psycopgda2 -Architecture: all -Section: python -Depends: ${zope:Depends}, python-psycopg2 (>= ${source:Version}) -Description: Zope database adapter based on python-psycopg2 - The package contains the PostgreSQL database adapter for Zope 2.7, 2.8 and - 2.9 based on the psycopg2 Python module. - -Package: python-psycopg2da -Architecture: all -Section: python -Depends: ${zope:Depends}, python-psycopg2 (>= ${source:Version}) -XB-Python-Version: ${zope:PythonVersion} -Description: Zope database adapter based on python-psycopg2 -- zope3 version - The package contains the PostgreSQL database adapter for Zope 3 based on - the psycopg2 Python module. diff --git a/debian/copyright b/debian/copyright deleted file mode 100644 index 0f2211d0..00000000 --- a/debian/copyright +++ /dev/null @@ -1,112 +0,0 @@ -This package was debianized by Fabio Tranchitella on -Sun, 16 Jul 2006 21:10:01 +0200. - -psycopg2 can be downloaded from its homepage: - - http://initd.org/projects/psycopg - -The tarball has been re-packed to get rid of the upstream debian/ directory: -no other changes have been made to the tarball. - - -Copyright: - - Copyright (C) 2001-2006 Federico Di Gregorio - Copyright (C) 2001 Michele Comitini - - For psycopg2da: - Copyright (C) 2006 Fabio Tranchitella - - For the files doc/copy_from.py and doc/copy_to.py: - Copyright (C) 2001-2005 Federico Di Gregorio - Copyright (C) 2002 Tom Jenkins - - For the file tests/dbapi20.py: - Copyright (C) 2003 Ian Bicking - - For the file scripts/ext2html.py: - Copyright (C) 2003 Daniele Varrazzo - - -License for psycopg2, ZPsycopgDA and psycopg2da: - - psycopg 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. - - On Debian GNU/Linux systems, the complete text of the GNU General - Public License can be found in '/usr/share/common-licenses/GPL'. - - As a special exception, specific permission is granted for the GPLed - code in this distribition to be linked to OpenSSL and PostgreSQL libpq - without invoking GPL clause 2(b). - - Note that the GPL was chosen to avoid proprietary adapters based on - psycopg code. Using psycopg in a proprietary product (even bundling - psycopg with the proprietary product) is fine as long as: - - 1. psycopg is called from Python only using only the provided API - (i.e., no linking with C code and no C modules based on it); and - - 2. all the other points of the GPL are respected (you offer a copy - of psycopg's source code, and so on.) - - -License for the files tests/dbapi20.py and scripts/ext2html.py: - - These modules have been placed in the public domain. - - -Alternative licenses for ZPsycopgDA: - - If you prefer you can use the Zope Database Adapter ZPsycopgDA (i.e., - every file inside the ZPsycopgDA directory) user the ZPL license as - published on the Zope web site, http://www.zope.org/Resources/ZPL. - - -Alternative licenses for psycopg2da: - - If you prefer you can use the Zope3 Database Adapter psycopg2da (i.e., - every file inside the psycopg2da directory) user the ZPL license as - published on the Zope web site, http://www.zope.org/Resources/ZPL. - - -Alternative licenses for psycopg/adapter*.{j,c} and -psycopg/microprotocol*.{h.c}: - - Also, the following BSD-like license applies (at your option) to the - files following the pattern psycopg/adapter*.{h,c} and - psycopg/microprotocol*.{h,c}: - - Permission is granted to anyone to use this software for any purpose, - including commercial applications, and to alter it and redistribute it - freely, subject to the following restrictions: - - 1. The origin of this software must not be misrepresented; you must not - claim that you wrote the original software. If you use this - software in a product, an acknowledgment in the product documentation - would be appreciated but is not required. - - 2. Altered source versions must be plainly marked as such, and must not - be misrepresented as being the original software. - - 3. This notice may not be removed or altered from any source distribution. - - psycopg is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - -Proprietary licenses: - - A non-exclusive license is available for companies that want to include - psycopg in their proprietary products without respecting the spirit of the - GPL. The price of the license is one day of development done by the author, - at the consulting fee he applies to his usual customers at the day of the - request. - - Please contact the upstream author (Federico Di Gregorio ) - for more information about this license. - diff --git a/debian/pycompat b/debian/pycompat deleted file mode 100644 index 0cfbf088..00000000 --- a/debian/pycompat +++ /dev/null @@ -1 +0,0 @@ -2 diff --git a/debian/python-psycopg2da.dzproduct b/debian/python-psycopg2da.dzproduct deleted file mode 100644 index d7d9206a..00000000 --- a/debian/python-psycopg2da.dzproduct +++ /dev/null @@ -1,3 +0,0 @@ -Name: psycopg2da -ZopeVersions: 3 -Global: yes diff --git a/debian/rules b/debian/rules deleted file mode 100755 index f33dd46d..00000000 --- a/debian/rules +++ /dev/null @@ -1,93 +0,0 @@ -#!/usr/bin/make -f -# Sample debian/rules that uses debhelper. -# GNU copyright 1997 to 1999 by Joey Hess. - -PYVERS=$(shell pyversions -r debian/control) - -configure: configure-stamp -configure-stamp: - dh_testdir - rm -f configure - touch configure-stamp - -build: configure build-stamp -build-stamp: - dh_testdir - for python in $(PYVERS); do \ - $$python setup.py build ; \ - done - for python in $(PYVERS); do \ - $$python-dbg setup.py build ; \ - done - touch build-stamp - -clean: configure - dh_testdir - dh_testroot - rm -fr *-stamp build - for python in $(PYVERS); do \ - $$python setup.py clean ; \ - done - dh_clean - -install-arch: build - dh_testdir - dh_testroot - dh_clean -k - dh_installdirs - # psycopg2 - for python in $(PYVERS); do \ - $$python setup.py install \ - --root=$(CURDIR)/debian/python-psycopg2 --no-compile; \ - done - for python in $(PYVERS); do \ - $$python-dbg setup.py install \ - --root=$(CURDIR)/debian/python-psycopg2-dbg --no-compile; \ - done - find debian/python-*-dbg ! -type d ! -name '*.so' | xargs rm -f - find debian/python-*-dbg -depth -empty -exec rmdir {} \; - -install-indep: build - # Zope package - dh_installzope -p zope-psycopgda2 ZPsycopgDA - # Zope3 package - dh_installzope -p python-psycopg2da psycopg2da - -# Build architecture-independent files here. -binary-indep: build install-indep - dh_testdir - dh_testroot - dh_installdocs -i AUTHORS - dh_installchangelogs -i - dh_link -i - dh_compress -i - dh_fixperms -i - dh_pycentral -p python-psycopg2da - dh_installdeb -i - dh_gencontrol -i - dh_md5sums -i - dh_builddeb -i - -# Build architecture-dependent files here. -binary-arch: build install-arch - dh_testdir - dh_testroot - dh_installdocs -a README AUTHORS doc tests - dh_installchangelogs -a ChangeLog - dh_link -a - dh_strip -ppython-psycopg2 --dbg-package=python-psycopg2-dbg - rm -rf debian/python-psycopg2-dbg/usr/share/doc/python-psycopg2-dbg - ln -s python-psycopg2 debian/python-psycopg2-dbg/usr/share/doc/python-psycopg2-dbg - dh_compress -a - dh_fixperms -a - dh_makeshlibs -a - dh_pycentral -a - dh_python -a - dh_installdeb -a - dh_shlibdeps -a - dh_gencontrol -a - dh_md5sums -a - dh_builddeb -a - -binary: binary-indep binary-arch -.PHONY: build clean binary-indep binary-arch binary install configure diff --git a/debian/watch b/debian/watch deleted file mode 100644 index 7441da9e..00000000 --- a/debian/watch +++ /dev/null @@ -1,2 +0,0 @@ -version=3 -http://www.initd.org/pub/software/psycopg/psycopg2-([0-9][0-9\.\-]*).tar.gz debian uupdate diff --git a/debian/zope-psycopgda2.dzproduct b/debian/zope-psycopgda2.dzproduct deleted file mode 100644 index e5634e4c..00000000 --- a/debian/zope-psycopgda2.dzproduct +++ /dev/null @@ -1,4 +0,0 @@ -Name: ZPsycopgDA -Directory: ZPsycopgDA:2 -Package: zope-psycopgda2 -ZopeVersions: >= 2.9 diff --git a/doc/src/_static/psycopg.css b/doc/src/_static/psycopg.css index c4a2af65..a5d5b3a6 100644 --- a/doc/src/_static/psycopg.css +++ b/doc/src/_static/psycopg.css @@ -26,3 +26,7 @@ a > tt.sql:hover { dl.faq dt { font-weight: bold; } + +table.data-types div.line-block { + margin-bottom: 0; +} diff --git a/doc/src/advanced.rst b/doc/src/advanced.rst index a7b406a5..dafb1f58 100644 --- a/doc/src/advanced.rst +++ b/doc/src/advanced.rst @@ -27,6 +27,7 @@ More advanced topics wait(aconn) acurs = aconn.cursor() + .. index:: double: Subclassing; Cursor double: Subclassing; Connection @@ -45,6 +46,16 @@ but other uses are possible. `cursor` is much more interesting, because it is the class where query building, execution and result type-casting into Python variables happens. +The `~psycopg2.extras` module contains several examples of :ref:`connection +and cursor sublcasses `. + +.. note:: + + If you only need a customized cursor class, since Psycopg 2.5 you can use + the `~connection.cursor_factory` parameter of a regular connection instead + of creating a new `!connection` subclass. + + .. index:: single: Example; Cursor subclass @@ -403,13 +414,13 @@ this will be probably implemented in a future release. .. _green-support: -Support to coroutine libraries ------------------------------- +Support for coroutine libraries +------------------------------- .. versionadded:: 2.2.0 -Psycopg can be used together with coroutine_\-based libraries, and participate -to cooperative multithreading. +Psycopg can be used together with coroutine_\-based libraries and participate +in cooperative multithreading. Coroutine-based libraries (such as Eventlet_ or gevent_) can usually patch the Python standard library in order to enable a coroutine switch in the presence of diff --git a/doc/src/conf.py b/doc/src/conf.py index 7105907f..5937a7b4 100644 --- a/doc/src/conf.py +++ b/doc/src/conf.py @@ -26,7 +26,7 @@ extensions = ['sphinx.ext.autodoc', 'sphinx.ext.todo', 'sphinx.ext.ifconfig', 'sphinx.ext.doctest', 'sphinx.ext.intersphinx' ] # Specific extensions for Psycopg documentation. -extensions += [ 'dbapi_extension', 'sql_role' ] +extensions += [ 'dbapi_extension', 'sql_role', 'ticket_role' ] # Add any paths that contain templates here, relative to this directory. templates_path = ['_templates'] @@ -42,7 +42,7 @@ master_doc = 'index' # General information about the project. project = u'Psycopg' -copyright = u'2001-2011, Federico Di Gregorio. Documentation by Daniele Varrazzo' +copyright = u'2001-2013, Federico Di Gregorio. Documentation by Daniele Varrazzo' # The version info for the project you're documenting, acts as replacement for # |version| and |release|, also used in various other places throughout the @@ -65,6 +65,9 @@ intersphinx_mapping = { 'py3': ('http://docs.python.org/3.2', None), } +# Pattern to generate links to the bug tracker +ticket_url = 'http://psycopg.lighthouseapp.com/projects/62710/tickets/%s' + # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. #language = None diff --git a/doc/src/connection.rst b/doc/src/connection.rst index e0b34ab8..2828ab89 100644 --- a/doc/src/connection.rst +++ b/doc/src/connection.rst @@ -21,16 +21,17 @@ The ``connection`` class Connections are thread safe and can be shared among many threads. See :ref:`thread-safety` for details. - .. method:: cursor([name] [, cursor_factory] [, withhold]) + .. method:: cursor(name=None, cursor_factory=None, scrollable=None, withhold=False) Return a new `cursor` object using the connection. If *name* is specified, the returned cursor will be a :ref:`server side cursor ` (also known as *named cursor*). - Otherwise it will be a regular *client side* cursor. By default a - :sql:`WITHOUT HOLD` cursor is created; to create a :sql:`WITH HOLD` - cursor, pass a `!True` value as the *withhold* parameter. See - :ref:`server-side-cursors`. + Otherwise it will be a regular *client side* cursor. By default a + named cursor is declared without :sql:`SCROLL` option and + :sql:`WITHOUT HOLD`: set the argument or property `~cursor.scrollable` + to `!True`/`!False` and or `~cursor.withhold` to `!True` to change the + declaration. The name can be a string not valid as a PostgreSQL identifier: for example it may start with a digit and contain non-alphanumeric @@ -46,14 +47,17 @@ The ``connection`` class Consider it as part of the query, not as a query parameter. The *cursor_factory* argument can be used to create non-standard - cursors. The class returned should be a subclass of + cursors. The class returned must be a subclass of `psycopg2.extensions.cursor`. See :ref:`subclassing-cursor` for - details. + details. A default factory for the connection can also be specified + using the `~connection.cursor_factory` attribute. + + .. versionchanged:: 2.4.3 added the *withhold* argument. + .. versionchanged:: 2.5 added the *scrollable* argument. .. extension:: - The `name` and `cursor_factory` parameters are Psycopg - extensions to the |DBAPI|. + All the function arguments are Psycopg extensions to the |DBAPI|. .. index:: @@ -71,6 +75,10 @@ The ``connection`` class automatically open, commands have immediate effect. See :ref:`transactions-control` for details. + .. versionchanged:: 2.5 if the connection is used in a ``with`` + statement, the method is automatically called if no exception is + raised in the ``with`` block. + .. index:: pair: Transaction; Rollback @@ -81,6 +89,10 @@ The ``connection`` class connection without committing the changes first will cause an implicit rollback to be performed. + .. versionchanged:: 2.5 if the connection is used in a ``with`` + statement, the method is automatically called if an exception is + raised in the ``with`` block. + .. method:: close() @@ -493,6 +505,15 @@ The ``connection`` class the payload was not accessible. To keep backward compatibility, `!Notify` objects can still be accessed as 2 items tuples. + + .. attribute:: cursor_factory + + The default cursor factory used by `~connection.cursor()` if the + parameter is not specified. + + .. versionadded:: 2.5 + + .. index:: pair: Backend; PID diff --git a/doc/src/cursor.rst b/doc/src/cursor.rst index 4b8495ae..62be5e3c 100644 --- a/doc/src/cursor.rst +++ b/doc/src/cursor.rst @@ -83,6 +83,11 @@ The ``cursor`` class The cursor will be unusable from this point forward; an `~psycopg2.InterfaceError` will be raised if any operation is attempted with the cursor. + + .. versionchanged:: 2.5 if the cursor is used in a ``with`` statement, + the method is automatically called at the end of the ``with`` + block. + .. attribute:: closed @@ -114,20 +119,51 @@ The ``cursor`` class The `name` attribute is a Psycopg extension to the |DBAPI|. + .. attribute:: scrollable + + Read/write attribute: specifies if a named cursor is declared + :sql:`SCROLL`, hence is capable to scroll backwards (using + `~cursor.scroll()`). If `!True`, the cursor can be scrolled backwards, + if `!False` it is never scrollable. If `!None` (default) the cursor + scroll option is not specified, usually but not always meaning no + backward scroll (see the |declare-notes|__). + + .. |declare-notes| replace:: :sql:`DECLARE` notes + .. __: http://www.postgresql.org/docs/current/static/sql-declare.html#SQL-DECLARE-NOTES + + .. note:: + + set the value before calling `~cursor.execute()` or use the + `connection.cursor()` *scrollable* parameter, otherwise the value + will have no effect. + + .. versionadded:: 2.5 + + .. extension:: + + The `scrollable` attribute is a Psycopg extension to the |DBAPI|. + + .. attribute:: withhold - + Read/write attribute: specifies if a named cursor lifetime should extend outside of the current transaction, i.e., it is possible to - fetch from the cursor even after a `commection.commit()` (but not after + fetch from the cursor even after a `connection.commit()` (but not after a `connection.rollback()`). See :ref:`server-side-cursors` + .. note:: + + set the value before calling `~cursor.execute()` or use the + `connection.cursor()` *withhold* parameter, otherwise the value + will have no effect. + .. versionadded:: 2.4.3 - + .. extension:: The `withhold` attribute is a Psycopg extension to the |DBAPI|. - - + + .. |execute*| replace:: `execute*()` .. _execute*: @@ -297,7 +333,8 @@ The ``cursor`` class not changed. The method can be used both for client-side cursors and - :ref:`server-side cursors `. + :ref:`server-side cursors `. Server-side cursors + can usually scroll backwards only if declared `~cursor.scrollable`. .. note:: @@ -527,10 +564,19 @@ The ``cursor`` class |COPY|__ command documentation). :param sql: the :sql:`COPY` statement to execute. - :param file: a file-like object; must be a readable file for - :sql:`COPY FROM` or an writable file for :sql:`COPY TO`. + :param file: a file-like object to read or write (according to *sql*). :param size: size of the read buffer to be used in :sql:`COPY FROM`. + The *sql* statement should be in the form :samp:`COPY {table} TO + STDOUT` to export :samp:`{table}` to the *file* object passed as + argument or :samp:`COPY {table} FROM STDIN` to import the content of + the *file* object into :samp:`{table}`. + + *file* must be a readable file-like object (as required by + `~cursor.copy_from()`) for *sql* statement :sql:`COPY ... FROM STDIN` + or a writable one (as required by `~cursor.copy_to()`) for :sql:`COPY + ... TO STDOUT`. + Example: >>> cur.copy_expert("COPY test TO STDOUT WITH CSV HEADER", sys.stdout) diff --git a/doc/src/errorcodes.rst b/doc/src/errorcodes.rst index f69dbe1e..bfaaeb45 100644 --- a/doc/src/errorcodes.rst +++ b/doc/src/errorcodes.rst @@ -50,7 +50,7 @@ An example of the available constants defined in the module: '42P01' Constants representing all the error values documented by PostgreSQL versions -between 8.1 and 9.1 are included in the module. +between 8.1 and 9.2 are included in the module. .. autofunction:: lookup(code) diff --git a/doc/src/extensions.rst b/doc/src/extensions.rst index b0a68ce4..9465789c 100644 --- a/doc/src/extensions.rst +++ b/doc/src/extensions.rst @@ -13,7 +13,7 @@ The module contains a few objects and function extending the minimum set of functionalities defined by the |DBAPI|_. -.. class:: connection +.. class:: connection(dsn, async=False) Is the class usually returned by the `~psycopg2.connect()` function. It is exposed by the `extensions` module in order to allow @@ -21,11 +21,9 @@ functionalities defined by the |DBAPI|_. `!connect()` function using the `connection_factory` parameter. See also :ref:`subclassing-connection`. - Subclasses should have constructor signature :samp:`({dsn}, {async}=0)`. - For a complete description of the class, see `connection`. -.. class:: cursor +.. class:: cursor(conn, name=None) It is the class usually returnded by the `connection.cursor()` method. It is exposed by the `extensions` module in order to allow @@ -139,6 +137,37 @@ functionalities defined by the |DBAPI|_. .. automethod:: from_string(s) +.. autoclass:: Diagnostics(exception) + + .. versionadded:: 2.5 + + The attributes currently available are: + + .. attribute:: + column_name + constraint_name + context + datatype_name + internal_position + internal_query + message_detail + message_hint + message_primary + schema_name + severity + source_file + source_function + source_line + sqlstate + statement_position + table_name + + A string with the error field if available; `!None` if not available. + The attribute value is available only if the error sent by the server: + not all the fields are available for all the errors and for all the + server versions. + + .. autofunction:: set_wait_callback(f) .. versionadded:: 2.2.0 diff --git a/doc/src/extras.rst b/doc/src/extras.rst index ce0e8315..2a82af8d 100644 --- a/doc/src/extras.rst +++ b/doc/src/extras.rst @@ -16,22 +16,27 @@ This module is a generic place used to hold little helper functions and classes until a better place in the distribution is found. -.. index:: - pair: Cursor; Dictionary - -.. _dict-cursor: - +.. _cursor-subclasses: Connection and cursor subclasses -------------------------------- A few objects that change the way the results are returned by the cursor or -modify the object behavior in some other way. Typically `!connection` -subclasses are passed as *connection_factory* argument to -`~psycopg2.connect()` so that the connection will generate the matching -`!cursor` subclass. Alternatively a `!cursor` subclass can be used one-off by -passing it as the *cursor_factory* argument to the `~connection.cursor()` -method of a regular `!connection`. +modify the object behavior in some other way. Typically `!cursor` subclasses +are passed as *cursor_factory* argument to `~psycopg2.connect()` so that the +connection's `~connection.cursor()` method will generate objects of this +class. Alternatively a `!cursor` subclass can be used one-off by passing it +as the *cursor_factory* argument to the `!cursor()` method. + +If you want to use a `!connection` subclass you can pass it as the +*connection_factory* argument of the `!connect()` function. + + +.. index:: + pair: Cursor; Dictionary + +.. _dict-cursor: + Dictionary-like cursor ^^^^^^^^^^^^^^^^^^^^^^ @@ -61,6 +66,11 @@ The records still support indexing as the original tuple: .. autoclass:: DictConnection + .. note:: + + Not very useful since Psycopg 2.5: you can use `psycopg2.connect`\ + ``(dsn, cursor_factory=DictCursor)`` instead of `!DictConnection`. + .. autoclass:: DictRow @@ -71,6 +81,12 @@ Real dictionary cursor .. autoclass:: RealDictConnection + .. note:: + + Not very useful since Psycopg 2.5: you can use `psycopg2.connect`\ + ``(dsn, cursor_factory=RealDictCursor)`` instead of + `!RealDictConnection`. + .. autoclass:: RealDictRow @@ -101,6 +117,12 @@ expect it to be... :: .. autoclass:: NamedTupleConnection + .. note:: + + Not very useful since Psycopg 2.5: you can use `psycopg2.connect`\ + ``(dsn, cursor_factory=NamedTupleCursor)`` instead of + `!NamedTupleConnection`. + .. index:: pair: Cursor; Logging @@ -128,12 +150,100 @@ Additional data types --------------------- -.. _adapt-hstore: +.. index:: + pair: JSON; Data types + pair: JSON; Adaptation + +.. _adapt-json: + +JSON_ adaptation +^^^^^^^^^^^^^^^^ + +.. versionadded:: 2.5 + +Psycopg can adapt Python objects to and from the PostgreSQL |pgjson|_ type. +With PostgreSQL 9.2 adaptation is available out-of-the-box. To use JSON data +with previous database versions (either with the `9.1 json extension`__, but +even if you want to convert text fields to JSON) you can use +`register_json()`. + +.. __: http://people.planetpostgresql.org/andrew/index.php?/archives/255-JSON-for-PG-9.2-...-and-now-for-9.1!.html + +The Python library used to convert Python objects to JSON depends on the +language version: with Python 2.6 and following the :py:mod:`json` module from +the standard library is used; with previous versions the `simplejson`_ module +is used if available. Note that the last `!simplejson` version supporting +Python 2.4 is the 2.0.9. + +.. _JSON: http://www.json.org/ +.. |pgjson| replace:: :sql:`json` +.. _pgjson: http://www.postgresql.org/docs/current/static/datatype-json.html +.. _simplejson: http://pypi.python.org/pypi/simplejson/ + +In order to pass a Python object to the database as query argument you can use +the `Json` adapter:: + + curs.execute("insert into mytable (jsondata) values (%s)", + [Json({'a': 100})]) + +Reading from the database, |pgjson| values will be automatically converted to +Python objects. + +.. note:: + + You can use `~psycopg2.extensions.register_adapter()` to adapt any Python + dictionary to JSON, either registering `Json` or any subclass or factory + creating a compatible adapter:: + + psycopg2.extensions.register_adapter(dict, psycopg2.extras.Json) + + This setting is global though, so it is not compatible with similar + adapters such as the one registered by `register_hstore()`. Any other + object supported by JSON can be registered the same way, but this will + clobber the default adaptation rule, so be careful to unwanted side + effects. + +If you want to customize the adaptation from Python to PostgreSQL you can +either provide a custom `!dumps()` function to `!Json`:: + + curs.execute("insert into mytable (jsondata) values (%s)", + [Json({'a': 100}, dumps=simplejson.dumps)]) + +or you can subclass it overriding the `~Json.dumps()` method:: + + class MyJson(Json): + def dumps(self, obj): + return simplejson.dumps(obj) + + curs.execute("insert into mytable (jsondata) values (%s)", + [MyJson({'a': 100})]) + +Customizing the conversion from PostgreSQL to Python can be done passing a +custom `!loads()` function to `register_json()` (or `register_default_json()` +for PostgreSQL 9.2). For example, if you want to convert the float values +from :sql:`json` into :py:class:`~decimal.Decimal` you can use:: + + loads = lambda x: json.loads(x, parse_float=Decimal) + psycopg2.extras.register_json(conn, loads=loads) + + + +.. autoclass:: Json + + .. automethod:: dumps + +.. autofunction:: register_json + +.. autofunction:: register_default_json + + .. index:: pair: hstore; Data types pair: dict; Adaptation +.. _adapt-hstore: + Hstore data type ^^^^^^^^^^^^^^^^ @@ -141,7 +251,7 @@ Hstore data type The |hstore|_ data type is a key-value store embedded in PostgreSQL. It has been available for several server versions but with the release 9.0 it has -been greatly improved in capacity and usefulness with the addiction of many +been greatly improved in capacity and usefulness with the addition of many functions. It supports GiST or GIN indexes allowing search by keys or key/value pairs as well as regular BTree indexes for equality, uniqueness etc. @@ -168,13 +278,13 @@ can be enabled using the `register_hstore()` function. -.. _adapt-composite: - .. index:: pair: Composite types; Data types pair: tuple; Adaptation pair: namedtuple; Adaptation +.. _adapt-composite: + Composite types casting ^^^^^^^^^^^^^^^^^^^^^^^ @@ -198,8 +308,8 @@ after a table row type) into a Python named tuple, or into a regular tuple if >>> cur.fetchone()[0] card(value=8, suit='hearts') -Nested composite types are handled as expected, but the type of the composite -components must be registered as well. +Nested composite types are handled as expected, provided that the type of the +composite components are registered as well. .. doctest:: @@ -214,15 +324,167 @@ components must be registered as well. Adaptation from Python tuples to composite types is automatic instead and requires no adapter registration. + +.. _custom-composite: + +.. Note:: + + If you want to convert PostgreSQL composite types into something different + than a `!namedtuple` you can subclass the `CompositeCaster` overriding + `~CompositeCaster.make()`. For example, if you want to convert your type + into a Python dictionary you can use:: + + >>> class DictComposite(psycopg2.extras.CompositeCaster): + ... def make(self, values): + ... return dict(zip(self.attnames, values)) + + >>> psycopg2.extras.register_composite('card', cur, + ... factory=DictComposite) + + >>> cur.execute("select (8, 'hearts')::card") + >>> cur.fetchone()[0] + {'suit': 'hearts', 'value': 8} + + .. autofunction:: register_composite + .. versionchanged:: 2.4.3 + added support for array of composite types + .. versionchanged:: 2.5 + added the *factory* parameter + + .. autoclass:: CompositeCaster + .. automethod:: make + + .. versionadded:: 2.5 + + Object attributes: + + .. attribute:: name + + The name of the PostgreSQL type. + + .. attribute:: schema + + The schema where the type is defined. + + .. versionadded:: 2.5 + + .. attribute:: oid + + The oid of the PostgreSQL type. + + .. attribute:: array_oid + + The oid of the PostgreSQL array type, if available. + + .. attribute:: type + + The type of the Python objects returned. If :py:func:`collections.namedtuple()` + is available, it is a named tuple with attributes equal to the type + components. Otherwise it is just the `!tuple` object. + + .. attribute:: attnames + + List of component names of the type to be casted. + + .. attribute:: atttypes + + List of component type oids of the type to be casted. + + +.. index:: + pair: range; Data types + +.. _adapt-range: + +Range data types +^^^^^^^^^^^^^^^^ + +.. versionadded:: 2.5 + +Psycopg offers a `Range` Python type and supports adaptation between them and +PostgreSQL |range|_ types. Builtin |range| types are supported out-of-the-box; +user-defined |range| types can be adapted using `register_range()`. + +.. |range| replace:: :sql:`range` +.. _range: http://www.postgresql.org/docs/current/static/rangetypes.html + +.. autoclass:: Range + + This Python type is only used to pass and retrieve range values to and + from PostgreSQL and doesn't attempt to replicate the PostgreSQL range + features: it doesn't perform normalization and doesn't implement all the + operators__ supported by the database. + + .. __: http://www.postgresql.org/docs/current/static/functions-range.html#RANGE-OPERATORS-TABLE + + `!Range` objects are immutable, hashable, and support the ``in`` operator + (checking if an element is within the range). They can be tested for + equivalence but not for ordering. Empty ranges evaluate to `!False` in + boolean context, nonempty evaluate to `!True`. + + Although it is possible to instantiate `!Range` objects, the class doesn't + have an adapter registered, so you cannot normally pass these instances as + query arguments. To use range objects as query arguments you can either + use one of the provided subclasses, such as `NumericRange` or create a + custom subclass using `register_range()`. + + Object attributes: + + .. autoattribute:: isempty + .. autoattribute:: lower + .. autoattribute:: upper + .. autoattribute:: lower_inc + .. autoattribute:: upper_inc + .. autoattribute:: lower_inf + .. autoattribute:: upper_inf + + +The following `Range` subclasses map builtin PostgreSQL |range| types to +Python objects: they have an adapter registered so their instances can be +passed as query arguments. |range| values read from database queries are +automatically casted into instances of these classes. + +.. autoclass:: NumericRange +.. autoclass:: DateRange +.. autoclass:: DateTimeRange +.. autoclass:: DateTimeTZRange + +Custom |range| types (created with |CREATE TYPE|_ :sql:`... AS RANGE`) can be +adapted to a custom `Range` subclass: + +.. autofunction:: register_range + +.. autoclass:: RangeCaster + + Object attributes: + + .. attribute:: range + + The `!Range` subclass adapted. + + .. attribute:: adapter + + The `~psycopg2.extensions.ISQLQuote` responsible to adapt `!range`. + + .. attribute:: typecaster + + The object responsible for casting. + + .. attribute:: array_typecaster + + The object responsible to cast arrays, if available, else `!None`. + .. index:: pair: UUID; Data types +.. _adapt-uuid: + UUID data type ^^^^^^^^^^^^^^ diff --git a/doc/src/faq.rst b/doc/src/faq.rst index 01527ca0..12aa99c5 100644 --- a/doc/src/faq.rst +++ b/doc/src/faq.rst @@ -7,6 +7,8 @@ Here are a few gotchas you may encounter using `psycopg2`. Feel free to suggest new entries! +.. _faq-transactions: + Problems with transactions handling ----------------------------------- @@ -51,6 +53,8 @@ Why do I get the error *current transaction is aborted, commands ignored until e informations. +.. _faq-types: + Problems with type conversions ------------------------------ @@ -151,6 +155,8 @@ Arrays of *TYPE* are not casted to list. provided in the `~psycopg2.extensions.new_array_type()` documentation. +.. _faq-best-practices: + Best practices -------------- @@ -191,6 +197,8 @@ What are the advantages or disadvantages of using named cursors? little memory on the client and to skip or discard parts of the result set. +.. _faq-compile: + Problems compiling and deploying psycopg2 ----------------------------------------- diff --git a/doc/src/index.rst b/doc/src/index.rst index e15e7bb7..595c2361 100644 --- a/doc/src/index.rst +++ b/doc/src/index.rst @@ -4,12 +4,12 @@ Psycopg -- PostgreSQL database adapter for Python .. sectionauthor:: Daniele Varrazzo -Psycopg_ is a PostgreSQL_ database adapter for the Python_ programming -language. Its main features are that it supports the full Python |DBAPI|_ -and it is thread safe (threads can share the connections). It was designed for -heavily multi-threaded applications that create and destroy lots of cursors and -make a large number of concurrent :sql:`INSERT`\ s or :sql:`UPDATE`\ s. -The Psycopg distribution includes ZPsycopgDA, a Zope_ Database Adapter. +Psycopg_ is the most popular PostgreSQL_ database adapter for the Python_ +programming language. Its main features are the complete implementation of +the Python |DBAPI|_ specification and the thread safety (several threads can +share the same connection). It was designed for heavily multi-threaded +applications that create and destroy lots of cursors and make a large number +of concurrent :sql:`INSERT`\s or :sql:`UPDATE`\s. Psycopg 2 is mostly implemented in C as a libpq_ wrapper, resulting in being both efficient and secure. It features client-side and :ref:`server-side @@ -18,12 +18,13 @@ both efficient and secure. It features client-side and :ref:`server-side support, and a flexible :ref:`objects adaptation system `. Many basic Python types are supported out-of-the-box and mapped to matching PostgreSQL data types, such as strings -(both bytes and Unicode), numbers (ints, longs, floats, decimals), booleans and -datetime objects (both built-in and `mx.DateTime`_), several types of -:ref:`binary objects `. Also available are mappings between lists -and PostgreSQL arrays of any supported type, between :ref:`dictionaries and -PostgreSQL hstores `, and between :ref:`tuples/namedtuples and -PostgreSQL composite types `. +(both byte strings and Unicode), numbers (ints, longs, floats, decimals), +booleans and date/time objects (both built-in and `mx.DateTime`_), several +types of :ref:`binary objects `. Also available are mappings +between lists and PostgreSQL arrays of any supported type, between +:ref:`dictionaries and PostgreSQL hstore `, between +:ref:`tuples/namedtuples and PostgreSQL composite types `, +and between Python objects and :ref:`JSON `. Psycopg 2 is both Unicode and Python 3 friendly. @@ -31,7 +32,6 @@ Psycopg 2 is both Unicode and Python 3 friendly. .. _Psycopg: http://initd.org/psycopg/ .. _PostgreSQL: http://www.postgresql.org/ .. _Python: http://www.python.org/ -.. _Zope: http://www.zope.org/ .. _libpq: http://www.postgresql.org/docs/current/static/libpq.html .. |COPY-TO-FROM| replace:: :sql:`COPY TO/COPY FROM` .. __: http://www.postgresql.org/docs/current/static/sql-copy.html @@ -42,6 +42,7 @@ Psycopg 2 is both Unicode and Python 3 friendly. .. toctree:: :maxdepth: 2 + install usage module connection @@ -53,6 +54,7 @@ Psycopg 2 is both Unicode and Python 3 friendly. extras errorcodes faq + news .. ifconfig:: builder != 'text' diff --git a/doc/src/install.rst b/doc/src/install.rst new file mode 100644 index 00000000..017be955 --- /dev/null +++ b/doc/src/install.rst @@ -0,0 +1,258 @@ +Introduction +============ + +.. sectionauthor:: Daniele Varrazzo + +Psycopg is a PostgreSQL_ adapter for the Python_ programming language. It is a +wrapper for the libpq_, the official PostgreSQL client library. + +The `psycopg2` package is the current mature implementation of the adapter: it +is a C extension and as such it is only compatible with CPython_. If you want +to use Psycopg on a different Python implementation (PyPy, Jython, IronPython) +there is an experimental `porting of Psycopg for Ctypes`__, but it is not as +mature as the C implementation yet. + +The current `!psycopg2` implementation supports: + +- Python 2 versions from 2.5 to 2.7 +- Python 3 versions from 3.1 to 3.3 +- PostgreSQL versions from 7.4 to 9.2 + +.. _PostgreSQL: http://www.postgresql.org/ +.. _Python: http://www.python.org/ +.. _libpq: http://www.postgresql.org/docs/current/static/libpq.html +.. _CPython: http://en.wikipedia.org/wiki/CPython +.. _Ctypes: http://docs.python.org/library/ctypes.html +.. __: https://github.com/mvantellingen/psycopg2-ctypes + + +.. note:: + + `!psycopg2` usually depends at runtime on the libpq dynamic library. + However it can connect to PostgreSQL servers of any supported version, + independently of the version of the libpq used: just install the most + recent libpq version or the most practical, without trying to match it to + the version of the PostgreSQL server you will have to connect to. + + +Installation +============ + +If possible, and usually it is, please :ref:`install Psycopg from a package +` available for your distribution or operating system. + +Compiling from source is a very easy task, however `!psycopg2` is a C +extension module and as such it requires a few more things in place respect to +a pure Python module. So, if you don't have experience compiling Python +extension packages, *above all if you are a Windows or a Mac OS user*, please +use a pre-compiled package and go straight to the :ref:`module usage ` +avoid bothering with the gory details. + + + +.. _install-from-package: + +Install from a package +---------------------- + +.. index:: + pair: Install; Linux + +**Linux** + Psycopg is available already packaged in many Linux distributions: look + for a package such as ``python-psycopg2`` using the package manager of + your choice. + + On Debian, Ubuntu and other deb-based distributions you should just need:: + + sudo apt-get install python-psycopg2 + + to install the package with all its dependencies. + + +.. index:: + pair: Install; Mac OS X + +**Mac OS X** + Psycopg is available as a `fink package`__ in the *unstable* tree: you may + install it with:: + + fink install psycopg2-py27 + + .. __: http://pdb.finkproject.org/pdb/package.php/psycopg2-py27 + + The library is also available on `MacPorts`__ try:: + + sudo port install py27-psycopg2 + + .. __: http://www.macports.org/ + + +.. index:: + pair: Install; Windows + +**Microsoft Windows** + Jason Erickson maintains a packaged `Windows port of Psycopg`__ with + installation executable. Download. Double click. Done. + + .. __: http://www.stickpeople.com/projects/python/win-psycopg/ + + + +.. index:: + single: Install; from source + +.. _install-from-source: + +Install from source +------------------- + +These notes illustrate how to compile Psycopg on Linux. If you want to compile +Psycopg on other platforms you may have to adjust some details accordingly. + +.. _requirements: + +Psycopg is a C wrapper to the libpq PostgreSQL client library. To install it +from sources you will need: + +- A C compiler. + +- The Python header files. They are usually installed in a package such as + **python-dev**. A message such as *error: Python.h: No such file or + directory* is an indication that the Python headers are missing. + +- The libpq header files. They are usually installed in a package such as + **libpq-dev**. If you get an *error: libpq-fe.h: No such file or directory* + you are missing them. + +- The :program:`pg_config` program: it is usually installed by the + **libpq-dev** package but sometimes it is not in a :envvar:`PATH` directory. + Having it in the :envvar:`PATH` greatly streamlines the installation, so try + running ``pg_config --version``: if it returns an error or an unexpected + version number then locate the directory containing the :program:`pg_config` + shipped with the right libpq version (usually + ``/usr/lib/postgresql/X.Y/bin/``) and add it to the :envvar:`PATH`:: + + $ export PATH=/usr/lib/postgresql/X.Y/bin/:$PATH + + You only need it to compile and install `!psycopg2`, not for its regular + usage. + +.. note:: + + The libpq header files used to compile `!psycopg2` should match the + version of the library linked at runtime. If you get errors about missing + or mismatching libraries when importing `!psycopg2` check (e.g. using + :program:`ldd`) if the module ``psycopg2/_psycopg.so`` is linked to the + right ``libpq.so``. + + + +.. index:: + single: Install; from PyPI + +.. _package-manager: + +Use a Python package manager +^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +If the above requirements are satisfied, you can use :program:`easy_install`, +:program:`pip` or whatever the Python package manager of the week:: + + $ pip install psycopg2 + +Please refer to your package manager documentation about performing a local or +global installation, :program:`virtualenv` (fully supported by recent Psycopg +versions), using different Python versions and other nuances. + + +.. index:: + single: setup.py + single: setup.cfg + +.. _source-package: + +Use the source package +^^^^^^^^^^^^^^^^^^^^^^ + +You can download a copy of Psycopg source files from the `Psycopg download +page`__. Once unpackaged, to compile and install the package you can run:: + + $ python setup.py build + $ sudo python setup.py install + +If you have less standard requirements such as: + +- creating a :ref:`debug build `, +- using :program:`pg_config` not in the :envvar:`PATH`, +- supporting ``mx.DateTime``, + +then take a look at the ``setup.cfg`` file. + +Some of the options available in ``setup.cfg`` are also available as command +line arguments of the ``build_ext`` sub-command. For instance you can specify +an alternate :program:`pg_config` version using:: + + $ python setup.py build_ext --pg-config /path/to/pg_config build + +Use ``python setup.py build_ext --help`` to get a list of the options +supported. + +.. __: http://initd.org/psycopg/download/ + + + +.. index:: + single: debug + single: PSYCOPG_DEBUG + +.. _debug-build: + +Creating a debug build +---------------------- + +In case of problems, Psycopg can be configured to emit detailed debug +messages, which can be very useful for diagnostics and to report a bug. In +order to create a debug package: + +- `Download`__ and unpack the Psycopg source package. + +- Edit the ``setup.cfg`` file adding the ``PSYCOPG_DEBUG`` flag to the + ``define`` option. + +- :ref:`Compile and install ` the package. + +- Set the :envvar:`PSYCOPG_DEBUG` variable:: + + $ export PSYCOPG_DEBUG=1 + +- Run your program (making sure that the `!psycopg2` package imported is the + one you just compiled and not e.g. the system one): you will have a copious + stream of informations printed on stdout. + +.. __: http://initd.org/psycopg/download/ + + + +.. _other-problems: + +If you still have problems +-------------------------- + +Try the following. *In order:* + +- Read again the :ref:`requirements `. + +- Read the :ref:`FAQ `. + +- Google for `!psycopg2` *your error message*. Especially useful the week + after the release of a new OS X version. + +- Write to the `Mailing List`__. + +- Complain on your blog or on Twitter that `!psycopg2` is the worst package + ever and about the quality time you have wasted figuring out the correct + :envvar:`ARCHFLAGS`. Especially useful from the Starbucks near you. + +.. __: http://mail.postgresql.org/mj/mj_wwwusr/domain=postgresql.org?func=lists-long-full&extra=psycopg + diff --git a/doc/src/module.rst b/doc/src/module.rst index 35292ba3..feaef516 100644 --- a/doc/src/module.rst +++ b/doc/src/module.rst @@ -7,7 +7,7 @@ The `psycopg2` module content The module interface respects the standard defined in the |DBAPI|_. -.. index:: +.. index:: single: Connection string double: Connection; Parameters single: Username; Connection @@ -16,11 +16,14 @@ The module interface respects the standard defined in the |DBAPI|_. single: Port; Connection single: DSN (Database Source Name) -.. function:: connect(dsn or params [, connection_factory] [, async=0]) +.. function:: + connect(dsn, connection_factory=None, cursor_factory=None, async=False) + connect(\*\*kwargs, connection_factory=None, cursor_factory=None, async=False) Create a new database session and return a new `connection` object. - The connection parameters can be specified either as a string:: + The connection parameters can be specified either as a `libpq connection + string`__ using the *dsn* parameter:: conn = psycopg2.connect("dbname=test user=postgres password=secret") @@ -28,9 +31,15 @@ The module interface respects the standard defined in the |DBAPI|_. conn = psycopg2.connect(database="test", user="postgres", password="secret") + The two call styles are mutually exclusive: you cannot specify connection + parameters as keyword arguments together with a connection string; only + the parameters not needed for the database connection (*i.e.* + *connection_factory*, *cursor_factory*, and *async*) are supported + together with the *dsn* argument. + The basic connection parameters are: - - `!dbname` -- the database name (only in dsn string) + - `!dbname` -- the database name (only in the *dsn* string) - `!database` -- the database name (only as keyword argument) - `!user` -- user name used to authenticate - `!password` -- password used to authenticate @@ -38,26 +47,45 @@ The module interface respects the standard defined in the |DBAPI|_. - `!port` -- connection port number (defaults to 5432 if not provided) 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`__. + be passed either in the connection string or as keywords. The PostgreSQL + documentation contains the complete list of the `supported parameters`__. Also note that the same parameters can be passed to the client library using `environment variables`__. - .. __: http://www.postgresql.org/docs/current/static/libpq-connect.html#LIBPQ-PQCONNECTDBPARAMS - .. __: http://www.postgresql.org/docs/current/static/libpq-envars.html + .. __: + .. _connstring: http://www.postgresql.org/docs/current/static/libpq-connect.html#LIBPQ-CONNSTRING + .. __: + .. _connparams: http://www.postgresql.org/docs/current/static/libpq-connect.html#LIBPQ-PARAMKEYWORDS + .. __: + .. _connenvvars: http://www.postgresql.org/docs/current/static/libpq-envars.html Using the *connection_factory* parameter a different class or connections factory can be specified. It should be a callable object - taking a *dsn* argument. See :ref:`subclassing-connection` for - details. + taking a *dsn* string argument. See :ref:`subclassing-connection` for + details. If a *cursor_factory* is specified, the connection's + `~connection.cursor_factory` is set to it. If you only need customized + cursors you can use this parameter instead of subclassing a connection. - Using *async*\=1 an asynchronous connection will be created: see + Using *async*\=\ `!True` 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. + .. versionchanged:: 2.5 + added the *cursor_factory* parameter. + + .. seealso:: + + - libpq `connection string syntax`__ + - libpq supported `connection parameters`__ + - libpq supported `environment variables`__ + + .. __: connstring_ + .. __: connparams_ + .. __: connenvvars_ + .. extension:: The parameters *connection_factory* and *async* are Psycopg extensions @@ -135,10 +163,27 @@ available through the following exceptions: The cursor the exception was raised from; `None` if not applicable. + .. attribute:: diag + + A `~psycopg2.extensions.Diagnostics` object containing further + information about the error. :: + + >>> try: + ... cur.execute("SELECT * FROM barf") + ... except Exception, e: + ... pass + + >>> e.diag.severity + 'ERROR' + >>> e.diag.message_primary + 'relation "barf" does not exist' + + .. versionadded:: 2.5 + .. extension:: - The `~Error.pgerror`, `~Error.pgcode`, and `~Error.cursor` attributes - are Psycopg extensions. + The `~Error.pgerror`, `~Error.pgcode`, `~Error.cursor`, and + `~Error.diag` attributes are Psycopg extensions. .. exception:: InterfaceError @@ -258,15 +303,15 @@ the type codes for date, time and timestamp columns; see the Implementation Hints below for details). The module exports the following constructors and singletons: - + .. function:: Date(year,month,day) This function constructs an object holding a date value. - + .. function:: Time(hour,minute,second) This function constructs an object holding a time value. - + .. function:: Timestamp(year,month,day,hour,minute,second) This function constructs an object holding a time stamp value. @@ -278,11 +323,11 @@ The module exports the following constructors and singletons: the standard Python time module for details). .. function:: TimeFromTicks(ticks) - + This function constructs an object holding a time value from the given ticks value (number of seconds since the epoch; see the documentation of the standard Python time module for details). - + .. function:: TimestampFromTicks(ticks) This function constructs an object holding a time stamp value from the @@ -290,10 +335,16 @@ The module exports the following constructors and singletons: documentation of the standard Python time module for details). .. function:: Binary(string) - + This function constructs an object capable of holding a binary (long) string value. - + +.. note:: + + All the adapters returned by the module level factories (`!Binary`, + `!Date`, `!Time`, `!Timestamp` and the `!*FromTicks` variants) expose the + wrapped object (a regular Python object such as `!datetime`) in an + `!adapted` attribute. .. data:: STRING @@ -304,17 +355,17 @@ The module exports the following constructors and singletons: This type object is used to describe (long) binary columns in a database (e.g. LONG, RAW, BLOBs). - + .. data:: NUMBER This type object is used to describe numeric columns in a database. .. data:: DATETIME - + This type object is used to describe date/time columns in a database. - + .. data:: ROWID - + This type object is used to describe the "Row ID" column in a database. diff --git a/doc/src/news.rst b/doc/src/news.rst new file mode 100644 index 00000000..d5b11a69 --- /dev/null +++ b/doc/src/news.rst @@ -0,0 +1,4 @@ +Release notes +============= + +.. include:: ../../NEWS diff --git a/doc/src/tools/lib/ticket_role.py b/doc/src/tools/lib/ticket_role.py new file mode 100644 index 00000000..f8ceea17 --- /dev/null +++ b/doc/src/tools/lib/ticket_role.py @@ -0,0 +1,39 @@ +# -*- coding: utf-8 -*- +""" + ticket role + ~~~~~~~~~~~ + + An interpreted text role to link docs to lighthouse issues. + + :copyright: Copyright 2013 by Daniele Varrazzo. +""" + +from docutils import nodes, utils +from docutils.parsers.rst import roles + +def ticket_role(name, rawtext, text, lineno, inliner, options={}, content=[]): + try: + num = int(text.replace('#', '')) + except ValueError: + msg = inliner.reporter.error( + "ticket number must be... a number, got '%s'" % text) + prb = inliner.problematic(rawtext, rawtext, msg) + return [prb], [msg] + + url_pattern = inliner.document.settings.env.app.config.ticket_url + if url_pattern is None: + msg = inliner.reporter.warning( + "ticket not configured: please configure ticket_url in conf.py") + prb = inliner.problematic(rawtext, rawtext, msg) + return [prb], [msg] + + url = url_pattern % num + roles.set_classes(options) + node = nodes.reference(rawtext, 'ticket ' + utils.unescape(text), + refuri=url, **options) + return [node], [] + +def setup(app): + app.add_config_value('ticket_url', None, 'env') + app.add_role('ticket', ticket_role) + diff --git a/doc/src/usage.rst b/doc/src/usage.rst index 1ab64197..f1f2a1a4 100644 --- a/doc/src/usage.rst +++ b/doc/src/usage.rst @@ -1,3 +1,5 @@ +.. _usage: + Basic module usage ================== @@ -49,7 +51,7 @@ The main entry points of Psycopg are: - create new `cursor`\s using the `~connection.cursor()` method to execute database commands and queries, - - terminate the session using the methods `~connection.commit()` or + - terminate transactions using the methods `~connection.commit()` or `~connection.rollback()`. - The class `cursor` allows interaction with the database: @@ -202,28 +204,88 @@ Adaptation of Python values to SQL types Many standard Python types are adapted into SQL and returned as Python objects when a query is executed. -If you need to convert other Python types to and from PostgreSQL data types, -see :ref:`adapting-new-types` and :ref:`type-casting-from-sql-to-python`. You -can also find a few other specialized adapters in the `psycopg2.extras` -module. +The following table shows the default mapping between Python and PostgreSQL +types: -In the following examples the method `~cursor.mogrify()` is used to show -the SQL string that would be sent to the database. +.. + TODO: The table is not rendered in text output + +.. only:: html + + .. table:: + :class: data-types + + +--------------------+-------------------------+--------------------------+ + | Python | PostgreSQL | See also | + +====================+=========================+==========================+ + | `!None` | :sql:`NULL` | :ref:`adapt-consts` | + +--------------------+-------------------------+ | + | `!bool` | :sql:`bool` | | + +--------------------+-------------------------+--------------------------+ + | `!float` | | :sql:`real` | :ref:`adapt-numbers` | + | | | :sql:`double` | | + +--------------------+-------------------------+ | + | | `!int` | | :sql:`smallint` | | + | | `!long` | | :sql:`integer` | | + | | | :sql:`bigint` | | + +--------------------+-------------------------+ | + | `~decimal.Decimal` | :sql:`numeric` | | + +--------------------+-------------------------+--------------------------+ + | | `!str` | | :sql:`varchar` | :ref:`adapt-string` | + | | `!unicode` | | :sql:`text` | | + +--------------------+-------------------------+--------------------------+ + | | `buffer` | :sql:`bytea` | :ref:`adapt-binary` | + | | `memoryview` | | | + | | `bytearray` | | | + | | `bytes` | | | + | | Buffer protocol | | | + +--------------------+-------------------------+--------------------------+ + | `!date` | :sql:`date` | :ref:`adapt-date` | + +--------------------+-------------------------+ | + | `!time` | :sql:`time` | | + +--------------------+-------------------------+ | + | `!datetime` | | :sql:`timestamp` | | + | | | :sql:`timestamptz` | | + +--------------------+-------------------------+ | + | `!timedelta` | :sql:`interval` | | + +--------------------+-------------------------+--------------------------+ + | `!list` | :sql:`ARRAY` | :ref:`adapt-list` | + +--------------------+-------------------------+--------------------------+ + | | `!tuple` | | Composite types | | :ref:`adapt-tuple` | + | | `!namedtuple` | | :sql:`IN` syntax | | :ref:`adapt-composite` | + +--------------------+-------------------------+--------------------------+ + | `!dict` | :sql:`hstore` | :ref:`adapt-hstore` | + +--------------------+-------------------------+--------------------------+ + | Psycopg's `!Range` | :sql:`range` | :ref:`adapt-range` | + +--------------------+-------------------------+--------------------------+ + | Anything\ |tm| | :sql:`json` | :ref:`adapt-json` | + +--------------------+-------------------------+--------------------------+ + | `uuid` | :sql:`uuid` | :ref:`adapt-uuid` | + +--------------------+-------------------------+--------------------------+ + +.. |tm| unicode:: U+2122 + +The mapping is fairly customizable: see :ref:`adapting-new-types` and +:ref:`type-casting-from-sql-to-python`. You can also find a few other +specialized adapters in the `psycopg2.extras` module. -.. _adapt-consts: .. index:: pair: None; Adaptation single: NULL; Adaptation pair: Boolean; Adaptation -- Python `None` and boolean values `True` and `False` are converted into the - proper SQL literals:: +.. _adapt-consts: + +Constants adaptation +^^^^^^^^^^^^^^^^^^^^ + +Python `None` and boolean values `True` and `False` are converted into the +proper SQL literals:: >>> cur.mogrify("SELECT %s, %s, %s;", (None, True, False)) - >>> 'SELECT NULL, true, false;' + 'SELECT NULL, true, false;' -.. _adapt-numbers: .. index:: single: Adaptation; numbers @@ -231,168 +293,48 @@ the SQL string that would be sent to the database. single: Float; Adaptation single: Decimal; Adaptation -- Numeric objects: `int`, `long`, `float`, `~decimal.Decimal` are converted in - the PostgreSQL numerical representation:: +.. _adapt-numbers: + +Numbers adaptation +^^^^^^^^^^^^^^^^^^ + +Numeric objects: `int`, `long`, `float`, `~decimal.Decimal` are converted in +the PostgreSQL numerical representation:: >>> cur.mogrify("SELECT %s, %s, %s, %s;", (10, 10L, 10.0, Decimal("10.00"))) - >>> 'SELECT 10, 10, 10.0, 10.00;' + 'SELECT 10, 10, 10.0, 10.00;' + +Reading from the database, integer types are converted into `!int`, floating +point types are converted into `!float`, :sql:`numeric`\/\ :sql:`decimal` are +converted into `!Decimal`. + +.. note:: + + Sometimes you may prefer to receive :sql:`numeric` data as `!float` + insted, for performance reason or ease of manipulation: you can configure + an adapter to :ref:`cast PostgreSQL numeric to Python float `. + This of course may imply a loss of precision. + +.. seealso:: `PostgreSQL numeric types + `__ -.. _adapt-string: .. index:: pair: Strings; Adaptation single: Unicode; Adaptation -- String types: `str`, `unicode` are converted in SQL string syntax. - `!unicode` objects (`!str` in Python 3) are encoded in the connection - `~connection.encoding` to be sent to the backend: trying to send a character - not supported by the encoding will result in an error. Received data can be - converted either as `!str` or `!unicode`: see :ref:`unicode-handling`. +.. _adapt-string: -.. _adapt-binary: +Strings adaptation +^^^^^^^^^^^^^^^^^^ -.. index:: - single: Buffer; Adaptation - single: bytea; Adaptation - single: bytes; Adaptation - single: bytearray; Adaptation - single: memoryview; Adaptation - single: Binary string - -- Binary types: Python types representing binary objects are converted into - PostgreSQL binary string syntax, suitable for :sql:`bytea` fields. Such - types are `buffer` (only available in Python 2), `memoryview` (available - from Python 2.7), `bytearray` (available from Python 2.6) and `bytes` - (only from Python 3: the name is available from Python 2.6 but it's only an - alias for the type `!str`). Any object implementing the `Revised Buffer - Protocol`__ should be usable as binary type where the protocol is supported - (i.e. from Python 2.6). Received data is returned as `!buffer` (in Python 2) - or `!memoryview` (in Python 3). - - .. __: http://www.python.org/dev/peps/pep-3118/ - - .. versionchanged:: 2.4 - only strings were supported before. - - .. versionchanged:: 2.4.1 - can parse the 'hex' format from 9.0 servers without relying on the - version of the client library. - - .. note:: - - In Python 2, if you have binary data in a `!str` object, you can pass them - to a :sql:`bytea` field using the `psycopg2.Binary` wrapper:: - - mypic = open('picture.png', 'rb').read() - curs.execute("insert into blobs (file) values (%s)", - (psycopg2.Binary(mypic),)) - - .. warning:: - - Since version 9.0 PostgreSQL uses by default `a new "hex" format`__ to - emit :sql:`bytea` fields. Starting from Psycopg 2.4.1 the format is - correctly supported. If you use a previous version you will need some - extra care when receiving bytea from PostgreSQL: you must have at least - libpq 9.0 installed on the client or alternatively you can set the - `bytea_output`__ configuration parameter to ``escape``, either in the - server configuration file or in the client session (using a query such as - ``SET bytea_output TO escape;``) before receiving binary data. - - .. __: http://www.postgresql.org/docs/current/static/datatype-binary.html - .. __: http://www.postgresql.org/docs/current/static/runtime-config-client.html#GUC-BYTEA-OUTPUT - -.. _adapt-date: - -.. index:: - single: Adaptation; Date/Time objects - single: Date objects; Adaptation - single: Time objects; Adaptation - single: Interval objects; Adaptation - single: mx.DateTime; Adaptation - -- Date and time objects: builtin `~datetime.datetime`, `~datetime.date`, - `~datetime.time`, `~datetime.timedelta` are converted into PostgreSQL's - :sql:`timestamp`, :sql:`date`, :sql:`time`, :sql:`interval` data types. - Time zones are supported too. The Egenix `mx.DateTime`_ objects are adapted - the same way:: - - >>> dt = datetime.datetime.now() - >>> dt - datetime.datetime(2010, 2, 8, 1, 40, 27, 425337) - - >>> cur.mogrify("SELECT %s, %s, %s;", (dt, dt.date(), dt.time())) - "SELECT '2010-02-08T01:40:27.425337', '2010-02-08', '01:40:27.425337';" - - >>> cur.mogrify("SELECT %s;", (dt - datetime.datetime(2010,1,1),)) - "SELECT '38 days 6027.425337 seconds';" - -.. _adapt-list: - -.. index:: - single: Array; Adaptation - double: Lists; Adaptation - -- Python lists are converted into PostgreSQL :sql:`ARRAY`\ s:: - - >>> cur.mogrify("SELECT %s;", ([10, 20, 30], )) - 'SELECT ARRAY[10, 20, 30];' - - .. note:: - - Reading back from PostgreSQL, arrays are converted to list of Python - objects as expected, but only if the types are known one. Arrays of - unknown types are returned as represented by the database (e.g. - ``{a,b,c}``). You can easily create a typecaster for :ref:`array of - unknown types `. - -.. _adapt-tuple: - -.. index:: - double: Tuple; Adaptation - single: IN operator - -- Python tuples are converted in a syntax suitable for the SQL :sql:`IN` - operator and to represent a composite type:: - - >>> cur.mogrify("SELECT %s IN %s;", (10, (10, 20, 30))) - 'SELECT 10 IN (10, 20, 30);' - - .. note:: - - SQL doesn't allow an empty list in the IN operator, so your code should - guard against empty tuples. - - If you want PostgreSQL composite types to be converted into a Python - tuple/namedtuple you can use the `~psycopg2.extras.register_composite()` - function. - - .. versionadded:: 2.0.6 - the tuple :sql:`IN` adaptation. - - .. versionchanged:: 2.0.14 - the tuple :sql:`IN` adapter is always active. In previous releases it - was necessary to import the `~psycopg2.extensions` module to have it - registered. - - .. versionchanged:: 2.3 - `~collections.namedtuple` instances are adapted like regular tuples and - can thus be used to represent composite types. - -.. _adapt-dict: - -.. index:: - single: dict; Adaptation - single: hstore; Adaptation - -- Python dictionaries are converted into the |hstore|_ data type. By default - the adapter is not enabled: see `~psycopg2.extras.register_hstore()` for - further details. - - .. |hstore| replace:: :sql:`hstore` - .. _hstore: http://www.postgresql.org/docs/current/static/hstore.html - - .. versionadded:: 2.3 - the :sql:`hstore` adaptation. +Python `str` and `unicode` are converted into the SQL string syntax. +`!unicode` objects (`!str` in Python 3) are encoded in the connection +`~connection.encoding` before sending to the backend: trying to send a +character not supported by the encoding will result in an error. Data is +usually received as `!str` (*i.e.* it is *decoded* on Python 3, left *encoded* +on Python 2). However it is possible to receive `!unicode` on Python 2 too: +see :ref:`unicode-handling`. .. index:: @@ -401,7 +343,7 @@ the SQL string that would be sent to the database. .. _unicode-handling: Unicode handling -^^^^^^^^^^^^^^^^ +'''''''''''''''' Psycopg can exchange Unicode data with a PostgreSQL database. Python `!unicode` objects are automatically *encoded* in the client encoding @@ -464,20 +406,108 @@ the connection or globally: see the function psycopg2.extensions.register_type(psycopg2.extensions.UNICODE) psycopg2.extensions.register_type(psycopg2.extensions.UNICODEARRAY) - and then forget about this story. + and forget about this story. +.. index:: + single: Buffer; Adaptation + single: bytea; Adaptation + single: bytes; Adaptation + single: bytearray; Adaptation + single: memoryview; Adaptation + single: Binary string + +.. _adapt-binary: + +Binary adaptation +^^^^^^^^^^^^^^^^^ + +Binary types: Python types representing binary objects are converted into +PostgreSQL binary string syntax, suitable for :sql:`bytea` fields. Such +types are `buffer` (only available in Python 2), `memoryview` (available +from Python 2.7), `bytearray` (available from Python 2.6) and `bytes` +(only from Python 3: the name is available from Python 2.6 but it's only an +alias for the type `!str`). Any object implementing the `Revised Buffer +Protocol`__ should be usable as binary type where the protocol is supported +(i.e. from Python 2.6). Received data is returned as `!buffer` (in Python 2) +or `!memoryview` (in Python 3). + +.. __: http://www.python.org/dev/peps/pep-3118/ + +.. versionchanged:: 2.4 + only strings were supported before. + +.. versionchanged:: 2.4.1 + can parse the 'hex' format from 9.0 servers without relying on the + version of the client library. + +.. note:: + + In Python 2, if you have binary data in a `!str` object, you can pass them + to a :sql:`bytea` field using the `psycopg2.Binary` wrapper:: + + mypic = open('picture.png', 'rb').read() + curs.execute("insert into blobs (file) values (%s)", + (psycopg2.Binary(mypic),)) + +.. warning:: + + Since version 9.0 PostgreSQL uses by default `a new "hex" format`__ to + emit :sql:`bytea` fields. Starting from Psycopg 2.4.1 the format is + correctly supported. If you use a previous version you will need some + extra care when receiving bytea from PostgreSQL: you must have at least + libpq 9.0 installed on the client or alternatively you can set the + `bytea_output`__ configuration parameter to ``escape``, either in the + server configuration file or in the client session (using a query such as + ``SET bytea_output TO escape;``) before receiving binary data. + + .. __: http://www.postgresql.org/docs/current/static/datatype-binary.html + .. __: http://www.postgresql.org/docs/current/static/runtime-config-client.html#GUC-BYTEA-OUTPUT + + +.. index:: + single: Adaptation; Date/Time objects + single: Date objects; Adaptation + single: Time objects; Adaptation + single: Interval objects; Adaptation + single: mx.DateTime; Adaptation + +.. _adapt-date: + +Date/Time objects adaptation +^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Date and time objects: builtin `~datetime.datetime`, `~datetime.date`, +`~datetime.time`, `~datetime.timedelta` are converted into PostgreSQL's +:sql:`timestamp[tz]`, :sql:`date`, :sql:`time`, :sql:`interval` data types. +Time zones are supported too. The Egenix `mx.DateTime`_ objects are adapted +the same way:: + + >>> dt = datetime.datetime.now() + >>> dt + datetime.datetime(2010, 2, 8, 1, 40, 27, 425337) + + >>> cur.mogrify("SELECT %s, %s, %s;", (dt, dt.date(), dt.time())) + "SELECT '2010-02-08T01:40:27.425337', '2010-02-08', '01:40:27.425337';" + + >>> cur.mogrify("SELECT %s;", (dt - datetime.datetime(2010,1,1),)) + "SELECT '38 days 6027.425337 seconds';" + +.. seealso:: `PostgreSQL date/time types + `__ + .. index:: single: Time Zones .. _tz-handling: Time zones handling -^^^^^^^^^^^^^^^^^^^ +''''''''''''''''''' -The PostgreSQL type :sql:`timestamp with time zone` is converted into Python -`~datetime.datetime` objects with a `~datetime.datetime.tzinfo` attribute set -to a `~psycopg2.tz.FixedOffsetTimezone` instance. +The PostgreSQL type :sql:`timestamp with time zone` (a.k.a. +:sql:`timestamptz`) is converted into Python `~datetime.datetime` objects with +a `~datetime.datetime.tzinfo` attribute set to a +`~psycopg2.tz.FixedOffsetTimezone` instance. >>> cur.execute("SET TIME ZONE 'Europe/Rome';") # UTC + 1 hour >>> cur.execute("SELECT '2010-01-01 10:30:45'::timestamptz;") @@ -500,6 +530,81 @@ rounded to the nearest minute, with an error of up to 30 seconds. versions use `psycopg2.extras.register_tstz_w_secs()`. +.. _adapt-list: + +Lists adaptation +^^^^^^^^^^^^^^^^ + +.. index:: + single: Array; Adaptation + double: Lists; Adaptation + +Python lists are converted into PostgreSQL :sql:`ARRAY`\ s:: + + >>> cur.mogrify("SELECT %s;", ([10, 20, 30], )) + 'SELECT ARRAY[10,20,30];' + +.. note:: + + You can use a Python list as the argument of the :sql:`IN` operator using + `the PostgreSQL ANY operator`__. :: + + ids = [10, 20, 30] + cur.execute("SELECT * FROM data WHERE id = ANY(%s);", (ids,)) + + Furthermore :sql:`ANY` can also work with empty lists, whereas :sql:`IN ()` + is a SQL syntax error. + + .. __: http://www.postgresql.org/docs/current/static/functions-subquery.html#FUNCTIONS-SUBQUERY-ANY-SOME + +.. note:: + + Reading back from PostgreSQL, arrays are converted to lists of Python + objects as expected, but only if the items are of a known known type. + Arrays of unknown types are returned as represented by the database (e.g. + ``{a,b,c}``). If you want to convert the items into Python objects you can + easily create a typecaster for :ref:`array of unknown types + `. + + +.. _adapt-tuple: + +Tuples adaptation +^^^^^^^^^^^^^^^^^^ + +.. index:: + double: Tuple; Adaptation + single: IN operator + +Python tuples are converted in a syntax suitable for the SQL :sql:`IN` +operator and to represent a composite type:: + + >>> cur.mogrify("SELECT %s IN %s;", (10, (10, 20, 30))) + 'SELECT 10 IN (10, 20, 30);' + +.. note:: + + SQL doesn't allow an empty list in the :sql:`IN` operator, so your code + should guard against empty tuples. Alternatively you can :ref:`use a + Python list `. + +If you want PostgreSQL composite types to be converted into a Python +tuple/namedtuple you can use the `~psycopg2.extras.register_composite()` +function. + +.. versionadded:: 2.0.6 + the tuple :sql:`IN` adaptation. + +.. versionchanged:: 2.0.14 + the tuple :sql:`IN` adapter is always active. In previous releases it + was necessary to import the `~psycopg2.extensions` module to have it + registered. + +.. versionchanged:: 2.3 + `~collections.namedtuple` instances are adapted like regular tuples and + can thus be used to represent composite types. + + .. index:: Transaction, Begin, Commit, Rollback, Autocommit, Read only .. _transactions-control: @@ -527,7 +632,7 @@ It is possible to set the connection in *autocommit* mode: this way all the commands executed will be immediately committed and no rollback is possible. A few commands (e.g. :sql:`CREATE DATABASE`, :sql:`VACUUM`...) require to be run outside any transaction: in order to be able to run these commands from -Psycopg, the session must be in autocommit mode: you can use the +Psycopg, the connection must be in autocommit mode: you can use the `~connection.autocommit` property (`~connection.set_isolation_level()` in older versions). @@ -546,6 +651,30 @@ change the isolation level. See the `~connection.set_session()` method for all the details. +.. index:: + single: with statement + +``with`` statement +^^^^^^^^^^^^^^^^^^ + +Starting from version 2.5, psycopg2's connections and cursors are *context +managers* and can be used with the ``with`` statement:: + + with psycopg2.connect(DSN) as conn: + with conn.cursor() as curs: + curs.execute(SQL) + +When a connection exits the ``with`` block, if no exception has been raised by +the block, the transaction is committed. In case of exception the transaction +is rolled back. In no case the connection is closed: a connection can be used +in more than a ``with`` statement and each ``with`` block is effectively +wrapped in a transaction. + +When a cursor exits the ``with`` block it is closed, releasing any resource +eventually associated with it. The state of the transaction is not affected. + + + .. index:: pair: Server side; Cursor pair: Named; Cursor @@ -576,7 +705,9 @@ cursor is created using the `~connection.cursor()` method specifying the *name* parameter. Such cursor will behave mostly like a regular cursor, allowing the user to move in the dataset using the `~cursor.scroll()` method and to read the data using `~cursor.fetchone()` and -`~cursor.fetchmany()` methods. +`~cursor.fetchmany()` methods. Normally you can only scroll forward in a +cursor: if you need to scroll backwards you should declare your cursor +`~cursor.scrollable`. Named cursors are also :ref:`iterable ` like regular cursors. Note however that before Psycopg 2.4 iteration was performed fetching one diff --git a/lib/__init__.py b/lib/__init__.py index 892d8038..cf8c06ae 100644 --- a/lib/__init__.py +++ b/lib/__init__.py @@ -41,23 +41,6 @@ Homepage: http://initd.org/projects/psycopg2 # Import modules needed by _psycopg to allow tools like py2exe to do # their work without bothering about the module dependencies. -import sys, warnings -if sys.version_info >= (2, 3): - try: - import datetime as _psycopg_needs_datetime - except: - warnings.warn( - "can't import datetime module probably needed by _psycopg", - RuntimeWarning) -if sys.version_info >= (2, 4): - try: - import decimal as _psycopg_needs_decimal - except: - warnings.warn( - "can't import decimal module probably needed by _psycopg", - RuntimeWarning) -del sys, warnings - # Note: the first internal import should be _psycopg, otherwise the real cause # of a failed loading of the C module may get hidden, see # http://archives.postgresql.org/psycopg/2011-02/msg00044.php @@ -118,7 +101,7 @@ del re def connect(dsn=None, database=None, user=None, password=None, host=None, port=None, - connection_factory=None, async=False, **kwargs): + connection_factory=None, cursor_factory=None, async=False, **kwargs): """ Create a new database connection. @@ -143,6 +126,9 @@ def connect(dsn=None, factory can be specified. It should be a callable object taking a dsn argument. + Using the *cursor_factory* parameter, a new default cursor factory will be + used by cursor(). + Using *async*=True an asynchronous connection will be created. Any other keyword parameter will be passed to the underlying client @@ -175,8 +161,8 @@ def connect(dsn=None, dsn = " ".join(["%s=%s" % (k, _param_escape(str(v))) for (k, v) in items]) - return _connect(dsn, connection_factory=connection_factory, async=async) - - -__all__ = filter(lambda k: not k.startswith('_'), locals().keys()) + conn = _connect(dsn, connection_factory=connection_factory, async=async) + if cursor_factory is not None: + conn.cursor_factory = cursor_factory + return conn diff --git a/lib/_json.py b/lib/_json.py new file mode 100644 index 00000000..536dd58b --- /dev/null +++ b/lib/_json.py @@ -0,0 +1,194 @@ +"""Implementation of the JSON adaptation objects + +This module exists to avoid a circular import problem: pyscopg2.extras depends +on psycopg2.extension, so I can't create the default JSON typecasters in +extensions importing register_json from extras. +""" + +# psycopg/_json.py - Implementation of the JSON adaptation objects +# +# Copyright (C) 2012 Daniele Varrazzo +# +# psycopg2 is free software: you can redistribute it and/or modify it +# under the terms of the GNU Lesser General Public License as published +# by the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# In addition, as a special exception, the copyright holders give +# permission to link this program with the OpenSSL library (or with +# modified versions of OpenSSL that use the same license as OpenSSL), +# and distribute linked combinations including the two. +# +# You must obey the GNU Lesser General Public License in all respects for +# all of the code used other than OpenSSL. +# +# psycopg2 is distributed in the hope that it will be useful, but WITHOUT +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +# FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public +# License for more details. + +import sys + +from psycopg2._psycopg import ISQLQuote, QuotedString +from psycopg2._psycopg import new_type, new_array_type, register_type + + +# import the best json implementation available +if sys.version_info[:2] >= (2,6): + import json +else: + try: + import simplejson as json + except ImportError: + json = None + + +# oids from PostgreSQL 9.2 +JSON_OID = 114 +JSONARRAY_OID = 199 + +class Json(object): + """ + An `~psycopg2.extensions.ISQLQuote` wrapper to adapt a Python object to + :sql:`json` data type. + + `!Json` can be used to wrap any object supported by the provided *dumps* + function. If none is provided, the standard :py:func:`json.dumps()` is + used (`!simplejson` for Python < 2.6; + `~psycopg2.extensions.ISQLQuote.getquoted()` will raise `!ImportError` if + the module is not available). + + """ + def __init__(self, adapted, dumps=None): + self.adapted = adapted + + if dumps is not None: + self._dumps = dumps + elif json is not None: + self._dumps = json.dumps + else: + self._dumps = None + + def __conform__(self, proto): + if proto is ISQLQuote: + return self + + def dumps(self, obj): + """Serialize *obj* in JSON format. + + The default is to call `!json.dumps()` or the *dumps* function + provided in the constructor. You can override this method to create a + customized JSON wrapper. + """ + dumps = self._dumps + if dumps is not None: + return dumps(obj) + else: + raise ImportError( + "json module not available: " + "you should provide a dumps function") + + def getquoted(self): + s = self.dumps(self.adapted) + return QuotedString(s).getquoted() + + +def register_json(conn_or_curs=None, globally=False, loads=None, + oid=None, array_oid=None): + """Create and register typecasters converting :sql:`json` type to Python objects. + + :param conn_or_curs: a connection or cursor used to find the :sql:`json` + and :sql:`json[]` oids; the typecasters are registered in a scope + limited to this object, unless *globally* is set to `!True`. It can be + `!None` if the oids are provided + :param globally: if `!False` register the typecasters only on + *conn_or_curs*, otherwise register them globally + :param loads: the function used to parse the data into a Python object. If + `!None` use `!json.loads()`, where `!json` is the module chosen + according to the Python version (see above) + :param oid: the OID of the :sql:`json` type if known; If not, it will be + queried on *conn_or_curs* + :param array_oid: the OID of the :sql:`json[]` array type if known; + if not, it will be queried on *conn_or_curs* + + The connection or cursor passed to the function will be used to query the + database and look for the OID of the :sql:`json` type. No query is + performed if *oid* and *array_oid* are provided. Raise + `~psycopg2.ProgrammingError` if the type is not found. + + """ + if oid is None: + oid, array_oid = _get_json_oids(conn_or_curs) + + JSON, JSONARRAY = _create_json_typecasters(oid, array_oid, loads) + + register_type(JSON, not globally and conn_or_curs or None) + + if JSONARRAY is not None: + register_type(JSONARRAY, not globally and conn_or_curs or None) + + return JSON, JSONARRAY + +def register_default_json(conn_or_curs=None, globally=False, loads=None): + """ + Create and register :sql:`json` typecasters for PostgreSQL 9.2 and following. + + Since PostgreSQL 9.2 :sql:`json` is a builtin type, hence its oid is known + and fixed. This function allows specifying a customized *loads* function + for the default :sql:`json` type without querying the database. + All the parameters have the same meaning of `register_json()`. + """ + return register_json(conn_or_curs=conn_or_curs, globally=globally, + loads=loads, oid=JSON_OID, array_oid=JSONARRAY_OID) + +def _create_json_typecasters(oid, array_oid, loads=None): + """Create typecasters for json data type.""" + if loads is None: + if json is None: + raise ImportError("no json module available") + else: + loads = json.loads + + def typecast_json(s, cur): + if s is None: + return None + return loads(s) + + JSON = new_type((oid, ), 'JSON', typecast_json) + if array_oid is not None: + JSONARRAY = new_array_type((array_oid, ), "JSONARRAY", JSON) + else: + JSONARRAY = None + + return JSON, JSONARRAY + +def _get_json_oids(conn_or_curs): + # lazy imports + from psycopg2.extensions import STATUS_IN_TRANSACTION + from psycopg2.extras import _solve_conn_curs + + conn, curs = _solve_conn_curs(conn_or_curs) + + # Store the transaction status of the connection to revert it after use + conn_status = conn.status + + # column typarray not available before PG 8.3 + typarray = conn.server_version >= 80300 and "typarray" or "NULL" + + # get the oid for the hstore + curs.execute( + "SELECT t.oid, %s FROM pg_type t WHERE t.typname = 'json';" + % typarray) + r = curs.fetchone() + + # revert the status of the connection as before the command + if (conn_status != STATUS_IN_TRANSACTION and not conn.autocommit): + conn.rollback() + + if not r: + raise conn.ProgrammingError("json data type not found") + + return r + + + diff --git a/lib/_range.py b/lib/_range.py new file mode 100644 index 00000000..25600d84 --- /dev/null +++ b/lib/_range.py @@ -0,0 +1,468 @@ +"""Implementation of the Range type and adaptation + +""" + +# psycopg/_range.py - Implementation of the Range type and adaptation +# +# Copyright (C) 2012 Daniele Varrazzo +# +# psycopg2 is free software: you can redistribute it and/or modify it +# under the terms of the GNU Lesser General Public License as published +# by the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# In addition, as a special exception, the copyright holders give +# permission to link this program with the OpenSSL library (or with +# modified versions of OpenSSL that use the same license as OpenSSL), +# and distribute linked combinations including the two. +# +# You must obey the GNU Lesser General Public License in all respects for +# all of the code used other than OpenSSL. +# +# psycopg2 is distributed in the hope that it will be useful, but WITHOUT +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +# FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public +# License for more details. + +import re + +from psycopg2._psycopg import ProgrammingError, InterfaceError +from psycopg2.extensions import ISQLQuote, adapt, register_adapter, b +from psycopg2.extensions import new_type, new_array_type, register_type + +class Range(object): + """Python representation for a PostgreSQL |range|_ type. + + :param lower: lower bound for the range. `!None` means unbound + :param upper: upper bound for the range. `!None` means unbound + :param bounds: one of the literal strings ``()``, ``[)``, ``(]``, ``[]``, + representing whether the lower or upper bounds are included + :param empty: if `!True`, the range is empty + + """ + __slots__ = ('_lower', '_upper', '_bounds') + + def __init__(self, lower=None, upper=None, bounds='[)', empty=False): + if not empty: + if bounds not in ('[)', '(]', '()', '[]'): + raise ValueError("bound flags not valid: %r" % bounds) + + self._lower = lower + self._upper = upper + self._bounds = bounds + else: + self._lower = self._upper = self._bounds = None + + def __repr__(self): + if self._bounds is None: + return "%s(empty=True)" % self.__class__.__name__ + else: + return "%s(%r, %r, %r)" % (self.__class__.__name__, + self._lower, self._upper, self._bounds) + + @property + def lower(self): + """The lower bound of the range. `!None` if empty or unbound.""" + return self._lower + + @property + def upper(self): + """The upper bound of the range. `!None` if empty or unbound.""" + return self._upper + + @property + def isempty(self): + """`!True` if the range is empty.""" + return self._bounds is None + + @property + def lower_inf(self): + """`!True` if the range doesn't have a lower bound.""" + if self._bounds is None: return False + return self._lower is None + + @property + def upper_inf(self): + """`!True` if the range doesn't have an upper bound.""" + if self._bounds is None: return False + return self._upper is None + + @property + def lower_inc(self): + """`!True` if the lower bound is included in the range.""" + if self._bounds is None: return False + if self._lower is None: return False + return self._bounds[0] == '[' + + @property + def upper_inc(self): + """`!True` if the upper bound is included in the range.""" + if self._bounds is None: return False + if self._upper is None: return False + return self._bounds[1] == ']' + + def __contains__(self, x): + if self._bounds is None: return False + if self._lower is not None: + if self._bounds[0] == '[': + if x < self._lower: return False + else: + if x <= self._lower: return False + + if self._upper is not None: + if self._bounds[1] == ']': + if x > self._upper: return False + else: + if x >= self._upper: return False + + return True + + def __nonzero__(self): + return self._bounds is not None + + def __eq__(self, other): + return (self._lower == other._lower + and self._upper == other._upper + and self._bounds == other._bounds) + + def __ne__(self, other): + return not self.__eq__(other) + + def __hash__(self): + return hash((self._lower, self._upper, self._bounds)) + + def __lt__(self, other): + raise TypeError( + 'Range objects cannot be ordered; please refer to the PostgreSQL' + ' documentation to perform this operation in the database') + + __le__ = __gt__ = __ge__ = __lt__ + + +def register_range(pgrange, pyrange, conn_or_curs, globally=False): + """Create and register an adapter and the typecasters to convert between + a PostgreSQL |range|_ type and a PostgreSQL `Range` subclass. + + :param pgrange: the name of the PostgreSQL |range| type. Can be + schema-qualified + :param pyrange: a `Range` strict subclass, or just a name to give to a new + class + :param conn_or_curs: a connection or cursor used to find the oid of the + range and its subtype; the typecaster is registered in a scope limited + to this object, unless *globally* is set to `!True` + :param globally: if `!False` (default) register the typecaster only on + *conn_or_curs*, otherwise register it globally + :return: `RangeCaster` instance responsible for the conversion + + If a string is passed to *pyrange*, a new `Range` subclass is created + with such name and will be available as the `~RangeCaster.range` attribute + of the returned `RangeCaster` object. + + The function queries the database on *conn_or_curs* to inspect the + *pgrange* type and raises `~psycopg2.ProgrammingError` if the type is not + found. If querying the database is not advisable, use directly the + `RangeCaster` class and register the adapter and typecasters using the + provided functions. + + """ + caster = RangeCaster._from_db(pgrange, pyrange, conn_or_curs) + caster._register(not globally and conn_or_curs or None) + return caster + + +class RangeAdapter(object): + """`ISQLQuote` adapter for `Range` subclasses. + + This is an abstract class: concrete classes must set a `name` class + attribute or override `getquoted()`. + """ + name = None + + def __init__(self, adapted): + self.adapted = adapted + + def __conform__(self, proto): + if self._proto is ISQLQuote: + return self + + def prepare(self, conn): + self._conn = conn + + def getquoted(self): + if self.name is None: + raise NotImplementedError( + 'RangeAdapter must be subclassed overriding its name ' + 'or the getquoted() method') + + r = self.adapted + if r.isempty: + return b("'empty'::" + self.name) + + if r.lower is not None: + a = adapt(r.lower) + if hasattr(a, 'prepare'): + a.prepare(self._conn) + lower = a.getquoted() + else: + lower = b('NULL') + + if r.upper is not None: + a = adapt(r.upper) + if hasattr(a, 'prepare'): + a.prepare(self._conn) + upper = a.getquoted() + else: + upper = b('NULL') + + return b(self.name + '(') + lower + b(', ') + upper \ + + b(", '%s')" % r._bounds) + + +class RangeCaster(object): + """Helper class to convert between `Range` and PostgreSQL range types. + + Objects of this class are usually created by `register_range()`. Manual + creation could be useful if querying the database is not advisable: in + this case the oids must be provided. + """ + def __init__(self, pgrange, pyrange, oid, subtype_oid, array_oid=None): + self.subtype_oid = subtype_oid + self._create_ranges(pgrange, pyrange) + + name = self.adapter.name or self.adapter.__class__.__name__ + + self.typecaster = new_type((oid,), name, self.parse) + + if array_oid is not None: + self.array_typecaster = new_array_type( + (array_oid,), name + "ARRAY", self.typecaster) + else: + self.array_typecaster = None + + def _create_ranges(self, pgrange, pyrange): + """Create Range and RangeAdapter classes if needed.""" + # if got a string create a new RangeAdapter concrete type (with a name) + # else take it as an adapter. Passing an adapter should be considered + # an implementation detail and is not documented. It is currently used + # for the numeric ranges. + self.adapter = None + if isinstance(pgrange, basestring): + self.adapter = type(pgrange, (RangeAdapter,), {}) + self.adapter.name = pgrange + else: + try: + if issubclass(pgrange, RangeAdapter) and pgrange is not RangeAdapter: + self.adapter = pgrange + except TypeError: + pass + + if self.adapter is None: + raise TypeError( + 'pgrange must be a string or a RangeAdapter strict subclass') + + self.range = None + try: + if isinstance(pyrange, basestring): + self.range = type(pyrange, (Range,), {}) + if issubclass(pyrange, Range) and pyrange is not Range: + self.range = pyrange + except TypeError: + pass + + if self.range is None: + raise TypeError( + 'pyrange must be a type or a Range strict subclass') + + @classmethod + def _from_db(self, name, pyrange, conn_or_curs): + """Return a `RangeCaster` instance for the type *pgrange*. + + Raise `ProgrammingError` if the type is not found. + """ + from psycopg2.extensions import STATUS_IN_TRANSACTION + from psycopg2.extras import _solve_conn_curs + conn, curs = _solve_conn_curs(conn_or_curs) + + if conn.server_version < 90200: + raise ProgrammingError("range types not available in version %s" + % conn.server_version) + + # Store the transaction status of the connection to revert it after use + conn_status = conn.status + + # Use the correct schema + if '.' in name: + schema, tname = name.split('.', 1) + else: + tname = name + schema = 'public' + + # get the type oid and attributes + try: + curs.execute("""\ +select rngtypid, rngsubtype, + (select typarray from pg_type where oid = rngtypid) +from pg_range r +join pg_type t on t.oid = rngtypid +join pg_namespace ns on ns.oid = typnamespace +where typname = %s and ns.nspname = %s; +""", (tname, schema)) + + except ProgrammingError: + if not conn.autocommit: + conn.rollback() + raise + else: + rec = curs.fetchone() + + # revert the status of the connection as before the command + if (conn_status != STATUS_IN_TRANSACTION + and not conn.autocommit): + conn.rollback() + + if not rec: + raise ProgrammingError( + "PostgreSQL type '%s' not found" % name) + + type, subtype, array = rec + + return RangeCaster(name, pyrange, + oid=type, subtype_oid=subtype, array_oid=array) + + _re_range = re.compile(r""" + ( \(|\[ ) # lower bound flag + (?: # lower bound: + " ( (?: [^"] | "")* ) " # - a quoted string + | ( [^",]+ ) # - or an unquoted string + )? # - or empty (not catched) + , + (?: # upper bound: + " ( (?: [^"] | "")* ) " # - a quoted string + | ( [^"\)\]]+ ) # - or an unquoted string + )? # - or empty (not catched) + ( \)|\] ) # upper bound flag + """, re.VERBOSE) + + _re_undouble = re.compile(r'(["\\])\1') + + def parse(self, s, cur=None): + if s is None: + return None + + if s == 'empty': + return self.range(empty=True) + + m = self._re_range.match(s) + if m is None: + raise InterfaceError("failed to parse range: %s") + + lower = m.group(3) + if lower is None: + lower = m.group(2) + if lower is not None: + lower = self._re_undouble.sub(r"\1", lower) + + upper = m.group(5) + if upper is None: + upper = m.group(4) + if upper is not None: + upper = self._re_undouble.sub(r"\1", upper) + + if cur is not None: + lower = cur.cast(self.subtype_oid, lower) + upper = cur.cast(self.subtype_oid, upper) + + bounds = m.group(1) + m.group(6) + + return self.range(lower, upper, bounds) + + def _register(self, scope=None): + register_type(self.typecaster, scope) + if self.array_typecaster is not None: + register_type(self.array_typecaster, scope) + + register_adapter(self.range, self.adapter) + + +class NumericRange(Range): + """A `Range` suitable to pass Python numeric types to a PostgreSQL range. + + PostgreSQL types :sql:`int4range`, :sql:`int8range`, :sql:`numrange` are + casted into `!NumericRange` instances. + """ + pass + +class DateRange(Range): + """Represents :sql:`daterange` values.""" + pass + +class DateTimeRange(Range): + """Represents :sql:`tsrange` values.""" + pass + +class DateTimeTZRange(Range): + """Represents :sql:`tstzrange` values.""" + pass + + +# Special adaptation for NumericRange. Allows to pass number range regardless +# of whether they are ints, floats and what size of ints are, which are +# pointless in Python world. On the way back, no numeric range is casted to +# NumericRange, but only to their subclasses + +class NumberRangeAdapter(RangeAdapter): + """Adapt a range if the subtype doesn't need quotes.""" + def getquoted(self): + r = self.adapted + if r.isempty: + return "'empty'" + + if not r.lower_inf: + # not exactly: we are relying that none of these object is really + # quoted (they are numbers). Also, I'm lazy and not preparing the + # adapter because I assume encoding doesn't matter for these + # objects. + lower = adapt(r.lower).getquoted().decode('ascii') + else: + lower = '' + + if not r.upper_inf: + upper = adapt(r.upper).getquoted().decode('ascii') + else: + upper = '' + + return b("'%s%s,%s%s'" % ( + r._bounds[0], lower, upper, r._bounds[1])) + +# TODO: probably won't work with infs, nans and other tricky cases. +register_adapter(NumericRange, NumberRangeAdapter) + + +# Register globally typecasters and adapters for builtin range types. + +# note: the adapter is registered more than once, but this is harmless. +int4range_caster = RangeCaster(NumberRangeAdapter, NumericRange, + oid=3904, subtype_oid=23, array_oid=3905) +int4range_caster._register() + +int8range_caster = RangeCaster(NumberRangeAdapter, NumericRange, + oid=3926, subtype_oid=20, array_oid=3927) +int8range_caster._register() + +numrange_caster = RangeCaster(NumberRangeAdapter, NumericRange, + oid=3906, subtype_oid=1700, array_oid=3907) +numrange_caster._register() + +daterange_caster = RangeCaster('daterange', DateRange, + oid=3912, subtype_oid=1082, array_oid=3913) +daterange_caster._register() + +tsrange_caster = RangeCaster('tsrange', DateTimeRange, + oid=3908, subtype_oid=1114, array_oid=3909) +tsrange_caster._register() + +tstzrange_caster = RangeCaster('tstzrange', DateTimeTZRange, + oid=3910, subtype_oid=1184, array_oid=3911) +tstzrange_caster._register() + + diff --git a/lib/errorcodes.py b/lib/errorcodes.py index 4a4ee1ce..12c300f6 100644 --- a/lib/errorcodes.py +++ b/lib/errorcodes.py @@ -59,6 +59,7 @@ CLASS_INVALID_TRANSACTION_INITIATION = '0B' CLASS_LOCATOR_EXCEPTION = '0F' CLASS_INVALID_GRANTOR = '0L' CLASS_INVALID_ROLE_SPECIFICATION = '0P' +CLASS_DIAGNOSTICS_EXCEPTION = '0Z' CLASS_CASE_NOT_FOUND = '20' CLASS_CARDINALITY_VIOLATION = '21' CLASS_DATA_EXCEPTION = '22' @@ -139,6 +140,10 @@ INVALID_GRANT_OPERATION = '0LP01' # Class 0P - Invalid Role Specification INVALID_ROLE_SPECIFICATION = '0P000' +# Class 0Z - Diagnostics Exception +DIAGNOSTICS_EXCEPTION = '0Z000' +STACKED_DIAGNOSTICS_ACCESSED_WITHOUT_ACTIVE_HANDLER = '0Z002' + # Class 20 - Case Not Found CASE_NOT_FOUND = '20000' @@ -331,6 +336,7 @@ INSUFFICIENT_RESOURCES = '53000' DISK_FULL = '53100' OUT_OF_MEMORY = '53200' TOO_MANY_CONNECTIONS = '53300' +CONFIGURATION_LIMIT_EXCEEDED = '53400' # Class 54 - Program Limit Exceeded PROGRAM_LIMIT_EXCEEDED = '54000' @@ -353,6 +359,7 @@ CANNOT_CONNECT_NOW = '57P03' DATABASE_DROPPED = '57P04' # Class 58 - System Error (errors external to PostgreSQL itself) +SYSTEM_ERROR = '58000' IO_ERROR = '58030' UNDEFINED_FILE = '58P01' DUPLICATE_FILE = '58P02' diff --git a/lib/extensions.py b/lib/extensions.py index bdcfdf28..f499e487 100644 --- a/lib/extensions.py +++ b/lib/extensions.py @@ -58,7 +58,7 @@ except ImportError: from psycopg2._psycopg import adapt, adapters, encodings, connection, cursor, lobject, Xid from psycopg2._psycopg import string_types, binary_types, new_type, new_array_type, register_type -from psycopg2._psycopg import ISQLQuote, Notify +from psycopg2._psycopg import ISQLQuote, Notify, Diagnostics from psycopg2._psycopg import QueryCanceledError, TransactionRollbackError @@ -151,6 +151,21 @@ class NoneAdapter(object): return _null +# Create default json typecasters for PostgreSQL 9.2 oids +from psycopg2._json import register_default_json + +try: + JSON, JSONARRAY = register_default_json() +except ImportError: + pass + +del register_default_json + + +# Create default Range typecasters +from psycopg2. _range import Range +del Range + # Add the "cleaned" version of the encodings to the key. # When the encoding is set its name is cleaned up from - and _ and turned # uppercase, so an encoding not respecting these rules wouldn't be found in the @@ -160,5 +175,3 @@ for k, v in encodings.items(): encodings[k] = v del k, v - -__all__ = filter(lambda k: not k.startswith('_'), locals().keys()) diff --git a/lib/extras.py b/lib/extras.py index 214f528d..33dd83d6 100644 --- a/lib/extras.py +++ b/lib/extras.py @@ -25,16 +25,15 @@ and classes untill a better place in the distribution is found. # FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public # License for more details. -import os -import sys -import time -import warnings -import re as regex +import os as _os +import sys as _sys +import time as _time +import re as _re try: - import logging + import logging as _logging except: - logging = None + _logging = None import psycopg2 from psycopg2 import extensions as _ext @@ -192,7 +191,7 @@ class DictRow(list): self._index = data[1] # drop the crusty Py2 methods - if sys.version_info[0] > 2: + if _sys.version_info[0] > 2: items = iteritems; del iteritems keys = iterkeys; del iterkeys values = itervalues; del itervalues @@ -302,21 +301,21 @@ class NamedTupleCursor(_cursor): nt = self.Record if nt is None: nt = self.Record = self._make_nt() - return nt(*t) + return nt._make(t) def fetchmany(self, size=None): ts = super(NamedTupleCursor, self).fetchmany(size) nt = self.Record if nt is None: nt = self.Record = self._make_nt() - return [nt(*t) for t in ts] + return map(nt._make, ts) def fetchall(self): ts = super(NamedTupleCursor, self).fetchall() nt = self.Record if nt is None: nt = self.Record = self._make_nt() - return [nt(*t) for t in ts] + return map(nt._make, ts) def __iter__(self): it = super(NamedTupleCursor, self).__iter__() @@ -326,10 +325,10 @@ class NamedTupleCursor(_cursor): if nt is None: nt = self.Record = self._make_nt() - yield nt(*t) + yield nt._make(t) while 1: - yield nt(*it.next()) + yield nt._make(it.next()) try: from collections import namedtuple @@ -354,7 +353,7 @@ class LoggingConnection(_connection): instance from the standard logging module. """ self._logobj = logobj - if logging and isinstance(logobj, logging.Logger): + if _logging and isinstance(logobj, _logging.Logger): self.log = self._logtologger else: self.log = self._logtofile @@ -370,7 +369,7 @@ class LoggingConnection(_connection): def _logtofile(self, msg, curs): msg = self.filter(msg, curs) - if msg: self._logobj.write(msg + os.linesep) + if msg: self._logobj.write(msg + _os.linesep) def _logtologger(self, msg, curs): msg = self.filter(msg, curs) @@ -418,9 +417,9 @@ class MinTimeLoggingConnection(LoggingConnection): self._mintime = mintime def filter(self, msg, curs): - t = (time.time() - curs.timestamp) * 1000 + t = (_time.time() - curs.timestamp) * 1000 if t > self._mintime: - return msg + os.linesep + " (execution time: %d ms)" % t + return msg + _os.linesep + " (execution time: %d ms)" % t def cursor(self, *args, **kwargs): kwargs.setdefault('cursor_factory', MinTimeLoggingCursor) @@ -430,11 +429,11 @@ class MinTimeLoggingCursor(LoggingCursor): """The cursor sub-class companion to `MinTimeLoggingConnection`.""" def execute(self, query, vars=None): - self.timestamp = time.time() + self.timestamp = _time.time() return LoggingCursor.execute(self, query, vars) def callproc(self, procname, vars=None): - self.timestamp = time.time() + self.timestamp = _time.time() return LoggingCursor.execute(self, procname, vars) @@ -558,20 +557,21 @@ def register_tstz_w_secs(oids=None, conn_or_curs=None): These are now correctly handled by the default type caster, so currently the function doesn't do anything. """ + import warnings warnings.warn("deprecated", DeprecationWarning) -import select -from psycopg2.extensions import POLL_OK, POLL_READ, POLL_WRITE -from psycopg2 import OperationalError - def wait_select(conn): """Wait until a connection or cursor has data available. The function is an example of a wait callback to be registered with - `~psycopg2.extensions.set_wait_callback()`. This function uses `!select()` - to wait for data available. + `~psycopg2.extensions.set_wait_callback()`. This function uses + :py:func:`~select.select()` to wait for data available. + """ + import select + from psycopg2.extensions import POLL_OK, POLL_READ, POLL_WRITE + while 1: state = conn.poll() if state == POLL_OK: @@ -581,11 +581,14 @@ def wait_select(conn): elif state == POLL_WRITE: select.select([], [conn.fileno()], []) else: - raise OperationalError("bad state from poll: %s" % state) + raise conn.OperationalError("bad state from poll: %s" % state) def _solve_conn_curs(conn_or_curs): """Return the connection and a DBAPI cursor from a connection or cursor.""" + if conn_or_curs is None: + raise psycopg2.ProgrammingError("no connection or cursor provided") + if hasattr(conn_or_curs, 'execute'): conn = conn_or_curs.connection curs = conn.cursor(cursor_factory=_cursor) @@ -645,7 +648,7 @@ class HstoreAdapter(object): getquoted = _getquoted_9 - _re_hstore = regex.compile(r""" + _re_hstore = _re.compile(r""" # hstore key: # a string of normal or escaped chars "((?: [^"\\] | \\. )*)" @@ -656,10 +659,10 @@ class HstoreAdapter(object): | "((?: [^"\\] | \\. )*)" ) (?:\s*,\s*|$) # pairs separated by comma or end of string. - """, regex.VERBOSE) + """, _re.VERBOSE) @classmethod - def parse(self, s, cur, _bsdec=regex.compile(r"\\(.)")): + def parse(self, s, cur, _bsdec=_re.compile(r"\\(.)")): """Parse an hstore representation in a Python string. The hstore is represented as something like:: @@ -783,7 +786,7 @@ def register_hstore(conn_or_curs, globally=False, unicode=False, array_oid = tuple([x for x in array_oid if x]) # create and register the typecaster - if sys.version_info[0] < 3 and unicode: + if _sys.version_info[0] < 3 and unicode: cast = HstoreAdapter.parse_unicode else: cast = HstoreAdapter.parse @@ -805,35 +808,10 @@ class CompositeCaster(object): querying the database at registration time is not desirable (such as when using an :ref:`asynchronous connections `). - .. attribute:: name - - The name of the PostgreSQL type. - - .. attribute:: oid - - The oid of the PostgreSQL type. - - .. attribute:: array_oid - - The oid of the PostgreSQL array type, if available. - - .. attribute:: type - - The type of the Python objects returned. If :py:func:`collections.namedtuple()` - is available, it is a named tuple with attributes equal to the type - components. Otherwise it is just the `!tuple` object. - - .. attribute:: attnames - - List of component names of the type to be casted. - - .. attribute:: atttypes - - List of component type oids of the type to be casted. - """ - def __init__(self, name, oid, attrs, array_oid=None): + def __init__(self, name, oid, attrs, array_oid=None, schema=None): self.name = name + self.schema = schema self.oid = oid self.array_oid = array_oid @@ -857,17 +835,30 @@ class CompositeCaster(object): "expecting %d components for the type %s, %d found instead" % (len(self.atttypes), self.name, len(tokens))) - attrs = [ curs.cast(oid, token) + values = [ curs.cast(oid, token) for oid, token in zip(self.atttypes, tokens) ] - return self._ctor(*attrs) - _re_tokenize = regex.compile(r""" + return self.make(values) + + def make(self, values): + """Return a new Python object representing the data being casted. + + *values* is the list of attributes, already casted into their Python + representation. + + You can subclass this method to :ref:`customize the composite cast + `. + """ + + return self._ctor(values) + + _re_tokenize = _re.compile(r""" \(? ([,)]) # an empty token, representing NULL | \(? " ((?: [^"] | "")*) " [,)] # or a quoted string | \(? ([^",)]+) [,)] # or an unquoted string - """, regex.VERBOSE) + """, _re.VERBOSE) - _re_undouble = regex.compile(r'(["\\])\1') + _re_undouble = _re.compile(r'(["\\])\1') @classmethod def tokenize(self, s): @@ -889,10 +880,10 @@ class CompositeCaster(object): from collections import namedtuple except ImportError: self.type = tuple - self._ctor = lambda *args: tuple(args) + self._ctor = self.type else: self.type = namedtuple(name, attnames) - self._ctor = self.type + self._ctor = self.type._make @classmethod def _from_db(self, name, conn_or_curs): @@ -941,10 +932,10 @@ ORDER BY attnum; array_oid = recs[0][1] type_attrs = [ (r[2], r[3]) for r in recs ] - return CompositeCaster(tname, type_oid, type_attrs, - array_oid=array_oid) + return self(tname, type_oid, type_attrs, + array_oid=array_oid, schema=schema) -def register_composite(name, conn_or_curs, globally=False): +def register_composite(name, conn_or_curs, globally=False, factory=None): """Register a typecaster to convert a composite type into a tuple. :param name: the name of a PostgreSQL composite type, e.g. created using @@ -954,14 +945,15 @@ def register_composite(name, conn_or_curs, globally=False): object, unless *globally* is set to `!True` :param globally: if `!False` (default) register the typecaster only on *conn_or_curs*, otherwise register it globally - :return: the registered `CompositeCaster` instance responsible for the - conversion - - .. versionchanged:: 2.4.3 - added support for array of composite types - + :param factory: if specified it should be a `CompositeCaster` subclass: use + it to :ref:`customize how to cast composite types ` + :return: the registered `CompositeCaster` or *factory* instance + responsible for the conversion """ - caster = CompositeCaster._from_db(name, conn_or_curs) + if factory is None: + factory = CompositeCaster + + caster = factory._from_db(name, conn_or_curs) _ext.register_type(caster.typecaster, not globally and conn_or_curs or None) if caster.array_typecaster is not None: @@ -970,4 +962,11 @@ def register_composite(name, conn_or_curs, globally=False): return caster -__all__ = filter(lambda k: not k.startswith('_'), locals().keys()) +# expose the json adaptation stuff into the module +from psycopg2._json import json, Json, register_json, register_default_json + + +# Expose range-related objects +from psycopg2._range import Range, NumericRange +from psycopg2._range import DateRange, DateTimeRange, DateTimeTZRange +from psycopg2._range import register_range, RangeAdapter, RangeCaster diff --git a/lib/pool.py b/lib/pool.py index f05767a3..3b41c803 100644 --- a/lib/pool.py +++ b/lib/pool.py @@ -27,30 +27,6 @@ This module implements thread-safe (and not) connection pools. import psycopg2 import psycopg2.extensions as _ext -try: - import logging - # create logger object for psycopg2 module and sub-modules - _logger = logging.getLogger("psycopg2") - def dbg(*args): - _logger.debug("psycopg2", ' '.join([str(x) for x in args])) - try: - import App # does this make sure that we're running in Zope? - _logger.info("installed. Logging using Python logging module") - except: - _logger.debug("installed. Logging using Python logging module") - -except ImportError: - from zLOG import LOG, DEBUG, INFO - def dbg(*args): - LOG('ZPsycopgDA', DEBUG, "", - ' '.join([str(x) for x in args])+'\n') - LOG('ZPsycopgDA', INFO, "Installed", "Logging using Zope's zLOG\n") - -except: - import sys - def dbg(*args): - sys.stderr.write(' '.join(args)+'\n') - class PoolError(psycopg2.Error): pass diff --git a/psycopg/adapter_asis.c b/psycopg/adapter_asis.c index f194e1d1..a2a8f4dc 100644 --- a/psycopg/adapter_asis.c +++ b/psycopg/adapter_asis.c @@ -117,13 +117,6 @@ asis_setup(asisObject *self, PyObject *obj) return 0; } -static int -asis_traverse(asisObject *self, visitproc visit, void *arg) -{ - Py_VISIT(self->wrapped); - return 0; -} - static void asis_dealloc(PyObject* obj) { @@ -156,12 +149,6 @@ asis_new(PyTypeObject *type, PyObject *args, PyObject *kwds) return type->tp_alloc(type, 0); } -static void -asis_del(PyObject* self) -{ - PyObject_GC_Del(self); -} - static PyObject * asis_repr(asisObject *self) { @@ -177,63 +164,41 @@ asis_repr(asisObject *self) PyTypeObject asisType = { PyVarObject_HEAD_INIT(NULL, 0) "psycopg2._psycopg.AsIs", - sizeof(asisObject), - 0, + sizeof(asisObject), 0, asis_dealloc, /*tp_dealloc*/ 0, /*tp_print*/ - 0, /*tp_getattr*/ 0, /*tp_setattr*/ - 0, /*tp_compare*/ - (reprfunc)asis_repr, /*tp_repr*/ 0, /*tp_as_number*/ 0, /*tp_as_sequence*/ 0, /*tp_as_mapping*/ 0, /*tp_hash */ - 0, /*tp_call*/ (reprfunc)asis_str, /*tp_str*/ - 0, /*tp_getattro*/ 0, /*tp_setattro*/ 0, /*tp_as_buffer*/ - - Py_TPFLAGS_DEFAULT|Py_TPFLAGS_BASETYPE|Py_TPFLAGS_HAVE_GC, /*tp_flags*/ + Py_TPFLAGS_DEFAULT|Py_TPFLAGS_BASETYPE, /*tp_flags*/ asisType_doc, /*tp_doc*/ - - (traverseproc)asis_traverse, /*tp_traverse*/ + 0, /*tp_traverse*/ 0, /*tp_clear*/ - 0, /*tp_richcompare*/ 0, /*tp_weaklistoffset*/ - 0, /*tp_iter*/ 0, /*tp_iternext*/ - - /* Attribute descriptor and subclassing stuff */ - asisObject_methods, /*tp_methods*/ asisObject_members, /*tp_members*/ 0, /*tp_getset*/ 0, /*tp_base*/ 0, /*tp_dict*/ - 0, /*tp_descr_get*/ 0, /*tp_descr_set*/ 0, /*tp_dictoffset*/ - asis_init, /*tp_init*/ - 0, /*tp_alloc will be set to PyType_GenericAlloc in module init*/ + 0, /*tp_alloc*/ asis_new, /*tp_new*/ - (freefunc)asis_del, /*tp_free Low-level free-memory routine */ - 0, /*tp_is_gc For PyObject_IS_GC */ - 0, /*tp_bases*/ - 0, /*tp_mro method resolution order */ - 0, /*tp_cache*/ - 0, /*tp_subclasses*/ - 0 /*tp_weaklist*/ }; diff --git a/psycopg/adapter_binary.c b/psycopg/adapter_binary.c index b08c1447..3edcea8d 100644 --- a/psycopg/adapter_binary.c +++ b/psycopg/adapter_binary.c @@ -159,8 +159,7 @@ binary_prepare(binaryObject *self, PyObject *args) self->conn = conn; Py_INCREF(self->conn); - Py_INCREF(Py_None); - return Py_None; + Py_RETURN_NONE; } static PyObject * @@ -221,17 +220,6 @@ binary_setup(binaryObject *self, PyObject *str) return 0; } -static int -binary_traverse(PyObject *obj, visitproc visit, void *arg) -{ - binaryObject *self = (binaryObject *)obj; - - Py_VISIT(self->wrapped); - Py_VISIT(self->buffer); - Py_VISIT(self->conn); - return 0; -} - static void binary_dealloc(PyObject* obj) { @@ -266,12 +254,6 @@ binary_new(PyTypeObject *type, PyObject *args, PyObject *kwds) return type->tp_alloc(type, 0); } -static void -binary_del(PyObject* self) -{ - PyObject_GC_Del(self); -} - static PyObject * binary_repr(binaryObject *self) { @@ -286,61 +268,41 @@ binary_repr(binaryObject *self) PyTypeObject binaryType = { PyVarObject_HEAD_INIT(NULL, 0) "psycopg2._psycopg.Binary", - sizeof(binaryObject), - 0, + sizeof(binaryObject), 0, binary_dealloc, /*tp_dealloc*/ 0, /*tp_print*/ 0, /*tp_getattr*/ 0, /*tp_setattr*/ - 0, /*tp_compare*/ (reprfunc)binary_repr, /*tp_repr*/ 0, /*tp_as_number*/ 0, /*tp_as_sequence*/ 0, /*tp_as_mapping*/ 0, /*tp_hash */ - 0, /*tp_call*/ (reprfunc)binary_str, /*tp_str*/ 0, /*tp_getattro*/ 0, /*tp_setattro*/ 0, /*tp_as_buffer*/ - - Py_TPFLAGS_DEFAULT|Py_TPFLAGS_BASETYPE|Py_TPFLAGS_HAVE_GC, /*tp_flags*/ - + Py_TPFLAGS_DEFAULT|Py_TPFLAGS_BASETYPE, /*tp_flags*/ binaryType_doc, /*tp_doc*/ - - binary_traverse, /*tp_traverse*/ + 0, /*tp_traverse*/ 0, /*tp_clear*/ - 0, /*tp_richcompare*/ 0, /*tp_weaklistoffset*/ - 0, /*tp_iter*/ 0, /*tp_iternext*/ - - /* Attribute descriptor and subclassing stuff */ - binaryObject_methods, /*tp_methods*/ binaryObject_members, /*tp_members*/ 0, /*tp_getset*/ 0, /*tp_base*/ 0, /*tp_dict*/ - 0, /*tp_descr_get*/ 0, /*tp_descr_set*/ 0, /*tp_dictoffset*/ - binary_init, /*tp_init*/ - 0, /*tp_alloc will be set to PyType_GenericAlloc in module init*/ + 0, /*tp_alloc*/ binary_new, /*tp_new*/ - (freefunc)binary_del, /*tp_free Low-level free-memory routine */ - 0, /*tp_is_gc For PyObject_IS_GC */ - 0, /*tp_bases*/ - 0, /*tp_mro method resolution order */ - 0, /*tp_cache*/ - 0, /*tp_subclasses*/ - 0 /*tp_weaklist*/ }; diff --git a/psycopg/adapter_datetime.c b/psycopg/adapter_datetime.c index bf31cfab..67697fba 100644 --- a/psycopg/adapter_datetime.c +++ b/psycopg/adapter_datetime.c @@ -183,15 +183,6 @@ pydatetime_setup(pydatetimeObject *self, PyObject *obj, int type) return 0; } -static int -pydatetime_traverse(PyObject *obj, visitproc visit, void *arg) -{ - pydatetimeObject *self = (pydatetimeObject *)obj; - - Py_VISIT(self->wrapped); - return 0; -} - static void pydatetime_dealloc(PyObject* obj) { @@ -223,12 +214,6 @@ pydatetime_new(PyTypeObject *type, PyObject *args, PyObject *kwds) return type->tp_alloc(type, 0); } -static void -pydatetime_del(PyObject* self) -{ - PyObject_GC_Del(self); -} - static PyObject * pydatetime_repr(pydatetimeObject *self) { @@ -244,61 +229,41 @@ pydatetime_repr(pydatetimeObject *self) PyTypeObject pydatetimeType = { PyVarObject_HEAD_INIT(NULL, 0) "psycopg2._psycopg.datetime", - sizeof(pydatetimeObject), - 0, + sizeof(pydatetimeObject), 0, pydatetime_dealloc, /*tp_dealloc*/ 0, /*tp_print*/ 0, /*tp_getattr*/ 0, /*tp_setattr*/ - 0, /*tp_compare*/ (reprfunc)pydatetime_repr, /*tp_repr*/ 0, /*tp_as_number*/ 0, /*tp_as_sequence*/ 0, /*tp_as_mapping*/ 0, /*tp_hash */ - 0, /*tp_call*/ (reprfunc)pydatetime_str, /*tp_str*/ 0, /*tp_getattro*/ 0, /*tp_setattro*/ 0, /*tp_as_buffer*/ - - Py_TPFLAGS_DEFAULT|Py_TPFLAGS_BASETYPE|Py_TPFLAGS_HAVE_GC, /*tp_flags*/ - + Py_TPFLAGS_DEFAULT|Py_TPFLAGS_BASETYPE, /*tp_flags*/ pydatetimeType_doc, /*tp_doc*/ - - pydatetime_traverse, /*tp_traverse*/ + 0, /*tp_traverse*/ 0, /*tp_clear*/ - 0, /*tp_richcompare*/ 0, /*tp_weaklistoffset*/ - 0, /*tp_iter*/ 0, /*tp_iternext*/ - - /* Attribute descriptor and subclassing stuff */ - pydatetimeObject_methods, /*tp_methods*/ pydatetimeObject_members, /*tp_members*/ 0, /*tp_getset*/ 0, /*tp_base*/ 0, /*tp_dict*/ - 0, /*tp_descr_get*/ 0, /*tp_descr_set*/ 0, /*tp_dictoffset*/ - pydatetime_init, /*tp_init*/ - 0, /*tp_alloc will be set to PyType_GenericAlloc in module init*/ + 0, /*tp_alloc*/ pydatetime_new, /*tp_new*/ - (freefunc)pydatetime_del, /*tp_free Low-level free-memory routine */ - 0, /*tp_is_gc For PyObject_IS_GC */ - 0, /*tp_bases*/ - 0, /*tp_mro method resolution order */ - 0, /*tp_cache*/ - 0, /*tp_subclasses*/ - 0 /*tp_weaklist*/ }; diff --git a/psycopg/adapter_list.c b/psycopg/adapter_list.c index d97ecfb1..1198a81b 100644 --- a/psycopg/adapter_list.c +++ b/psycopg/adapter_list.c @@ -103,16 +103,11 @@ list_prepare(listObject *self, PyObject *args) if (!PyArg_ParseTuple(args, "O!", &connectionType, &conn)) return NULL; - /* note that we don't copy the encoding from the connection, but take a - reference to it; we'll need it during the recursive adapt() call (the - encoding is here for a future expansion that will make .getquoted() - work even without a connection to the backend. */ Py_CLEAR(self->connection); Py_INCREF(conn); self->connection = conn; - Py_INCREF(Py_None); - return Py_None; + Py_RETURN_NONE; } static PyObject * @@ -154,7 +149,7 @@ static PyMethodDef listObject_methods[] = { /* initialization and finalization methods */ static int -list_setup(listObject *self, PyObject *obj, const char *enc) +list_setup(listObject *self, PyObject *obj) { Dprintf("list_setup: init list object at %p, refcnt = " FORMAT_CODE_PY_SSIZE_T, @@ -164,9 +159,6 @@ list_setup(listObject *self, PyObject *obj, const char *enc) if (!PyList_Check(obj)) return -1; - /* FIXME: remove this orrible strdup */ - if (enc) self->encoding = strdup(enc); - self->connection = NULL; Py_INCREF(obj); self->wrapped = obj; @@ -179,40 +171,42 @@ list_setup(listObject *self, PyObject *obj, const char *enc) } static int -list_traverse(PyObject *obj, visitproc visit, void *arg) +list_traverse(listObject *self, visitproc visit, void *arg) { - listObject *self = (listObject *)obj; - Py_VISIT(self->wrapped); Py_VISIT(self->connection); return 0; } -static void -list_dealloc(PyObject* obj) +static int +list_clear(listObject *self) { - listObject *self = (listObject *)obj; - Py_CLEAR(self->wrapped); Py_CLEAR(self->connection); - if (self->encoding) free(self->encoding); + return 0; +} + +static void +list_dealloc(listObject* self) +{ + PyObject_GC_UnTrack((PyObject *)self); + list_clear(self); Dprintf("list_dealloc: deleted list object at %p, " - "refcnt = " FORMAT_CODE_PY_SSIZE_T, obj, Py_REFCNT(obj)); + "refcnt = " FORMAT_CODE_PY_SSIZE_T, self, Py_REFCNT(self)); - Py_TYPE(obj)->tp_free(obj); + Py_TYPE(self)->tp_free((PyObject *)self); } static int list_init(PyObject *obj, PyObject *args, PyObject *kwds) { PyObject *l; - const char *enc = "latin-1"; /* default encoding as in Python */ - if (!PyArg_ParseTuple(args, "O|s", &l, &enc)) + if (!PyArg_ParseTuple(args, "O", &l)) return -1; - return list_setup((listObject *)obj, l, enc); + return list_setup((listObject *)obj, l); } static PyObject * @@ -221,12 +215,6 @@ list_new(PyTypeObject *type, PyObject *args, PyObject *kwds) return type->tp_alloc(type, 0); } -static void -list_del(PyObject* self) -{ - PyObject_GC_Del(self); -} - static PyObject * list_repr(listObject *self) { @@ -241,61 +229,41 @@ list_repr(listObject *self) PyTypeObject listType = { PyVarObject_HEAD_INIT(NULL, 0) "psycopg2._psycopg.List", - sizeof(listObject), - 0, - list_dealloc, /*tp_dealloc*/ + sizeof(listObject), 0, + (destructor)list_dealloc, /*tp_dealloc*/ 0, /*tp_print*/ 0, /*tp_getattr*/ 0, /*tp_setattr*/ - 0, /*tp_compare*/ (reprfunc)list_repr, /*tp_repr*/ 0, /*tp_as_number*/ 0, /*tp_as_sequence*/ 0, /*tp_as_mapping*/ 0, /*tp_hash */ - 0, /*tp_call*/ (reprfunc)list_str, /*tp_str*/ 0, /*tp_getattro*/ 0, /*tp_setattro*/ 0, /*tp_as_buffer*/ - Py_TPFLAGS_DEFAULT|Py_TPFLAGS_BASETYPE|Py_TPFLAGS_HAVE_GC, /*tp_flags*/ - listType_doc, /*tp_doc*/ - - list_traverse, /*tp_traverse*/ - 0, /*tp_clear*/ - + (traverseproc)list_traverse, /*tp_traverse*/ + (inquiry)list_clear, /*tp_clear*/ 0, /*tp_richcompare*/ 0, /*tp_weaklistoffset*/ - 0, /*tp_iter*/ 0, /*tp_iternext*/ - - /* Attribute descriptor and subclassing stuff */ - listObject_methods, /*tp_methods*/ listObject_members, /*tp_members*/ 0, /*tp_getset*/ 0, /*tp_base*/ 0, /*tp_dict*/ - 0, /*tp_descr_get*/ 0, /*tp_descr_set*/ 0, /*tp_dictoffset*/ - list_init, /*tp_init*/ - 0, /*tp_alloc will be set to PyType_GenericAlloc in module init*/ + 0, /*tp_alloc*/ list_new, /*tp_new*/ - (freefunc)list_del, /*tp_free Low-level free-memory routine */ - 0, /*tp_is_gc For PyObject_IS_GC */ - 0, /*tp_bases*/ - 0, /*tp_mro method resolution order */ - 0, /*tp_cache*/ - 0, /*tp_subclasses*/ - 0 /*tp_weaklist*/ }; @@ -305,10 +273,9 @@ PyObject * psyco_List(PyObject *module, PyObject *args) { PyObject *str; - const char *enc = "latin-1"; /* default encoding as in Python */ - if (!PyArg_ParseTuple(args, "O|s", &str, &enc)) + if (!PyArg_ParseTuple(args, "O", &str)) return NULL; - return PyObject_CallFunction((PyObject *)&listType, "Os", str, enc); + return PyObject_CallFunctionObjArgs((PyObject *)&listType, "O", str, NULL); } diff --git a/psycopg/adapter_list.h b/psycopg/adapter_list.h index d7136103..8ec20f68 100644 --- a/psycopg/adapter_list.h +++ b/psycopg/adapter_list.h @@ -37,7 +37,6 @@ typedef struct { PyObject *wrapped; PyObject *connection; - char *encoding; } listObject; HIDDEN PyObject *psyco_List(PyObject *module, PyObject *args); diff --git a/psycopg/adapter_mxdatetime.c b/psycopg/adapter_mxdatetime.c index abe73f86..4696a9d0 100644 --- a/psycopg/adapter_mxdatetime.c +++ b/psycopg/adapter_mxdatetime.c @@ -172,15 +172,6 @@ mxdatetime_setup(mxdatetimeObject *self, PyObject *obj, int type) return 0; } -static int -mxdatetime_traverse(PyObject *obj, visitproc visit, void *arg) -{ - mxdatetimeObject *self = (mxdatetimeObject *)obj; - - Py_VISIT(self->wrapped); - return 0; -} - static void mxdatetime_dealloc(PyObject* obj) { @@ -214,12 +205,6 @@ mxdatetime_new(PyTypeObject *type, PyObject *args, PyObject *kwds) return type->tp_alloc(type, 0); } -static void -mxdatetime_del(PyObject* self) -{ - PyObject_GC_Del(self); -} - static PyObject * mxdatetime_repr(mxdatetimeObject *self) { @@ -235,61 +220,41 @@ mxdatetime_repr(mxdatetimeObject *self) PyTypeObject mxdatetimeType = { PyVarObject_HEAD_INIT(NULL, 0) "psycopg2._psycopg.MxDateTime", - sizeof(mxdatetimeObject), - 0, + sizeof(mxdatetimeObject), 0, mxdatetime_dealloc, /*tp_dealloc*/ 0, /*tp_print*/ 0, /*tp_getattr*/ 0, /*tp_setattr*/ - 0, /*tp_compare*/ (reprfunc)mxdatetime_repr, /*tp_repr*/ 0, /*tp_as_number*/ 0, /*tp_as_sequence*/ 0, /*tp_as_mapping*/ 0, /*tp_hash */ - 0, /*tp_call*/ (reprfunc)mxdatetime_str, /*tp_str*/ 0, /*tp_getattro*/ 0, /*tp_setattro*/ 0, /*tp_as_buffer*/ - - Py_TPFLAGS_DEFAULT|Py_TPFLAGS_BASETYPE|Py_TPFLAGS_HAVE_GC, /*tp_flags*/ - + Py_TPFLAGS_DEFAULT|Py_TPFLAGS_BASETYPE, /*tp_flags*/ mxdatetimeType_doc, /*tp_doc*/ - - mxdatetime_traverse, /*tp_traverse*/ + 0, /*tp_traverse*/ 0, /*tp_clear*/ - 0, /*tp_richcompare*/ 0, /*tp_weaklistoffset*/ - 0, /*tp_iter*/ 0, /*tp_iternext*/ - - /* Attribute descriptor and subclassing stuff */ - mxdatetimeObject_methods, /*tp_methods*/ mxdatetimeObject_members, /*tp_members*/ 0, /*tp_getset*/ 0, /*tp_base*/ 0, /*tp_dict*/ - 0, /*tp_descr_get*/ 0, /*tp_descr_set*/ 0, /*tp_dictoffset*/ - mxdatetime_init, /*tp_init*/ 0, /*tp_alloc*/ mxdatetime_new, /*tp_new*/ - (freefunc)mxdatetime_del, /*tp_free Low-level free-memory routine */ - 0, /*tp_is_gc For PyObject_IS_GC */ - 0, /*tp_bases*/ - 0, /*tp_mro method resolution order */ - 0, /*tp_cache*/ - 0, /*tp_subclasses*/ - 0 /*tp_weaklist*/ }; diff --git a/psycopg/adapter_pboolean.c b/psycopg/adapter_pboolean.c index 4e2c4464..f8cd9959 100644 --- a/psycopg/adapter_pboolean.c +++ b/psycopg/adapter_pboolean.c @@ -114,15 +114,6 @@ pboolean_setup(pbooleanObject *self, PyObject *obj) return 0; } -static int -pboolean_traverse(PyObject *obj, visitproc visit, void *arg) -{ - pbooleanObject *self = (pbooleanObject *)obj; - - Py_VISIT(self->wrapped); - return 0; -} - static void pboolean_dealloc(PyObject* obj) { @@ -155,12 +146,6 @@ pboolean_new(PyTypeObject *type, PyObject *args, PyObject *kwds) return type->tp_alloc(type, 0); } -static void -pboolean_del(PyObject* self) -{ - PyObject_GC_Del(self); -} - static PyObject * pboolean_repr(pbooleanObject *self) { @@ -177,63 +162,41 @@ pboolean_repr(pbooleanObject *self) PyTypeObject pbooleanType = { PyVarObject_HEAD_INIT(NULL, 0) "psycopg2._psycopg.Boolean", - sizeof(pbooleanObject), - 0, + sizeof(pbooleanObject), 0, pboolean_dealloc, /*tp_dealloc*/ 0, /*tp_print*/ - 0, /*tp_getattr*/ 0, /*tp_setattr*/ - 0, /*tp_compare*/ - (reprfunc)pboolean_repr, /*tp_repr*/ 0, /*tp_as_number*/ 0, /*tp_as_sequence*/ 0, /*tp_as_mapping*/ 0, /*tp_hash */ - 0, /*tp_call*/ (reprfunc)pboolean_str, /*tp_str*/ - 0, /*tp_getattro*/ 0, /*tp_setattro*/ 0, /*tp_as_buffer*/ - - Py_TPFLAGS_DEFAULT|Py_TPFLAGS_BASETYPE|Py_TPFLAGS_HAVE_GC, /*tp_flags*/ + Py_TPFLAGS_DEFAULT|Py_TPFLAGS_BASETYPE, /*tp_flags*/ pbooleanType_doc, /*tp_doc*/ - - pboolean_traverse, /*tp_traverse*/ + 0, /*tp_traverse*/ 0, /*tp_clear*/ - 0, /*tp_richcompare*/ 0, /*tp_weaklistoffset*/ - 0, /*tp_iter*/ 0, /*tp_iternext*/ - - /* Attribute descriptor and subclassing stuff */ - pbooleanObject_methods, /*tp_methods*/ pbooleanObject_members, /*tp_members*/ 0, /*tp_getset*/ 0, /*tp_base*/ 0, /*tp_dict*/ - 0, /*tp_descr_get*/ 0, /*tp_descr_set*/ 0, /*tp_dictoffset*/ - pboolean_init, /*tp_init*/ - 0, /*tp_alloc will be set to PyType_GenericAlloc in module init*/ + 0, /*tp_alloc*/ pboolean_new, /*tp_new*/ - (freefunc)pboolean_del, /*tp_free Low-level free-memory routine */ - 0, /*tp_is_gc For PyObject_IS_GC */ - 0, /*tp_bases*/ - 0, /*tp_mro method resolution order */ - 0, /*tp_cache*/ - 0, /*tp_subclasses*/ - 0 /*tp_weaklist*/ }; diff --git a/psycopg/adapter_pdecimal.c b/psycopg/adapter_pdecimal.c index e14e7694..35721417 100644 --- a/psycopg/adapter_pdecimal.c +++ b/psycopg/adapter_pdecimal.c @@ -170,15 +170,6 @@ pdecimal_setup(pdecimalObject *self, PyObject *obj) return 0; } -static int -pdecimal_traverse(PyObject *obj, visitproc visit, void *arg) -{ - pdecimalObject *self = (pdecimalObject *)obj; - - Py_VISIT(self->wrapped); - return 0; -} - static void pdecimal_dealloc(PyObject* obj) { @@ -211,12 +202,6 @@ pdecimal_new(PyTypeObject *type, PyObject *args, PyObject *kwds) return type->tp_alloc(type, 0); } -static void -pdecimal_del(PyObject* self) -{ - PyObject_GC_Del(self); -} - static PyObject * pdecimal_repr(pdecimalObject *self) { @@ -233,63 +218,41 @@ pdecimal_repr(pdecimalObject *self) PyTypeObject pdecimalType = { PyVarObject_HEAD_INIT(NULL, 0) "psycopg2._psycopg.Decimal", - sizeof(pdecimalObject), - 0, + sizeof(pdecimalObject), 0, pdecimal_dealloc, /*tp_dealloc*/ 0, /*tp_print*/ - 0, /*tp_getattr*/ 0, /*tp_setattr*/ - 0, /*tp_compare*/ - (reprfunc)pdecimal_repr, /*tp_repr*/ 0, /*tp_as_number*/ 0, /*tp_as_sequence*/ 0, /*tp_as_mapping*/ 0, /*tp_hash */ - 0, /*tp_call*/ (reprfunc)pdecimal_str, /*tp_str*/ - 0, /*tp_getattro*/ 0, /*tp_setattro*/ 0, /*tp_as_buffer*/ - - Py_TPFLAGS_DEFAULT|Py_TPFLAGS_BASETYPE|Py_TPFLAGS_HAVE_GC, /*tp_flags*/ + Py_TPFLAGS_DEFAULT|Py_TPFLAGS_BASETYPE, /*tp_flags*/ pdecimalType_doc, /*tp_doc*/ - - pdecimal_traverse, /*tp_traverse*/ + 0, /*tp_traverse*/ 0, /*tp_clear*/ - 0, /*tp_richcompare*/ 0, /*tp_weaklistoffset*/ - 0, /*tp_iter*/ 0, /*tp_iternext*/ - - /* Attribute descriptor and subclassing stuff */ - pdecimalObject_methods, /*tp_methods*/ pdecimalObject_members, /*tp_members*/ 0, /*tp_getset*/ 0, /*tp_base*/ 0, /*tp_dict*/ - 0, /*tp_descr_get*/ 0, /*tp_descr_set*/ 0, /*tp_dictoffset*/ - pdecimal_init, /*tp_init*/ - 0, /*tp_alloc will be set to PyType_GenericAlloc in module init*/ + 0, /*tp_alloc*/ pdecimal_new, /*tp_new*/ - (freefunc)pdecimal_del, /*tp_free Low-level free-memory routine */ - 0, /*tp_is_gc For PyObject_IS_GC */ - 0, /*tp_bases*/ - 0, /*tp_mro method resolution order */ - 0, /*tp_cache*/ - 0, /*tp_subclasses*/ - 0 /*tp_weaklist*/ }; diff --git a/psycopg/adapter_pfloat.c b/psycopg/adapter_pfloat.c index 1b8074f9..7bb7a467 100644 --- a/psycopg/adapter_pfloat.c +++ b/psycopg/adapter_pfloat.c @@ -143,15 +143,6 @@ pfloat_setup(pfloatObject *self, PyObject *obj) return 0; } -static int -pfloat_traverse(PyObject *obj, visitproc visit, void *arg) -{ - pfloatObject *self = (pfloatObject *)obj; - - Py_VISIT(self->wrapped); - return 0; -} - static void pfloat_dealloc(PyObject* obj) { @@ -184,12 +175,6 @@ pfloat_new(PyTypeObject *type, PyObject *args, PyObject *kwds) return type->tp_alloc(type, 0); } -static void -pfloat_del(PyObject* self) -{ - PyObject_GC_Del(self); -} - static PyObject * pfloat_repr(pfloatObject *self) { @@ -206,63 +191,41 @@ pfloat_repr(pfloatObject *self) PyTypeObject pfloatType = { PyVarObject_HEAD_INIT(NULL, 0) "psycopg2._psycopg.Float", - sizeof(pfloatObject), - 0, + sizeof(pfloatObject), 0, pfloat_dealloc, /*tp_dealloc*/ 0, /*tp_print*/ - 0, /*tp_getattr*/ 0, /*tp_setattr*/ - 0, /*tp_compare*/ - (reprfunc)pfloat_repr, /*tp_repr*/ 0, /*tp_as_number*/ 0, /*tp_as_sequence*/ 0, /*tp_as_mapping*/ 0, /*tp_hash */ - 0, /*tp_call*/ (reprfunc)pfloat_str, /*tp_str*/ - 0, /*tp_getattro*/ 0, /*tp_setattro*/ 0, /*tp_as_buffer*/ - - Py_TPFLAGS_DEFAULT|Py_TPFLAGS_BASETYPE|Py_TPFLAGS_HAVE_GC, /*tp_flags*/ + Py_TPFLAGS_DEFAULT|Py_TPFLAGS_BASETYPE, /*tp_flags*/ pfloatType_doc, /*tp_doc*/ - - pfloat_traverse, /*tp_traverse*/ + 0, /*tp_traverse*/ 0, /*tp_clear*/ - 0, /*tp_richcompare*/ 0, /*tp_weaklistoffset*/ - 0, /*tp_iter*/ 0, /*tp_iternext*/ - - /* Attribute descriptor and subclassing stuff */ - pfloatObject_methods, /*tp_methods*/ pfloatObject_members, /*tp_members*/ 0, /*tp_getset*/ 0, /*tp_base*/ 0, /*tp_dict*/ - 0, /*tp_descr_get*/ 0, /*tp_descr_set*/ 0, /*tp_dictoffset*/ - pfloat_init, /*tp_init*/ - 0, /*tp_alloc will be set to PyType_GenericAlloc in module init*/ + 0, /*tp_alloc*/ pfloat_new, /*tp_new*/ - (freefunc)pfloat_del, /*tp_free Low-level free-memory routine */ - 0, /*tp_is_gc For PyObject_IS_GC */ - 0, /*tp_bases*/ - 0, /*tp_mro method resolution order */ - 0, /*tp_cache*/ - 0, /*tp_subclasses*/ - 0 /*tp_weaklist*/ }; diff --git a/psycopg/adapter_pint.c b/psycopg/adapter_pint.c index ad89a06b..6465acec 100644 --- a/psycopg/adapter_pint.c +++ b/psycopg/adapter_pint.c @@ -129,15 +129,6 @@ pint_setup(pintObject *self, PyObject *obj) return 0; } -static int -pint_traverse(PyObject *obj, visitproc visit, void *arg) -{ - pintObject *self = (pintObject *)obj; - - Py_VISIT(self->wrapped); - return 0; -} - static void pint_dealloc(PyObject* obj) { @@ -170,12 +161,6 @@ pint_new(PyTypeObject *type, PyObject *args, PyObject *kwds) return type->tp_alloc(type, 0); } -static void -pint_del(PyObject* self) -{ - PyObject_GC_Del(self); -} - static PyObject * pint_repr(pintObject *self) { @@ -192,63 +177,41 @@ pint_repr(pintObject *self) PyTypeObject pintType = { PyVarObject_HEAD_INIT(NULL, 0) "psycopg2._psycopg.Int", - sizeof(pintObject), - 0, + sizeof(pintObject), 0, pint_dealloc, /*tp_dealloc*/ 0, /*tp_print*/ - 0, /*tp_getattr*/ 0, /*tp_setattr*/ - 0, /*tp_compare*/ - (reprfunc)pint_repr, /*tp_repr*/ 0, /*tp_as_number*/ 0, /*tp_as_sequence*/ 0, /*tp_as_mapping*/ 0, /*tp_hash */ - 0, /*tp_call*/ (reprfunc)pint_str, /*tp_str*/ - 0, /*tp_getattro*/ 0, /*tp_setattro*/ 0, /*tp_as_buffer*/ - - Py_TPFLAGS_DEFAULT|Py_TPFLAGS_BASETYPE|Py_TPFLAGS_HAVE_GC, /*tp_flags*/ + Py_TPFLAGS_DEFAULT|Py_TPFLAGS_BASETYPE, /*tp_flags*/ pintType_doc, /*tp_doc*/ - - pint_traverse, /*tp_traverse*/ + 0, /*tp_traverse*/ 0, /*tp_clear*/ - 0, /*tp_richcompare*/ 0, /*tp_weaklistoffset*/ - 0, /*tp_iter*/ 0, /*tp_iternext*/ - - /* Attribute descriptor and subclassing stuff */ - pintObject_methods, /*tp_methods*/ pintObject_members, /*tp_members*/ 0, /*tp_getset*/ 0, /*tp_base*/ 0, /*tp_dict*/ - 0, /*tp_descr_get*/ 0, /*tp_descr_set*/ 0, /*tp_dictoffset*/ - pint_init, /*tp_init*/ - 0, /*tp_alloc will be set to PyType_GenericAlloc in module init*/ + 0, /*tp_alloc*/ pint_new, /*tp_new*/ - (freefunc)pint_del, /*tp_free Low-level free-memory routine */ - 0, /*tp_is_gc For PyObject_IS_GC */ - 0, /*tp_bases*/ - 0, /*tp_mro method resolution order */ - 0, /*tp_cache*/ - 0, /*tp_subclasses*/ - 0 /*tp_weaklist*/ }; diff --git a/psycopg/adapter_qstring.c b/psycopg/adapter_qstring.c index 53a87603..91c14673 100644 --- a/psycopg/adapter_qstring.c +++ b/psycopg/adapter_qstring.c @@ -32,26 +32,32 @@ #include +static const char *default_encoding = "latin1"; /* qstring_quote - do the quote process on plain and unicode strings */ -BORROWED static PyObject * +static PyObject * qstring_quote(qstringObject *self) { - PyObject *str; - char *s, *buffer; + PyObject *str = NULL; + char *s, *buffer = NULL; Py_ssize_t len, qlen; + const char *encoding = default_encoding; + PyObject *rv = NULL; /* if the wrapped object is an unicode object we can encode it to match - self->encoding but if the encoding is not specified we don't know what + conn->encoding but if the encoding is not specified we don't know what to do and we raise an exception */ + if (self->conn) { + encoding = self->conn->codec; + } - Dprintf("qstring_quote: encoding to %s", self->encoding); + Dprintf("qstring_quote: encoding to %s", encoding); - if (PyUnicode_Check(self->wrapped) && self->encoding) { - str = PyUnicode_AsEncodedString(self->wrapped, self->encoding, NULL); + if (PyUnicode_Check(self->wrapped) && encoding) { + str = PyUnicode_AsEncodedString(self->wrapped, encoding, NULL); Dprintf("qstring_quote: got encoded object at %p", str); - if (str == NULL) return NULL; + if (str == NULL) goto exit; } #if PY_MAJOR_VERSION < 3 @@ -68,30 +74,28 @@ qstring_quote(qstringObject *self) else { PyErr_SetString(PyExc_TypeError, "can't quote non-string object (or missing encoding)"); - return NULL; + goto exit; } /* encode the string into buffer */ Bytes_AsStringAndSize(str, &s, &len); if (!(buffer = psycopg_escape_string(self->conn, s, len, NULL, &qlen))) { - Py_DECREF(str); - PyErr_NoMemory(); - return NULL; + goto exit; } if (qlen > (size_t) PY_SSIZE_T_MAX) { PyErr_SetString(PyExc_IndexError, "PG buffer too large to fit in Python buffer."); - PyMem_Free(buffer); - Py_DECREF(str); - return NULL; + goto exit; } - self->buffer = Bytes_FromStringAndSize(buffer, qlen); - PyMem_Free(buffer); - Py_DECREF(str); + rv = Bytes_FromStringAndSize(buffer, qlen); - return self->buffer; +exit: + PyMem_Free(buffer); + Py_XDECREF(str); + + return rv; } /* qstring_str, qstring_getquoted - return result of quoting */ @@ -100,7 +104,7 @@ static PyObject * qstring_getquoted(qstringObject *self, PyObject *args) { if (self->buffer == NULL) { - qstring_quote(self); + self->buffer = qstring_quote(self); } Py_XINCREF(self->buffer); return self->buffer; @@ -115,25 +119,16 @@ qstring_str(qstringObject *self) static PyObject * qstring_prepare(qstringObject *self, PyObject *args) { - PyObject *conn; + connectionObject *conn; if (!PyArg_ParseTuple(args, "O!", &connectionType, &conn)) return NULL; - /* we bother copying the encoding only if the wrapped string is unicode, - we don't need the encoding if that's not the case */ - if (PyUnicode_Check(self->wrapped)) { - if (self->encoding) free(self->encoding); - self->encoding = strdup(((connectionObject *)conn)->codec); - Dprintf("qstring_prepare: set encoding to %s", self->encoding); - } - Py_CLEAR(self->conn); Py_INCREF(conn); self->conn = conn; - Py_INCREF(Py_None); - return Py_None; + Py_RETURN_NONE; } static PyObject * @@ -152,6 +147,18 @@ qstring_conform(qstringObject *self, PyObject *args) return res; } +static PyObject * +qstring_get_encoding(qstringObject *self) +{ + const char *encoding = default_encoding; + + if (self->conn) { + encoding = self->conn->codec; + } + + return Text_FromUTF8(encoding); +} + /** the QuotedString object **/ /* object member list */ @@ -159,7 +166,6 @@ qstring_conform(qstringObject *self, PyObject *args) static struct PyMemberDef qstringObject_members[] = { {"adapted", T_OBJECT, offsetof(qstringObject, wrapped), READONLY}, {"buffer", T_OBJECT, offsetof(qstringObject, buffer), READONLY}, - {"encoding", T_STRING, offsetof(qstringObject, encoding), READONLY}, {NULL} }; @@ -174,22 +180,24 @@ static PyMethodDef qstringObject_methods[] = { {NULL} /* Sentinel */ }; +static PyGetSetDef qstringObject_getsets[] = { + { "encoding", + (getter)qstring_get_encoding, + (setter)NULL, + "current encoding of the adapter" }, + {NULL} +}; + /* initialization and finalization methods */ static int -qstring_setup(qstringObject *self, PyObject *str, const char *enc) +qstring_setup(qstringObject *self, PyObject *str) { Dprintf("qstring_setup: init qstring object at %p, refcnt = " FORMAT_CODE_PY_SSIZE_T, self, Py_REFCNT(self) ); - self->buffer = NULL; - self->conn = NULL; - - /* FIXME: remove this orrible strdup */ - if (enc) self->encoding = strdup(enc); - Py_INCREF(str); self->wrapped = str; @@ -200,17 +208,6 @@ qstring_setup(qstringObject *self, PyObject *str, const char *enc) return 0; } -static int -qstring_traverse(PyObject *obj, visitproc visit, void *arg) -{ - qstringObject *self = (qstringObject *)obj; - - Py_VISIT(self->wrapped); - Py_VISIT(self->buffer); - Py_VISIT(self->conn); - return 0; -} - static void qstring_dealloc(PyObject* obj) { @@ -220,8 +217,6 @@ qstring_dealloc(PyObject* obj) Py_CLEAR(self->buffer); Py_CLEAR(self->conn); - if (self->encoding) free(self->encoding); - Dprintf("qstring_dealloc: deleted qstring object at %p, refcnt = " FORMAT_CODE_PY_SSIZE_T, obj, Py_REFCNT(obj) @@ -234,12 +229,11 @@ static int qstring_init(PyObject *obj, PyObject *args, PyObject *kwds) { PyObject *str; - const char *enc = "latin-1"; /* default encoding as in Python */ - if (!PyArg_ParseTuple(args, "O|s", &str, &enc)) + if (!PyArg_ParseTuple(args, "O", &str)) return -1; - return qstring_setup((qstringObject *)obj, str, enc); + return qstring_setup((qstringObject *)obj, str); } static PyObject * @@ -248,12 +242,6 @@ qstring_new(PyTypeObject *type, PyObject *args, PyObject *kwds) return type->tp_alloc(type, 0); } -static void -qstring_del(PyObject* self) -{ - PyObject_GC_Del(self); -} - static PyObject * qstring_repr(qstringObject *self) { @@ -264,66 +252,46 @@ qstring_repr(qstringObject *self) /* object type */ #define qstringType_doc \ -"QuotedString(str, enc) -> new quoted object with 'enc' encoding" +"QuotedString(str) -> new quoted object" PyTypeObject qstringType = { PyVarObject_HEAD_INIT(NULL, 0) "psycopg2._psycopg.QuotedString", - sizeof(qstringObject), - 0, + sizeof(qstringObject), 0, qstring_dealloc, /*tp_dealloc*/ 0, /*tp_print*/ 0, /*tp_getattr*/ 0, /*tp_setattr*/ - 0, /*tp_compare*/ (reprfunc)qstring_repr, /*tp_repr*/ 0, /*tp_as_number*/ 0, /*tp_as_sequence*/ 0, /*tp_as_mapping*/ 0, /*tp_hash */ - 0, /*tp_call*/ (reprfunc)qstring_str, /*tp_str*/ 0, /*tp_getattro*/ 0, /*tp_setattro*/ 0, /*tp_as_buffer*/ - - Py_TPFLAGS_DEFAULT|Py_TPFLAGS_BASETYPE|Py_TPFLAGS_HAVE_GC, /*tp_flags*/ - + Py_TPFLAGS_DEFAULT|Py_TPFLAGS_BASETYPE, /*tp_flags*/ qstringType_doc, /*tp_doc*/ - - qstring_traverse, /*tp_traverse*/ + 0, /*tp_traverse*/ 0, /*tp_clear*/ - 0, /*tp_richcompare*/ 0, /*tp_weaklistoffset*/ - 0, /*tp_iter*/ 0, /*tp_iternext*/ - - /* Attribute descriptor and subclassing stuff */ - qstringObject_methods, /*tp_methods*/ qstringObject_members, /*tp_members*/ - 0, /*tp_getset*/ + qstringObject_getsets, /*tp_getset*/ 0, /*tp_base*/ 0, /*tp_dict*/ - 0, /*tp_descr_get*/ 0, /*tp_descr_set*/ 0, /*tp_dictoffset*/ - qstring_init, /*tp_init*/ - 0, /*tp_alloc will be set to PyType_GenericAlloc in module init*/ + 0, /*tp_alloc*/ qstring_new, /*tp_new*/ - (freefunc)qstring_del, /*tp_free Low-level free-memory routine */ - 0, /*tp_is_gc For PyObject_IS_GC */ - 0, /*tp_bases*/ - 0, /*tp_mro method resolution order */ - 0, /*tp_cache*/ - 0, /*tp_subclasses*/ - 0 /*tp_weaklist*/ }; @@ -333,10 +301,9 @@ PyObject * psyco_QuotedString(PyObject *module, PyObject *args) { PyObject *str; - const char *enc = "latin-1"; /* default encoding as in Python */ - if (!PyArg_ParseTuple(args, "O|s", &str, &enc)) + if (!PyArg_ParseTuple(args, "O", &str)) return NULL; - return PyObject_CallFunction((PyObject *)&qstringType, "Os", str, enc); + return PyObject_CallFunctionObjArgs((PyObject *)&qstringType, str, NULL); } diff --git a/psycopg/adapter_qstring.h b/psycopg/adapter_qstring.h index d825fe0e..0446f276 100644 --- a/psycopg/adapter_qstring.h +++ b/psycopg/adapter_qstring.h @@ -37,13 +37,8 @@ typedef struct { PyObject *wrapped; PyObject *buffer; - /* NOTE: this used to be a PostgreSQL encoding: changed in 2.3.2 to be a - * Python codec name. I don't expect there has been any user for this - * object other than adapting str/unicode, so I don't expect client code - * broken for this reason. */ - char *encoding; - PyObject *conn; + connectionObject *conn; } qstringObject; /* functions exported to psycopgmodule.c */ diff --git a/psycopg/connection.h b/psycopg/connection.h index 01cc6a44..07dfe2e7 100644 --- a/psycopg/connection.h +++ b/psycopg/connection.h @@ -74,7 +74,8 @@ struct connectionObject_notice { const char *message; }; -typedef struct { +/* the typedef is forward-declared in psycopg.h */ +struct connectionObject { PyObject_HEAD pthread_mutex_t lock; /* the global connection lock */ @@ -88,7 +89,7 @@ typedef struct { 2 that something horrible happened */ long int mark; /* number of commits/rollbacks done so far */ int status; /* status of the connection */ - XidObject *tpc_xid; /* Transaction ID in two-phase commit */ + xidObject *tpc_xid; /* Transaction ID in two-phase commit */ long int async; /* 1 means the connection is async */ int protocol; /* protocol version */ @@ -120,7 +121,8 @@ typedef struct { int autocommit; -} connectionObject; + PyObject *cursor_factory; /* default cursor factory from cursor() */ +}; /* map isolation level values into a numeric const */ typedef struct { @@ -134,7 +136,6 @@ HIDDEN int conn_get_standard_conforming_strings(PGconn *pgconn); RAISES_NEG HIDDEN int conn_get_isolation_level(connectionObject *self); HIDDEN int conn_get_protocol_version(PGconn *pgconn); HIDDEN int conn_get_server_version(PGconn *pgconn); -HIDDEN PGcancel *conn_get_cancel(PGconn *pgconn); HIDDEN void conn_notice_process(connectionObject *self); HIDDEN void conn_notice_clean(connectionObject *self); HIDDEN void conn_notifies_process(connectionObject *self); @@ -151,9 +152,9 @@ HIDDEN int conn_set_autocommit(connectionObject *self, int value); RAISES_NEG HIDDEN int conn_switch_isolation_level(connectionObject *self, int level); RAISES_NEG HIDDEN int conn_set_client_encoding(connectionObject *self, const char *enc); HIDDEN int conn_poll(connectionObject *self); -RAISES_NEG HIDDEN int conn_tpc_begin(connectionObject *self, XidObject *xid); +RAISES_NEG HIDDEN int conn_tpc_begin(connectionObject *self, xidObject *xid); RAISES_NEG HIDDEN int conn_tpc_command(connectionObject *self, - const char *cmd, XidObject *xid); + const char *cmd, xidObject *xid); HIDDEN PyObject *conn_tpc_recover(connectionObject *self); /* exception-raising macros */ diff --git a/psycopg/connection_int.c b/psycopg/connection_int.c index ad495750..51c2381f 100644 --- a/psycopg/connection_int.c +++ b/psycopg/connection_int.c @@ -183,7 +183,7 @@ conn_notifies_process(connectionObject *self) if (!(channel = conn_text_from_chars(self, pgn->relname))) { goto error; } if (!(payload = conn_text_from_chars(self, pgn->extra))) { goto error; } - if (!(notify = PyObject_CallFunctionObjArgs((PyObject *)&NotifyType, + if (!(notify = PyObject_CallFunctionObjArgs((PyObject *)¬ifyType, pid, channel, payload, NULL))) { goto error; } @@ -439,10 +439,22 @@ conn_get_server_version(PGconn *pgconn) return (int)PQserverVersion(pgconn); } -PGcancel * -conn_get_cancel(PGconn *pgconn) +/* set up the cancel key of the connection. + * On success return 0, else set an exception and return -1 + */ +RAISES_NEG static int +conn_setup_cancel(connectionObject *self, PGconn *pgconn) { - return PQgetCancel(pgconn); + if (self->cancel) { + PQfreeCancel(self->cancel); + } + + if (!(self->cancel = PQgetCancel(self->pgconn))) { + PyErr_SetString(OperationalError, "can't get cancellation key"); + return -1; + } + + return 0; } @@ -486,9 +498,7 @@ conn_setup(connectionObject *self, PGconn *pgconn) return -1; } - self->cancel = conn_get_cancel(self->pgconn); - if (self->cancel == NULL) { - PyErr_SetString(OperationalError, "can't get cancellation key"); + if (0 > conn_setup_cancel(self, pgconn)) { return -1; } @@ -788,10 +798,8 @@ _conn_poll_setup_async(connectionObject *self) if (0 > conn_read_encoding(self, self->pgconn)) { break; } - self->cancel = conn_get_cancel(self->pgconn); - if (self->cancel == NULL) { - PyErr_SetString(OperationalError, "can't get cancellation key"); - break; + if (0 > conn_setup_cancel(self, self->pgconn)) { + return -1; } /* asynchronous connections always use isolation level 0, the user is @@ -890,7 +898,7 @@ conn_poll(connectionObject *self) } curs = (cursorObject *)py_curs; - IFCLEARPGRES(curs->pgres); + CLEARPGRES(curs->pgres); curs->pgres = pq_get_last_result(self); /* fetch the tuples (if there are any) and build the result. We @@ -918,7 +926,8 @@ conn_poll(connectionObject *self) void conn_close(connectionObject *self) { - if (self->closed) { + /* a connection with closed == 2 still requires cleanup */ + if (self->closed == 1) { return; } @@ -936,7 +945,7 @@ conn_close(connectionObject *self) void conn_close_locked(connectionObject *self) { - if (self->closed) { + if (self->closed == 1) { return; } @@ -957,8 +966,6 @@ void conn_close_locked(connectionObject *self) PQfinish(self->pgconn); self->pgconn = NULL; Dprintf("conn_close: PQfinish called"); - PQfreeCancel(self->cancel); - self->cancel = NULL; } } @@ -1213,7 +1220,7 @@ exit: * until PREPARE. */ RAISES_NEG int -conn_tpc_begin(connectionObject *self, XidObject *xid) +conn_tpc_begin(connectionObject *self, xidObject *xid) { PGresult *pgres = NULL; char *error = NULL; @@ -1247,7 +1254,7 @@ conn_tpc_begin(connectionObject *self, XidObject *xid) * for many commands and for recovered transactions. */ RAISES_NEG int -conn_tpc_command(connectionObject *self, const char *cmd, XidObject *xid) +conn_tpc_command(connectionObject *self, const char *cmd, xidObject *xid) { PGresult *pgres = NULL; char *error = NULL; diff --git a/psycopg/connection_type.c b/psycopg/connection_type.c index c4b88b3e..e854fa51 100644 --- a/psycopg/connection_type.c +++ b/psycopg/connection_type.c @@ -52,63 +52,74 @@ static PyObject * psyco_conn_cursor(connectionObject *self, PyObject *args, PyObject *kwargs) { - PyObject *obj; + PyObject *obj = NULL; + PyObject *rv = NULL; PyObject *name = Py_None; PyObject *factory = (PyObject *)&cursorType; PyObject *withhold = Py_False; + PyObject *scrollable = Py_None; - static char *kwlist[] = {"name", "cursor_factory", "withhold", NULL}; - - if (!PyArg_ParseTupleAndKeywords(args, kwargs, "|OOO", kwlist, - &name, &factory, &withhold)) { - return NULL; - } - - if (PyObject_IsTrue(withhold) && (name == Py_None)) { - PyErr_SetString(ProgrammingError, - "'withhold=True can be specified only for named cursors"); - return NULL; - } + static char *kwlist[] = { + "name", "cursor_factory", "withhold", "scrollable", NULL}; EXC_IF_CONN_CLOSED(self); + if (self->cursor_factory && self->cursor_factory != Py_None) { + factory = self->cursor_factory; + } + + if (!PyArg_ParseTupleAndKeywords( + args, kwargs, "|OOOO", kwlist, + &name, &factory, &withhold, &scrollable)) { + goto exit; + } + if (self->status != CONN_STATUS_READY && self->status != CONN_STATUS_BEGIN && self->status != CONN_STATUS_PREPARED) { PyErr_SetString(OperationalError, "asynchronous connection attempt underway"); - return NULL; + goto exit; } if (name != Py_None && self->async == 1) { PyErr_SetString(ProgrammingError, "asynchronous connections " "cannot produce named cursors"); - return NULL; + goto exit; } Dprintf("psyco_conn_cursor: new %s cursor for connection at %p", (name == Py_None ? "unnamed" : "named"), self); if (!(obj = PyObject_CallFunctionObjArgs(factory, self, name, NULL))) { - return NULL; + goto exit; } if (PyObject_IsInstance(obj, (PyObject *)&cursorType) == 0) { PyErr_SetString(PyExc_TypeError, "cursor factory must be subclass of psycopg2._psycopg.cursor"); - Py_DECREF(obj); - return NULL; + goto exit; } - if (PyObject_IsTrue(withhold)) - ((cursorObject*)obj)->withhold = 1; + if (0 != psyco_curs_withhold_set((cursorObject *)obj, withhold)) { + goto exit; + } + if (0 != psyco_curs_scrollable_set((cursorObject *)obj, scrollable)) { + goto exit; + } Dprintf("psyco_conn_cursor: new cursor at %p: refcnt = " FORMAT_CODE_PY_SSIZE_T, obj, Py_REFCNT(obj) - ); - return obj; + ); + + rv = obj; + obj = NULL; + +exit: + Py_XDECREF(obj); + return rv; } @@ -117,14 +128,13 @@ psyco_conn_cursor(connectionObject *self, PyObject *args, PyObject *kwargs) #define psyco_conn_close_doc "close() -- Close the connection." static PyObject * -psyco_conn_close(connectionObject *self, PyObject *args) +psyco_conn_close(connectionObject *self) { Dprintf("psyco_conn_close: closing connection at %p", self); conn_close(self); Dprintf("psyco_conn_close: connection at %p closed", self); - Py_INCREF(Py_None); - return Py_None; + Py_RETURN_NONE; } @@ -133,7 +143,7 @@ psyco_conn_close(connectionObject *self, PyObject *args) #define psyco_conn_commit_doc "commit() -- Commit all changes to database." static PyObject * -psyco_conn_commit(connectionObject *self, PyObject *args) +psyco_conn_commit(connectionObject *self) { EXC_IF_CONN_CLOSED(self); EXC_IF_CONN_ASYNC(self, commit); @@ -142,8 +152,7 @@ psyco_conn_commit(connectionObject *self, PyObject *args) if (conn_commit(self) < 0) return NULL; - Py_INCREF(Py_None); - return Py_None; + Py_RETURN_NONE; } @@ -153,7 +162,7 @@ psyco_conn_commit(connectionObject *self, PyObject *args) "rollback() -- Roll back all changes done to database." static PyObject * -psyco_conn_rollback(connectionObject *self, PyObject *args) +psyco_conn_rollback(connectionObject *self) { EXC_IF_CONN_CLOSED(self); EXC_IF_CONN_ASYNC(self, rollback); @@ -162,8 +171,7 @@ psyco_conn_rollback(connectionObject *self, PyObject *args) if (conn_rollback(self) < 0) return NULL; - Py_INCREF(Py_None); - return Py_None; + Py_RETURN_NONE; } @@ -176,7 +184,7 @@ psyco_conn_xid(connectionObject *self, PyObject *args, PyObject *kwargs) EXC_IF_CONN_CLOSED(self); EXC_IF_TPC_NOT_SUPPORTED(self); - return PyObject_Call((PyObject *)&XidType, args, kwargs); + return PyObject_Call((PyObject *)&xidType, args, kwargs); } @@ -187,7 +195,7 @@ static PyObject * psyco_conn_tpc_begin(connectionObject *self, PyObject *args) { PyObject *rv = NULL; - XidObject *xid = NULL; + xidObject *xid = NULL; PyObject *oxid; EXC_IF_CONN_CLOSED(self); @@ -227,7 +235,7 @@ exit: "tpc_prepare() -- perform the first phase of a two-phase transaction." static PyObject * -psyco_conn_tpc_prepare(connectionObject *self, PyObject *args) +psyco_conn_tpc_prepare(connectionObject *self) { EXC_IF_CONN_CLOSED(self); EXC_IF_CONN_ASYNC(self, tpc_prepare); @@ -247,8 +255,7 @@ psyco_conn_tpc_prepare(connectionObject *self, PyObject *args) * can be performed until commit. */ self->status = CONN_STATUS_PREPARED; - Py_INCREF(Py_None); - return Py_None; + Py_RETURN_NONE; } @@ -279,7 +286,7 @@ _psyco_conn_tpc_finish(connectionObject *self, PyObject *args, _finish_f opc_f, char *tpc_cmd) { PyObject *oxid = NULL; - XidObject *xid = NULL; + xidObject *xid = NULL; PyObject *rv = NULL; if (!PyArg_ParseTuple(args, "|O", &oxid)) { goto exit; } @@ -371,7 +378,7 @@ psyco_conn_tpc_rollback(connectionObject *self, PyObject *args) "tpc_recover() -- returns a list of pending transaction IDs." static PyObject * -psyco_conn_tpc_recover(connectionObject *self, PyObject *args) +psyco_conn_tpc_recover(connectionObject *self) { EXC_IF_CONN_CLOSED(self); EXC_IF_CONN_ASYNC(self, tpc_recover); @@ -382,6 +389,54 @@ psyco_conn_tpc_recover(connectionObject *self, PyObject *args) } +#define psyco_conn_enter_doc \ +"__enter__ -> self" + +static PyObject * +psyco_conn_enter(connectionObject *self) +{ + EXC_IF_CONN_CLOSED(self); + + Py_INCREF(self); + return (PyObject *)self; +} + + +#define psyco_conn_exit_doc \ +"__exit__ -- commit if no exception, else roll back" + +static PyObject * +psyco_conn_exit(connectionObject *self, PyObject *args) +{ + PyObject *type, *name, *tb; + PyObject *tmp = NULL; + PyObject *rv = NULL; + + if (!PyArg_ParseTuple(args, "OOO", &type, &name, &tb)) { + goto exit; + } + + if (type == Py_None) { + if (!(tmp = PyObject_CallMethod((PyObject *)self, "commit", NULL))) { + goto exit; + } + } else { + if (!(tmp = PyObject_CallMethod((PyObject *)self, "rollback", NULL))) { + goto exit; + } + } + + /* success (of the commit or rollback, there may have been an exception in + * the block). Return None to avoid swallowing the exception */ + rv = Py_None; + Py_INCREF(rv); + +exit: + Py_XDECREF(tmp); + return rv; +} + + #ifdef PSYCOPG_EXTENSIONS @@ -528,8 +583,7 @@ psyco_conn_set_session(connectionObject *self, PyObject *args, PyObject *kwargs) return NULL; } - Py_INCREF(Py_None); - return Py_None; + Py_RETURN_NONE; } @@ -611,8 +665,7 @@ psyco_conn_set_isolation_level(connectionObject *self, PyObject *args) return NULL; } - Py_INCREF(Py_None); - return Py_None; + Py_RETURN_NONE; } /* set_client_encoding method - set client encoding */ @@ -645,7 +698,7 @@ psyco_conn_set_client_encoding(connectionObject *self, PyObject *args) "get_transaction_status() -- Get backend transaction status." static PyObject * -psyco_conn_get_transaction_status(connectionObject *self, PyObject *args) +psyco_conn_get_transaction_status(connectionObject *self) { EXC_IF_CONN_CLOSED(self); @@ -675,8 +728,7 @@ psyco_conn_get_parameter_status(connectionObject *self, PyObject *args) val = PQparameterStatus(self->pgconn, param); if (!val) { - Py_INCREF(Py_None); - return Py_None; + Py_RETURN_NONE; } return conn_text_from_chars(self, val); } @@ -749,7 +801,7 @@ psyco_conn_lobject(connectionObject *self, PyObject *args, PyObject *keywds) "get_backend_pid() -- Get backend process id." static PyObject * -psyco_conn_get_backend_pid(connectionObject *self, PyObject *args) +psyco_conn_get_backend_pid(connectionObject *self) { EXC_IF_CONN_CLOSED(self); @@ -762,7 +814,7 @@ psyco_conn_get_backend_pid(connectionObject *self, PyObject *args) "reset() -- Reset current connection to defaults." static PyObject * -psyco_conn_reset(connectionObject *self, PyObject *args) +psyco_conn_reset(connectionObject *self) { int res; @@ -776,8 +828,7 @@ psyco_conn_reset(connectionObject *self, PyObject *args) if (res < 0) return NULL; - Py_INCREF(Py_None); - return Py_None; + Py_RETURN_NONE; } static PyObject * @@ -790,7 +841,7 @@ psyco_conn_get_exception(PyObject *self, void *closure) } static PyObject * -psyco_conn_poll(connectionObject *self, PyObject *args) +psyco_conn_poll(connectionObject *self) { int res; @@ -812,7 +863,7 @@ psyco_conn_poll(connectionObject *self, PyObject *args) "fileno() -> int -- Return file descriptor associated to database connection." static PyObject * -psyco_conn_fileno(connectionObject *self, PyObject *args) +psyco_conn_fileno(connectionObject *self) { long int socket; @@ -831,7 +882,7 @@ psyco_conn_fileno(connectionObject *self, PyObject *args) "executing an asynchronous operation." static PyObject * -psyco_conn_isexecuting(connectionObject *self, PyObject *args) +psyco_conn_isexecuting(connectionObject *self) { /* synchronous connections will always return False */ if (self->async == 0) { @@ -863,7 +914,7 @@ psyco_conn_isexecuting(connectionObject *self, PyObject *args) "cancel() -- cancel the current operation" static PyObject * -psyco_conn_cancel(connectionObject *self, PyObject *args) +psyco_conn_cancel(connectionObject *self) { char errbuf[256]; @@ -884,8 +935,7 @@ psyco_conn_cancel(connectionObject *self, PyObject *args) PyErr_SetString(OperationalError, errbuf); return NULL; } - Py_INCREF(Py_None); - return Py_None; + Py_RETURN_NONE; } #endif /* PSYCOPG_EXTENSIONS */ @@ -917,6 +967,10 @@ static struct PyMethodDef connectionObject_methods[] = { METH_VARARGS, psyco_conn_tpc_rollback_doc}, {"tpc_recover", (PyCFunction)psyco_conn_tpc_recover, METH_NOARGS, psyco_conn_tpc_recover_doc}, + {"__enter__", (PyCFunction)psyco_conn_enter, + METH_NOARGS, psyco_conn_enter_doc}, + {"__exit__", (PyCFunction)psyco_conn_exit, + METH_VARARGS, psyco_conn_exit_doc}, #ifdef PSYCOPG_EXTENSIONS {"set_session", (PyCFunction)psyco_conn_set_session, METH_VARARGS|METH_KEYWORDS, psyco_conn_set_session_doc}, @@ -963,6 +1017,8 @@ static struct PyMemberDef connectionObject_members[] = { {"status", T_INT, offsetof(connectionObject, status), READONLY, "The current transaction status."}, + {"cursor_factory", T_OBJECT, offsetof(connectionObject, cursor_factory), 0, + "Default cursor_factory for cursor()."}, {"string_types", T_OBJECT, offsetof(connectionObject, string_types), READONLY, "A set of typecasters to convert textual values."}, {"binary_types", T_OBJECT, offsetof(connectionObject, binary_types), READONLY, @@ -1019,10 +1075,7 @@ connection_setup(connectionObject *self, const char *dsn, long int async) self, async, Py_REFCNT(self) ); - if (!(self->dsn = strdup(dsn))) { - PyErr_NoMemory(); - goto exit; - } + if (0 > psycopg_strdup(&self->dsn, dsn, 0)) { goto exit; } if (!(self->notice_list = PyList_New(0))) { goto exit; } if (!(self->notifies = PyList_New(0))) { goto exit; } self->async = async; @@ -1057,26 +1110,9 @@ exit: return res; } -static void -connection_dealloc(PyObject* obj) +static int +connection_clear(connectionObject *self) { - connectionObject *self = (connectionObject *)obj; - - if (self->weakreflist) { - PyObject_ClearWeakRefs(obj); - } - - PyObject_GC_UnTrack(self); - - if (self->closed == 0) conn_close(self); - - conn_notice_clean(self); - - if (self->dsn) free(self->dsn); - PyMem_Free(self->encoding); - PyMem_Free(self->codec); - if (self->critical) free(self->critical); - Py_CLEAR(self->tpc_xid); Py_CLEAR(self->async_cursor); Py_CLEAR(self->notice_list); @@ -1084,6 +1120,31 @@ connection_dealloc(PyObject* obj) Py_CLEAR(self->notifies); Py_CLEAR(self->string_types); Py_CLEAR(self->binary_types); + return 0; +} + +static void +connection_dealloc(PyObject* obj) +{ + connectionObject *self = (connectionObject *)obj; + + conn_close(self); + + PyObject_GC_UnTrack(self); + + if (self->weakreflist) { + PyObject_ClearWeakRefs(obj); + } + + conn_notice_clean(self); + + PyMem_Free(self->dsn); + PyMem_Free(self->encoding); + PyMem_Free(self->codec); + if (self->critical) free(self->critical); + if (self->cancel) PQfreeCancel(self->cancel); + + connection_clear(self); pthread_mutex_destroy(&(self->lock)); @@ -1114,12 +1175,6 @@ connection_new(PyTypeObject *type, PyObject *args, PyObject *kwds) return type->tp_alloc(type, 0); } -static void -connection_del(PyObject* self) -{ - PyObject_GC_Del(self); -} - static PyObject * connection_repr(connectionObject *self) { @@ -1154,8 +1209,7 @@ connection_traverse(connectionObject *self, visitproc visit, void *arg) PyTypeObject connectionType = { PyVarObject_HEAD_INIT(NULL, 0) "psycopg2._psycopg.connection", - sizeof(connectionObject), - 0, + sizeof(connectionObject), 0, connection_dealloc, /*tp_dealloc*/ 0, /*tp_print*/ 0, /*tp_getattr*/ @@ -1166,47 +1220,30 @@ PyTypeObject connectionType = { 0, /*tp_as_sequence*/ 0, /*tp_as_mapping*/ 0, /*tp_hash */ - 0, /*tp_call*/ (reprfunc)connection_repr, /*tp_str*/ 0, /*tp_getattro*/ 0, /*tp_setattro*/ 0, /*tp_as_buffer*/ - Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE | Py_TPFLAGS_HAVE_GC | Py_TPFLAGS_HAVE_WEAKREFS, /*tp_flags*/ connectionType_doc, /*tp_doc*/ - (traverseproc)connection_traverse, /*tp_traverse*/ - 0, /*tp_clear*/ - + (inquiry)connection_clear, /*tp_clear*/ 0, /*tp_richcompare*/ offsetof(connectionObject, weakreflist), /* tp_weaklistoffset */ - 0, /*tp_iter*/ 0, /*tp_iternext*/ - - /* Attribute descriptor and subclassing stuff */ - connectionObject_methods, /*tp_methods*/ connectionObject_members, /*tp_members*/ connectionObject_getsets, /*tp_getset*/ 0, /*tp_base*/ 0, /*tp_dict*/ - 0, /*tp_descr_get*/ 0, /*tp_descr_set*/ 0, /*tp_dictoffset*/ - connection_init, /*tp_init*/ - 0, /*tp_alloc will be set to PyType_GenericAlloc in module init*/ + 0, /*tp_alloc*/ connection_new, /*tp_new*/ - (freefunc)connection_del, /*tp_free Low-level free-memory routine */ - 0, /*tp_is_gc For PyObject_IS_GC */ - 0, /*tp_bases*/ - 0, /*tp_mro method resolution order */ - 0, /*tp_cache*/ - 0, /*tp_subclasses*/ - 0 /*tp_weaklist*/ }; diff --git a/psycopg/cursor.h b/psycopg/cursor.h index 723bd170..7940e7b4 100644 --- a/psycopg/cursor.h +++ b/psycopg/cursor.h @@ -44,6 +44,11 @@ struct cursorObject { int notuples:1; /* 1 if the command was not a SELECT query */ int withhold:1; /* 1 if the cursor is named and uses WITH HOLD */ + int scrollable; /* 1 if the cursor is named and SCROLLABLE, + 0 if not scrollable + -1 if undefined (PG may decide scrollable or not) + */ + long int rowcount; /* number of rows affected by last execute */ long int columns; /* number of columns fetched from the db */ long int arraysize; /* how many rows should fetchmany() return */ @@ -84,9 +89,11 @@ struct cursorObject { }; -/* C-callable functions in cursor_int.c and cursor_ext.c */ +/* C-callable functions in cursor_int.c and cursor_type.c */ BORROWED HIDDEN PyObject *curs_get_cast(cursorObject *self, PyObject *oid); HIDDEN void curs_reset(cursorObject *self); +HIDDEN int psyco_curs_withhold_set(cursorObject *self, PyObject *pyvalue); +HIDDEN int psyco_curs_scrollable_set(cursorObject *self, PyObject *pyvalue); /* exception-raising macros */ #define EXC_IF_CURS_CLOSED(self) \ diff --git a/psycopg/cursor_int.c b/psycopg/cursor_int.c index 1ac3f550..dd4c0d7d 100644 --- a/psycopg/cursor_int.c +++ b/psycopg/cursor_int.c @@ -72,19 +72,11 @@ curs_get_cast(cursorObject *self, PyObject *oid) void curs_reset(cursorObject *self) { - PyObject *tmp; - /* initialize some variables to default values */ self->notuples = 1; self->rowcount = -1; self->row = 0; - tmp = self->description; - Py_INCREF(Py_None); - self->description = Py_None; - Py_XDECREF(tmp); - - tmp = self->casts; - self->casts = NULL; - Py_XDECREF(tmp); + Py_CLEAR(self->description); + Py_CLEAR(self->casts); } diff --git a/psycopg/cursor_type.c b/psycopg/cursor_type.c index 3f4d045a..8fc32d87 100644 --- a/psycopg/cursor_type.c +++ b/psycopg/cursor_type.c @@ -50,7 +50,7 @@ extern PyObject *pyPsycopgTzFixedOffsetTimezone; "close() -- Close the cursor." static PyObject * -psyco_curs_close(cursorObject *self, PyObject *args) +psyco_curs_close(cursorObject *self) { EXC_IF_ASYNC_IN_PROGRESS(self, close); @@ -70,8 +70,7 @@ psyco_curs_close(cursorObject *self, PyObject *args) Dprintf("psyco_curs_close: cursor at %p closed", self); exit: - Py_INCREF(Py_None); - return Py_None; + Py_RETURN_NONE; } @@ -118,7 +117,7 @@ _mogrify(PyObject *var, PyObject *fmt, cursorObject *curs, PyObject **new) if (kind == 2) { Py_XDECREF(n); psyco_set_error(ProgrammingError, curs, - "argument formats can't be mixed", NULL, NULL); + "argument formats can't be mixed"); return -1; } kind = 1; @@ -190,7 +189,7 @@ _mogrify(PyObject *var, PyObject *fmt, cursorObject *curs, PyObject **new) /* we found %( but not a ) */ Py_XDECREF(n); psyco_set_error(ProgrammingError, curs, - "incomplete placeholder: '%(' without ')'", NULL, NULL); + "incomplete placeholder: '%(' without ')'"); return -1; } c = d + 1; /* after the ) */ @@ -205,7 +204,7 @@ _mogrify(PyObject *var, PyObject *fmt, cursorObject *curs, PyObject **new) if (kind == 1) { Py_XDECREF(n); psyco_set_error(ProgrammingError, curs, - "argument formats can't be mixed", NULL, NULL); + "argument formats can't be mixed"); return -1; } kind = 2; @@ -267,7 +266,7 @@ static PyObject *_psyco_curs_validate_sql_basic( if (!sql || !PyObject_IsTrue(sql)) { psyco_set_error(ProgrammingError, self, - "can't execute an empty query", NULL, NULL); + "can't execute an empty query"); goto fail; } @@ -338,8 +337,7 @@ _psyco_curs_merge_query_args(cursorObject *self, if (!strcmp(s, "not enough arguments for format string") || !strcmp(s, "not all arguments converted")) { Dprintf("psyco_curs_execute: -> got a match"); - psyco_set_error(ProgrammingError, self, - s, NULL, NULL); + psyco_set_error(ProgrammingError, self, s); pe = 1; } @@ -371,6 +369,7 @@ _psyco_curs_execute(cursorObject *self, int res = -1; int tmp; PyObject *fquery, *cvt = NULL; + const char *scroll; operation = _psyco_curs_validate_sql_basic(self, operation); @@ -379,13 +378,8 @@ _psyco_curs_execute(cursorObject *self, if (operation == NULL) { goto exit; } - IFCLEARPGRES(self->pgres); - - if (self->query) { - Py_DECREF(self->query); - self->query = NULL; - } - + CLEARPGRES(self->pgres); + Py_CLEAR(self->query); Dprintf("psyco_curs_execute: starting execution of new query"); /* here we are, and we have a sequence or a dictionary filled with @@ -397,6 +391,21 @@ _psyco_curs_execute(cursorObject *self, if (0 > _mogrify(vars, operation, self, &cvt)) { goto exit; } } + switch (self->scrollable) { + case -1: + scroll = ""; + break; + case 0: + scroll = "NO SCROLL "; + break; + case 1: + scroll = "SCROLL "; + break; + default: + PyErr_SetString(InternalError, "unexpected scrollable value"); + goto exit; + } + if (vars && cvt) { if (!(fquery = _psyco_curs_merge_query_args(self, operation, cvt))) { goto exit; @@ -404,8 +413,9 @@ _psyco_curs_execute(cursorObject *self, if (self->name != NULL) { self->query = Bytes_FromFormat( - "DECLARE \"%s\" CURSOR %s HOLD FOR %s", + "DECLARE \"%s\" %sCURSOR %s HOLD FOR %s", self->name, + scroll, self->withhold ? "WITH" : "WITHOUT", Bytes_AS_STRING(fquery)); Py_DECREF(fquery); @@ -417,8 +427,9 @@ _psyco_curs_execute(cursorObject *self, else { if (self->name != NULL) { self->query = Bytes_FromFormat( - "DECLARE \"%s\" CURSOR %s HOLD FOR %s", + "DECLARE \"%s\" %sCURSOR %s HOLD FOR %s", self->name, + scroll, self->withhold ? "WITH" : "WITHOUT", Bytes_AS_STRING(operation)); } @@ -462,15 +473,14 @@ psyco_curs_execute(cursorObject *self, PyObject *args, PyObject *kwargs) } if (self->name != NULL) { - if (self->query != Py_None) { + if (self->query) { psyco_set_error(ProgrammingError, self, - "can't call .execute() on named cursors more than once", - NULL, NULL); + "can't call .execute() on named cursors more than once"); return NULL; } if (self->conn->autocommit) { psyco_set_error(ProgrammingError, self, - "can't use a named cursor outside of transactions", NULL, NULL); + "can't use a named cursor outside of transactions"); return NULL; } EXC_IF_NO_MARK(self); @@ -485,8 +495,7 @@ psyco_curs_execute(cursorObject *self, PyObject *args, PyObject *kwargs) } /* success */ - Py_INCREF(Py_None); - return Py_None; + Py_RETURN_NONE; } #define psyco_curs_executemany_doc \ @@ -515,7 +524,7 @@ psyco_curs_executemany(cursorObject *self, PyObject *args, PyObject *kwargs) if (self->name != NULL) { psyco_set_error(ProgrammingError, self, - "can't call .executemany() on named cursors", NULL, NULL); + "can't call .executemany() on named cursors"); return NULL; } @@ -542,8 +551,7 @@ psyco_curs_executemany(cursorObject *self, PyObject *args, PyObject *kwargs) self->rowcount = rowcount; if (!PyErr_Occurred()) { - Py_INCREF(Py_None); - return Py_None; + Py_RETURN_NONE; } else { return NULL; @@ -743,7 +751,7 @@ exit: } static PyObject * -psyco_curs_fetchone(cursorObject *self, PyObject *args) +psyco_curs_fetchone(cursorObject *self) { PyObject *res; @@ -767,8 +775,7 @@ psyco_curs_fetchone(cursorObject *self, PyObject *args) if (self->row >= self->rowcount) { /* we exausted available data: return None */ - Py_INCREF(Py_None); - return Py_None; + Py_RETURN_NONE; } res = _psyco_curs_buildrow(self, self->row); @@ -779,7 +786,7 @@ psyco_curs_fetchone(cursorObject *self, PyObject *args) if (self->row >= self->rowcount && self->conn->async_cursor && PyWeakref_GetObject(self->conn->async_cursor) == (PyObject*)self) - IFCLEARPGRES(self->pgres); + CLEARPGRES(self->pgres); return res; } @@ -826,7 +833,7 @@ psyco_curs_next_named(cursorObject *self) if (self->row >= self->rowcount && self->conn->async_cursor && PyWeakref_GetObject(self->conn->async_cursor) == (PyObject*)self) - IFCLEARPGRES(self->pgres); + CLEARPGRES(self->pgres); return res; } @@ -911,7 +918,7 @@ psyco_curs_fetchmany(cursorObject *self, PyObject *args, PyObject *kwords) if (self->row >= self->rowcount && self->conn->async_cursor && PyWeakref_GetObject(self->conn->async_cursor) == (PyObject*)self) - IFCLEARPGRES(self->pgres); + CLEARPGRES(self->pgres); /* success */ rv = list; @@ -935,7 +942,7 @@ exit: "Return `!None` when no more data is available.\n" static PyObject * -psyco_curs_fetchall(cursorObject *self, PyObject *args) +psyco_curs_fetchall(cursorObject *self) { int i, size; PyObject *list = NULL; @@ -980,7 +987,7 @@ psyco_curs_fetchall(cursorObject *self, PyObject *args) if (self->row >= self->rowcount && self->conn->async_cursor && PyWeakref_GetObject(self->conn->async_cursor) == (PyObject*)self) - IFCLEARPGRES(self->pgres); + CLEARPGRES(self->pgres); /* success */ rv = list; @@ -1020,7 +1027,7 @@ psyco_curs_callproc(cursorObject *self, PyObject *args) if (self->name != NULL) { psyco_set_error(ProgrammingError, self, - "can't call .callproc() on named cursors", NULL, NULL); + "can't call .callproc() on named cursors"); goto exit; } @@ -1067,7 +1074,7 @@ exit: "sets) and will raise a NotSupportedError exception." static PyObject * -psyco_curs_nextset(cursorObject *self, PyObject *args) +psyco_curs_nextset(cursorObject *self) { EXC_IF_CURS_CLOSED(self); @@ -1092,8 +1099,7 @@ psyco_curs_setinputsizes(cursorObject *self, PyObject *args) EXC_IF_CURS_CLOSED(self); - Py_INCREF(Py_None); - return Py_None; + Py_RETURN_NONE; } @@ -1113,8 +1119,7 @@ psyco_curs_setoutputsize(cursorObject *self, PyObject *args) EXC_IF_CURS_CLOSED(self); - Py_INCREF(Py_None); - return Py_None; + Py_RETURN_NONE; } @@ -1147,13 +1152,13 @@ psyco_curs_scroll(cursorObject *self, PyObject *args, PyObject *kwargs) newpos = value; } else { psyco_set_error(ProgrammingError, self, - "scroll mode must be 'relative' or 'absolute'", NULL, NULL); + "scroll mode must be 'relative' or 'absolute'"); return NULL; } if (newpos < 0 || newpos >= self->rowcount ) { psyco_set_error(ProgrammingError, self, - "scroll destination out of bounds", NULL, NULL); + "scroll destination out of bounds"); return NULL; } @@ -1178,8 +1183,43 @@ psyco_curs_scroll(cursorObject *self, PyObject *args, PyObject *kwargs) if (_psyco_curs_prefetch(self) < 0) return NULL; } - Py_INCREF(Py_None); - return Py_None; + Py_RETURN_NONE; +} + + +#define psyco_curs_enter_doc \ +"__enter__ -> self" + +static PyObject * +psyco_curs_enter(cursorObject *self) +{ + Py_INCREF(self); + return (PyObject *)self; +} + +#define psyco_curs_exit_doc \ +"__exit__ -- close the cursor" + +static PyObject * +psyco_curs_exit(cursorObject *self, PyObject *args) +{ + PyObject *tmp = NULL; + PyObject *rv = NULL; + + /* don't care about the arguments here: don't need to parse them */ + + if (!(tmp = PyObject_CallMethod((PyObject *)self, "close", ""))) { + goto exit; + } + + /* success (of curs.close()). + * Return None to avoid swallowing the exception */ + rv = Py_None; + Py_INCREF(rv); + +exit: + Py_XDECREF(tmp); + return rv; } @@ -1311,7 +1351,7 @@ psyco_curs_copy_from(cursorObject *self, PyObject *args, PyObject *kwargs) PyObject *file, *columns = NULL, *res = NULL; if (!PyArg_ParseTupleAndKeywords(args, kwargs, - "O&s|ss" CONV_CODE_PY_SSIZE_T "O", kwlist, + "O&s|ssnO", kwlist, _psyco_curs_has_read_check, &file, &table_name, &sep, &null, &bufsize, &columns)) { @@ -1327,14 +1367,12 @@ psyco_curs_copy_from(cursorObject *self, PyObject *args, PyObject *kwargs) goto exit; if (!(quoted_delimiter = psycopg_escape_string( - (PyObject*)self->conn, sep, 0, NULL, NULL))) { - PyErr_NoMemory(); + self->conn, sep, 0, NULL, NULL))) { goto exit; } if (!(quoted_null = psycopg_escape_string( - (PyObject*)self->conn, null, 0, NULL, NULL))) { - PyErr_NoMemory(); + self->conn, null, 0, NULL, NULL))) { goto exit; } @@ -1423,14 +1461,12 @@ psyco_curs_copy_to(cursorObject *self, PyObject *args, PyObject *kwargs) goto exit; if (!(quoted_delimiter = psycopg_escape_string( - (PyObject*)self->conn, sep, 0, NULL, NULL))) { - PyErr_NoMemory(); + self->conn, sep, 0, NULL, NULL))) { goto exit; } if (!(quoted_null = psycopg_escape_string( - (PyObject*)self->conn, null, 0, NULL, NULL))) { - PyErr_NoMemory(); + self->conn, null, 0, NULL, NULL))) { goto exit; } @@ -1488,7 +1524,7 @@ psyco_curs_copy_expert(cursorObject *self, PyObject *args, PyObject *kwargs) static char *kwlist[] = {"sql", "file", "size", NULL}; if (!PyArg_ParseTupleAndKeywords(args, kwargs, - "OO|" CONV_CODE_PY_SSIZE_T, kwlist, &sql, &file, &bufsize)) + "OO|n", kwlist, &sql, &file, &bufsize)) { return NULL; } EXC_IF_CURS_CLOSED(self); @@ -1567,22 +1603,70 @@ psyco_curs_withhold_get(cursorObject *self) return ret; } -static int +int psyco_curs_withhold_set(cursorObject *self, PyObject *pyvalue) { int value; - if (self->name == NULL) { + if (pyvalue != Py_False && self->name == NULL) { PyErr_SetString(ProgrammingError, "trying to set .withhold on unnamed cursor"); return -1; } - - if ((value = PyObject_IsTrue(pyvalue)) == -1) + + if ((value = PyObject_IsTrue(pyvalue)) == -1) return -1; self->withhold = value; - + + return 0; +} + +#define psyco_curs_scrollable_doc \ +"Set or return cursor use of SCROLL" + +static PyObject * +psyco_curs_scrollable_get(cursorObject *self) +{ + PyObject *ret = NULL; + + switch (self->scrollable) { + case -1: + ret = Py_None; + break; + case 0: + ret = Py_False; + break; + case 1: + ret = Py_True; + break; + default: + PyErr_SetString(InternalError, "unexpected scrollable value"); + } + + Py_XINCREF(ret); + return ret; +} + +int +psyco_curs_scrollable_set(cursorObject *self, PyObject *pyvalue) +{ + int value; + + if (pyvalue != Py_None && self->name == NULL) { + PyErr_SetString(ProgrammingError, + "trying to set .scrollable on unnamed cursor"); + return -1; + } + + if (pyvalue == Py_None) { + value = -1; + } else if ((value = PyObject_IsTrue(pyvalue)) == -1) { + return -1; + } + + self->scrollable = value; + return 0; } @@ -1608,7 +1692,7 @@ cursor_next(PyObject *self) if (NULL == ((cursorObject*)self)->name) { /* we don't parse arguments: psyco_curs_fetchone will do that for us */ - res = psyco_curs_fetchone((cursorObject*)self, NULL); + res = psyco_curs_fetchone((cursorObject*)self); /* convert a None to NULL to signal the end of iteration */ if (res && res == Py_None) { @@ -1650,6 +1734,10 @@ static struct PyMethodDef cursorObject_methods[] = { /* DBAPI-2.0 extensions */ {"scroll", (PyCFunction)psyco_curs_scroll, METH_VARARGS|METH_KEYWORDS, psyco_curs_scroll_doc}, + {"__enter__", (PyCFunction)psyco_curs_enter, + METH_NOARGS, psyco_curs_enter_doc}, + {"__exit__", (PyCFunction)psyco_curs_exit, + METH_VARARGS, psyco_curs_exit_doc}, /* psycopg extensions */ #ifdef PSYCOPG_EXTENSIONS {"cast", (PyCFunction)psyco_curs_cast, @@ -1712,6 +1800,10 @@ static struct PyGetSetDef cursorObject_getsets[] = { (getter)psyco_curs_withhold_get, (setter)psyco_curs_withhold_set, psyco_curs_withhold_doc, NULL }, + { "scrollable", + (getter)psyco_curs_scrollable_get, + (setter)psyco_curs_scrollable_set, + psyco_curs_scrollable_doc, NULL }, #endif {NULL} }; @@ -1740,31 +1832,15 @@ cursor_setup(cursorObject *self, connectionObject *conn, const char *name) Py_INCREF(conn); self->conn = conn; - self->closed = 0; - self->withhold = 0; self->mark = conn->mark; - self->pgres = NULL; self->notuples = 1; self->arraysize = 1; self->itersize = 2000; self->rowcount = -1; self->lastoid = InvalidOid; - self->casts = NULL; - self->notice = NULL; - - self->string_types = NULL; - self->binary_types = NULL; - self->weakreflist = NULL; - - Py_INCREF(Py_None); - self->description = Py_None; - Py_INCREF(Py_None); - self->pgstatus = Py_None; Py_INCREF(Py_None); self->tuple_factory = Py_None; - Py_INCREF(Py_None); - self->query = Py_None; /* default tzinfo factory */ Py_INCREF(pyPsycopgTzFixedOffsetTimezone); @@ -1777,30 +1853,39 @@ cursor_setup(cursorObject *self, connectionObject *conn, const char *name) return 0; } -static void -cursor_dealloc(PyObject* obj) +static int +cursor_clear(cursorObject *self) { - cursorObject *self = (cursorObject *)obj; - - if (self->weakreflist) { - PyObject_ClearWeakRefs(obj); - } - - PyObject_GC_UnTrack(self); - - PyMem_Free(self->name); - Py_CLEAR(self->conn); - Py_CLEAR(self->casts); Py_CLEAR(self->description); Py_CLEAR(self->pgstatus); + Py_CLEAR(self->casts); + Py_CLEAR(self->caster); + Py_CLEAR(self->copyfile); Py_CLEAR(self->tuple_factory); Py_CLEAR(self->tzinfo_factory); Py_CLEAR(self->query); Py_CLEAR(self->string_types); Py_CLEAR(self->binary_types); + return 0; +} - IFCLEARPGRES(self->pgres); +static void +cursor_dealloc(PyObject* obj) +{ + cursorObject *self = (cursorObject *)obj; + + PyObject_GC_UnTrack(self); + + if (self->weakreflist) { + PyObject_ClearWeakRefs(obj); + } + + cursor_clear(self); + + PyMem_Free(self->name); + + CLEARPGRES(self->pgres); Dprintf("cursor_dealloc: deleted cursor object at %p, refcnt = " FORMAT_CODE_PY_SSIZE_T, @@ -1847,12 +1932,6 @@ cursor_new(PyTypeObject *type, PyObject *args, PyObject *kwds) return type->tp_alloc(type, 0); } -static void -cursor_del(PyObject* self) -{ - PyObject_GC_Del(self); -} - static PyObject * cursor_repr(cursorObject *self) { @@ -1886,8 +1965,7 @@ cursor_traverse(cursorObject *self, visitproc visit, void *arg) PyTypeObject cursorType = { PyVarObject_HEAD_INIT(NULL, 0) "psycopg2._psycopg.cursor", - sizeof(cursorObject), - 0, + sizeof(cursorObject), 0, cursor_dealloc, /*tp_dealloc*/ 0, /*tp_print*/ 0, /*tp_getattr*/ @@ -1898,47 +1976,30 @@ PyTypeObject cursorType = { 0, /*tp_as_sequence*/ 0, /*tp_as_mapping*/ 0, /*tp_hash */ - 0, /*tp_call*/ (reprfunc)cursor_repr, /*tp_str*/ 0, /*tp_getattro*/ 0, /*tp_setattro*/ 0, /*tp_as_buffer*/ - Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE | Py_TPFLAGS_HAVE_ITER | Py_TPFLAGS_HAVE_GC | Py_TPFLAGS_HAVE_WEAKREFS , /*tp_flags*/ cursorType_doc, /*tp_doc*/ - (traverseproc)cursor_traverse, /*tp_traverse*/ - 0, /*tp_clear*/ - + (inquiry)cursor_clear, /*tp_clear*/ 0, /*tp_richcompare*/ offsetof(cursorObject, weakreflist), /*tp_weaklistoffset*/ - cursor_iter, /*tp_iter*/ cursor_next, /*tp_iternext*/ - - /* Attribute descriptor and subclassing stuff */ - cursorObject_methods, /*tp_methods*/ cursorObject_members, /*tp_members*/ cursorObject_getsets, /*tp_getset*/ 0, /*tp_base*/ 0, /*tp_dict*/ - 0, /*tp_descr_get*/ 0, /*tp_descr_set*/ 0, /*tp_dictoffset*/ - cursor_init, /*tp_init*/ - 0, /*tp_alloc Will be set to PyType_GenericAlloc in module init*/ + 0, /*tp_alloc*/ cursor_new, /*tp_new*/ - (freefunc)cursor_del, /*tp_free Low-level free-memory routine */ - 0, /*tp_is_gc For PyObject_IS_GC */ - 0, /*tp_bases*/ - 0, /*tp_mro method resolution order */ - 0, /*tp_cache*/ - 0, /*tp_subclasses*/ - 0 /*tp_weaklist*/ }; diff --git a/psycopg/diagnostics.h b/psycopg/diagnostics.h new file mode 100644 index 00000000..9187c500 --- /dev/null +++ b/psycopg/diagnostics.h @@ -0,0 +1,40 @@ +/* diagnostics.c - definition for the psycopg Diagnostics type + * + * Copyright (C) 2013 Matthew Woodcraft + * + * This file is part of psycopg. + * + * psycopg2 is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * In addition, as a special exception, the copyright holders give + * permission to link this program with the OpenSSL library (or with + * modified versions of OpenSSL that use the same license as OpenSSL), + * and distribute linked combinations including the two. + * + * You must obey the GNU Lesser General Public License in all respects for + * all of the code used other than OpenSSL. + * + * psycopg2 is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + */ + +#ifndef PSYCOPG_DIAGNOSTICS_H +#define PSYCOPG_DIAGNOSTICS_H 1 + +#include "psycopg/error.h" + +extern HIDDEN PyTypeObject diagnosticsType; + +typedef struct { + PyObject_HEAD + + errorObject *err; /* exception to retrieve the diagnostics from */ + +} diagnosticsObject; + +#endif /* PSYCOPG_DIAGNOSTICS_H */ diff --git a/psycopg/diagnostics_type.c b/psycopg/diagnostics_type.c new file mode 100644 index 00000000..dbcbf38e --- /dev/null +++ b/psycopg/diagnostics_type.c @@ -0,0 +1,197 @@ +/* diagnostics.c - present information from libpq error responses + * + * Copyright (C) 2013 Matthew Woodcraft + * + * This file is part of psycopg. + * + * psycopg2 is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * In addition, as a special exception, the copyright holders give + * permission to link this program with the OpenSSL library (or with + * modified versions of OpenSSL that use the same license as OpenSSL), + * and distribute linked combinations including the two. + * + * You must obey the GNU Lesser General Public License in all respects for + * all of the code used other than OpenSSL. + * + * psycopg2 is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + */ + +#define PSYCOPG_MODULE +#include "psycopg/psycopg.h" + +#include "psycopg/diagnostics.h" +#include "psycopg/error.h" + +/* These are new in PostgreSQL 9.3. Defining them here so that psycopg2 can + * use them with a 9.3+ server even if compiled against pre-9.3 headers. */ +#ifndef PG_DIAG_SCHEMA_NAME +#define PG_DIAG_SCHEMA_NAME 's' +#endif +#ifndef PG_DIAG_TABLE_NAME +#define PG_DIAG_TABLE_NAME 't' +#endif +#ifndef PG_DIAG_COLUMN_NAME +#define PG_DIAG_COLUMN_NAME 'c' +#endif +#ifndef PG_DIAG_DATATYPE_NAME +#define PG_DIAG_DATATYPE_NAME 'd' +#endif +#ifndef PG_DIAG_CONSTRAINT_NAME +#define PG_DIAG_CONSTRAINT_NAME 'n' +#endif + + +/* Retrieve an error string from the exception's cursor. + * + * If the cursor or its result isn't available, return None. + */ +static PyObject * +psyco_diagnostics_get_field(diagnosticsObject *self, void *closure) +{ + const char *errortext; + + if (!self->err->pgres) { + Py_RETURN_NONE; + } + + errortext = PQresultErrorField(self->err->pgres, (Py_intptr_t) closure); + return error_text_from_chars(self->err, errortext); +} + + +/* object calculated member list */ +static struct PyGetSetDef diagnosticsObject_getsets[] = { + { "severity", (getter)psyco_diagnostics_get_field, NULL, + NULL, (void*) PG_DIAG_SEVERITY }, + { "sqlstate", (getter)psyco_diagnostics_get_field, NULL, + NULL, (void*) PG_DIAG_SQLSTATE }, + { "message_primary", (getter)psyco_diagnostics_get_field, NULL, + NULL, (void*) PG_DIAG_MESSAGE_PRIMARY }, + { "message_detail", (getter)psyco_diagnostics_get_field, NULL, + NULL, (void*) PG_DIAG_MESSAGE_DETAIL }, + { "message_hint", (getter)psyco_diagnostics_get_field, NULL, + NULL, (void*) PG_DIAG_MESSAGE_HINT }, + { "statement_position", (getter)psyco_diagnostics_get_field, NULL, + NULL, (void*) PG_DIAG_STATEMENT_POSITION }, + { "internal_position", (getter)psyco_diagnostics_get_field, NULL, + NULL, (void*) PG_DIAG_INTERNAL_POSITION }, + { "internal_query", (getter)psyco_diagnostics_get_field, NULL, + NULL, (void*) PG_DIAG_INTERNAL_QUERY }, + { "context", (getter)psyco_diagnostics_get_field, NULL, + NULL, (void*) PG_DIAG_CONTEXT }, + { "schema_name", (getter)psyco_diagnostics_get_field, NULL, + NULL, (void*) PG_DIAG_SCHEMA_NAME }, + { "table_name", (getter)psyco_diagnostics_get_field, NULL, + NULL, (void*) PG_DIAG_TABLE_NAME }, + { "column_name", (getter)psyco_diagnostics_get_field, NULL, + NULL, (void*) PG_DIAG_COLUMN_NAME }, + { "datatype_name", (getter)psyco_diagnostics_get_field, NULL, + NULL, (void*) PG_DIAG_DATATYPE_NAME }, + { "constraint_name", (getter)psyco_diagnostics_get_field, NULL, + NULL, (void*) PG_DIAG_CONSTRAINT_NAME }, + { "source_file", (getter)psyco_diagnostics_get_field, NULL, + NULL, (void*) PG_DIAG_SOURCE_FILE }, + { "source_line", (getter)psyco_diagnostics_get_field, NULL, + NULL, (void*) PG_DIAG_SOURCE_LINE }, + { "source_function", (getter)psyco_diagnostics_get_field, NULL, + NULL, (void*) PG_DIAG_SOURCE_FUNCTION }, + {NULL} +}; + +/* initialization and finalization methods */ + +static PyObject * +diagnostics_new(PyTypeObject *type, PyObject *args, PyObject *kwds) +{ + return type->tp_alloc(type, 0); +} + +static int +diagnostics_init(diagnosticsObject *self, PyObject *args, PyObject *kwds) +{ + PyObject *err = NULL; + + if (!PyArg_ParseTuple(args, "O", &err)) + return -1; + + if (!PyObject_TypeCheck(err, &errorType)) { + PyErr_SetString(PyExc_TypeError, + "The argument must be a psycopg2.Error"); + return -1; + } + + Py_INCREF(err); + self->err = (errorObject *)err; + return 0; +} + +static void +diagnostics_dealloc(diagnosticsObject* self) +{ + Py_CLEAR(self->err); + Py_TYPE(self)->tp_free((PyObject *)self); +} + + +/* object type */ + +static const char diagnosticsType_doc[] = + "Details from a database error report.\n\n" + "The object is returned by the `~psycopg2.Error.diag` attribute of the\n" + "`!Error` object.\n" + "All the information available from the |PQresultErrorField|_ function\n" + "are exposed as attributes by the object, e.g. the `!severity` attribute\n" + "returns the `!PG_DIAG_SEVERITY` code. " + "Please refer to the `PostgreSQL documentation`__ for the meaning of all" + " the attributes.\n\n" + ".. |PQresultErrorField| replace:: `!PQresultErrorField()`\n" + ".. _PQresultErrorField: http://www.postgresql.org/docs/current/static/" + "libpq-exec.html#LIBPQ-PQRESULTERRORFIELD\n" + ".. __: PQresultErrorField_\n"; + +PyTypeObject diagnosticsType = { + PyVarObject_HEAD_INIT(NULL, 0) + "psycopg2._psycopg.Diagnostics", + sizeof(diagnosticsObject), 0, + (destructor)diagnostics_dealloc, /*tp_dealloc*/ + 0, /*tp_print*/ + 0, /*tp_getattr*/ + 0, /*tp_setattr*/ + 0, /*tp_compare*/ + 0, /*tp_repr*/ + 0, /*tp_as_number*/ + 0, /*tp_as_sequence*/ + 0, /*tp_as_mapping*/ + 0, /*tp_hash */ + 0, /*tp_call*/ + 0, /*tp_str*/ + 0, /*tp_getattro*/ + 0, /*tp_setattro*/ + 0, /*tp_as_buffer*/ + Py_TPFLAGS_DEFAULT|Py_TPFLAGS_BASETYPE, /*tp_flags*/ + diagnosticsType_doc, /*tp_doc*/ + 0, /*tp_traverse*/ + 0, /*tp_clear*/ + 0, /*tp_richcompare*/ + 0, /*tp_weaklistoffset*/ + 0, /*tp_iter*/ + 0, /*tp_iternext*/ + 0, /*tp_methods*/ + 0, /*tp_members*/ + diagnosticsObject_getsets, /*tp_getset*/ + 0, /*tp_base*/ + 0, /*tp_dict*/ + 0, /*tp_descr_get*/ + 0, /*tp_descr_set*/ + 0, /*tp_dictoffset*/ + (initproc)diagnostics_init, /*tp_init*/ + 0, /*tp_alloc*/ + diagnostics_new, /*tp_new*/ +}; diff --git a/psycopg/error.h b/psycopg/error.h new file mode 100644 index 00000000..9ae6dbd3 --- /dev/null +++ b/psycopg/error.h @@ -0,0 +1,43 @@ +/* error.h - definition for the psycopg base Error type + * + * Copyright (C) 2013 Daniele Varrazzo + * + * This file is part of psycopg. + * + * psycopg2 is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * In addition, as a special exception, the copyright holders give + * permission to link this program with the OpenSSL library (or with + * modified versions of OpenSSL that use the same license as OpenSSL), + * and distribute linked combinations including the two. + * + * You must obey the GNU Lesser General Public License in all respects for + * all of the code used other than OpenSSL. + * + * psycopg2 is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + */ + +#ifndef PSYCOPG_ERROR_H +#define PSYCOPG_ERROR_H 1 + +extern HIDDEN PyTypeObject errorType; + +typedef struct { + PyBaseExceptionObject exc; + + PyObject *pgerror; + PyObject *pgcode; + cursorObject *cursor; + char *codec; + PGresult *pgres; +} errorObject; + +HIDDEN PyObject *error_text_from_chars(errorObject *self, const char *str); + +#endif /* PSYCOPG_ERROR_H */ diff --git a/psycopg/error_type.c b/psycopg/error_type.c new file mode 100644 index 00000000..106b87a7 --- /dev/null +++ b/psycopg/error_type.c @@ -0,0 +1,276 @@ +/* error_type.c - python interface to the Error objects + * + * Copyright (C) 2013 Daniele Varrazzo + * + * This file is part of psycopg. + * + * psycopg2 is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * In addition, as a special exception, the copyright holders give + * permission to link this program with the OpenSSL library (or with + * modified versions of OpenSSL that use the same license as OpenSSL), + * and distribute linked combinations including the two. + * + * You must obey the GNU Lesser General Public License in all respects for + * all of the code used other than OpenSSL. + * + * psycopg2 is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + */ + +#define PSYCOPG_MODULE +#include "psycopg/psycopg.h" + +#include "psycopg/error.h" +#include "psycopg/diagnostics.h" +#include "psycopg/pqpath.h" + + +PyObject * +error_text_from_chars(errorObject *self, const char *str) +{ + if (str == NULL) { + Py_INCREF(Py_None); + return (Py_None); + } + +#if PY_MAJOR_VERSION < 3 + return PyString_FromString(str); +#else + return PyUnicode_Decode(str, strlen(str), + self->codec ? self->codec : "ascii", "replace"); +#endif +} + + +static const char pgerror_doc[] = + "The error message returned by the backend, if available, else None"; + +static const char pgcode_doc[] = + "The error code returned by the backend, if available, else None"; + +static const char cursor_doc[] = + "The cursor that raised the exception, if available, else None"; + +static const char diag_doc[] = + "A Diagnostics object to get further information about the error"; + +static PyMemberDef error_members[] = { + { "pgerror", T_OBJECT, offsetof(errorObject, pgerror), + READONLY, (char *)pgerror_doc }, + { "pgcode", T_OBJECT, offsetof(errorObject, pgcode), + READONLY, (char *)pgcode_doc }, + { "cursor", T_OBJECT, offsetof(errorObject, cursor), + READONLY, (char *)cursor_doc }, + { NULL } +}; + +static PyObject * +error_new(PyTypeObject *type, PyObject *args, PyObject *kwargs) +{ + return ((PyTypeObject *)PyExc_StandardError)->tp_new( + type, args, kwargs); +} + +static int +error_init(errorObject *self, PyObject *args, PyObject *kwargs) +{ + if (((PyTypeObject *)PyExc_StandardError)->tp_init( + (PyObject *)self, args, kwargs) < 0) { + return -1; + } + return 0; +} + +static int +error_traverse(errorObject *self, visitproc visit, void *arg) +{ + Py_VISIT(self->pgerror); + Py_VISIT(self->pgcode); + Py_VISIT(self->cursor); + + return ((PyTypeObject *)PyExc_StandardError)->tp_traverse( + (PyObject *)self, visit, arg); +} + +static int +error_clear(errorObject *self) +{ + Py_CLEAR(self->pgerror); + Py_CLEAR(self->pgcode); + Py_CLEAR(self->cursor); + + return ((PyTypeObject *)PyExc_StandardError)->tp_clear((PyObject *)self); +} + +static void +error_dealloc(errorObject *self) +{ + PyObject_GC_UnTrack((PyObject *)self); + error_clear(self); + PyMem_Free(self->codec); + CLEARPGRES(self->pgres); + + Py_TYPE(self)->tp_free((PyObject *)self); +} + + +static PyObject * +error_get_diag(errorObject *self, void *closure) +{ + return PyObject_CallFunctionObjArgs( + (PyObject *)&diagnosticsType, (PyObject *)self, NULL); +} + +static struct PyGetSetDef error_getsets[] = { + { "diag", (getter)error_get_diag, NULL, (char *)diag_doc }, + { NULL } +}; + + +/* Error.__reduce__ + * + * The method is required to make exceptions picklable: set the cursor + * attribute to None. Only working from Py 2.5: previous versions + * would require implementing __getstate__, and as of 2012 it's a little + * bit too late to care. */ +static PyObject * +psyco_error_reduce(errorObject *self) +{ + PyObject *meth = NULL; + PyObject *tuple = NULL; + PyObject *dict = NULL; + PyObject *rv = NULL; + + if (!(meth = PyObject_GetAttrString(PyExc_StandardError, "__reduce__"))) { + goto error; + } + if (!(tuple = PyObject_CallFunctionObjArgs(meth, self, NULL))) { + goto error; + } + + /* tuple is (type, args): convert it to (type, args, dict) + * with our extra items in the dict. + * + * If these checks fail, we can still return a valid object. Pickle + * will likely fail downstream, but there's nothing else we can do here */ + if (!PyTuple_Check(tuple)) { goto exit; } + if (2 != PyTuple_GET_SIZE(tuple)) { goto exit; } + + if (!(dict = PyDict_New())) { goto error; } + if (0 != PyDict_SetItemString(dict, "pgerror", self->pgerror)) { goto error; } + if (0 != PyDict_SetItemString(dict, "pgcode", self->pgcode)) { goto error; } + + { + PyObject *newtuple; + if (!(newtuple = PyTuple_Pack(3, + PyTuple_GET_ITEM(tuple, 0), + PyTuple_GET_ITEM(tuple, 1), + dict))) { + goto error; + } + Py_DECREF(tuple); + tuple = newtuple; + } + +exit: + rv = tuple; + tuple = NULL; + +error: + Py_XDECREF(dict); + Py_XDECREF(tuple); + Py_XDECREF(meth); + + return rv; +} + +PyObject * +psyco_error_setstate(errorObject *self, PyObject *state) +{ + PyObject *rv = NULL; + + /* we don't call the StandartError's setstate as it would try to load the + * dict content as attributes */ + + if (state == Py_None) { + goto exit; + } + if (!PyDict_Check(state)) { + PyErr_SetString(PyExc_TypeError, "state is not a dictionary"); + goto error; + } + + /* load the dict content in the structure */ + Py_CLEAR(self->pgerror); + self->pgerror = PyDict_GetItemString(state, "pgerror"); + Py_XINCREF(self->pgerror); + + Py_CLEAR(self->pgcode); + self->pgcode = PyDict_GetItemString(state, "pgcode"); + Py_XINCREF(self->pgcode); + + Py_CLEAR(self->cursor); + /* We never expect a cursor in the state as it's not picklable. + * at most there could be a None here, coming from Psycopg < 2.5 */ + +exit: + rv = Py_None; + Py_INCREF(rv); + +error: + return rv; +} + +static PyMethodDef error_methods[] = { + /* Make Error and all its subclasses picklable. */ + {"__reduce__", (PyCFunction)psyco_error_reduce, METH_NOARGS }, + {"__setstate__", (PyCFunction)psyco_error_setstate, METH_O }, + {NULL} +}; + + +PyTypeObject errorType = { + PyVarObject_HEAD_INIT(NULL, 0) + "psycopg2.Error", + sizeof(errorObject), 0, + (destructor)error_dealloc, /* tp_dealloc */ + 0, /*tp_print*/ + 0, /*tp_getattr*/ + 0, /*tp_setattr*/ + 0, /*tp_compare*/ + 0, /*tp_repr*/ + 0, /*tp_as_number*/ + 0, /*tp_as_sequence*/ + 0, /*tp_as_mapping*/ + 0, /*tp_hash */ + 0, /*tp_call*/ + 0, /*tp_str*/ + 0, /*tp_getattro*/ + 0, /*tp_setattro*/ + 0, /*tp_as_buffer*/ + Py_TPFLAGS_DEFAULT|Py_TPFLAGS_BASETYPE|Py_TPFLAGS_HAVE_GC, /*tp_flags*/ + Error_doc, /*tp_doc*/ + (traverseproc)error_traverse, /*tp_traverse*/ + (inquiry)error_clear, /*tp_clear*/ + 0, /*tp_richcompare*/ + 0, /*tp_weaklistoffset*/ + 0, /*tp_iter*/ + 0, /*tp_iternext*/ + error_methods, /*tp_methods*/ + error_members, /*tp_members*/ + error_getsets, /*tp_getset*/ + 0, /*tp_base Will be set to StandardError in module init */ + 0, /*tp_dict*/ + 0, /*tp_descr_get*/ + 0, /*tp_descr_set*/ + 0, /*tp_dictoffset*/ + (initproc)error_init, /*tp_init*/ + 0, /*tp_alloc*/ + error_new, /*tp_new*/ +}; diff --git a/psycopg/green.c b/psycopg/green.c index 3ffa810b..e7604076 100644 --- a/psycopg/green.c +++ b/psycopg/green.c @@ -53,8 +53,7 @@ psyco_set_wait_callback(PyObject *self, PyObject *obj) wait_callback = NULL; } - Py_INCREF(Py_None); - return Py_None; + Py_RETURN_NONE; } diff --git a/psycopg/lobject.h b/psycopg/lobject.h index 6587198c..56f9ead4 100644 --- a/psycopg/lobject.h +++ b/psycopg/lobject.h @@ -78,13 +78,13 @@ RAISES_NEG HIDDEN int lobject_close(lobjectObject *self); #define EXC_IF_LOBJ_LEVEL0(self) \ if (self->conn->autocommit) { \ psyco_set_error(ProgrammingError, NULL, \ - "can't use a lobject outside of transactions", NULL, NULL); \ + "can't use a lobject outside of transactions"); \ return NULL; \ } #define EXC_IF_LOBJ_UNMARKED(self) \ if (self->conn->mark != self->mark) { \ psyco_set_error(ProgrammingError, NULL, \ - "lobject isn't valid anymore", NULL, NULL); \ + "lobject isn't valid anymore"); \ return NULL; \ } diff --git a/psycopg/lobject_type.c b/psycopg/lobject_type.c index 625a2939..fee11c45 100644 --- a/psycopg/lobject_type.c +++ b/psycopg/lobject_type.c @@ -59,8 +59,7 @@ psyco_lobj_close(lobjectObject *self, PyObject *args) return NULL; } - Py_INCREF(Py_None); - return Py_None; + Py_RETURN_NONE; } /* write method - write data to the lobject */ @@ -128,7 +127,7 @@ psyco_lobj_read(lobjectObject *self, PyObject *args) Py_ssize_t size = -1; char *buffer; - if (!PyArg_ParseTuple(args, "|" CONV_CODE_PY_SSIZE_T, &size)) return NULL; + if (!PyArg_ParseTuple(args, "|n", &size)) return NULL; EXC_IF_LOBJ_CLOSED(self); EXC_IF_LOBJ_LEVEL0(self); @@ -213,10 +212,9 @@ static PyObject * psyco_lobj_unlink(lobjectObject *self, PyObject *args) { if (lobject_unlink(self) < 0) - return NULL; + return NULL; - Py_INCREF(Py_None); - return Py_None; + Py_RETURN_NONE; } /* export method - export lobject's content to given file */ @@ -230,15 +228,14 @@ psyco_lobj_export(lobjectObject *self, PyObject *args) const char *filename; if (!PyArg_ParseTuple(args, "s", &filename)) - return NULL; + return NULL; EXC_IF_LOBJ_LEVEL0(self); if (lobject_export(self, filename) < 0) - return NULL; + return NULL; - Py_INCREF(Py_None); - return Py_None; + Py_RETURN_NONE; } @@ -272,8 +269,7 @@ psyco_lobj_truncate(lobjectObject *self, PyObject *args) if (lobject_truncate(self, len) < 0) return NULL; - Py_INCREF(Py_None); - return Py_None; + Py_RETURN_NONE; } #endif /* PG_VERSION_HEX >= 0x080300 */ @@ -333,7 +329,7 @@ lobject_setup(lobjectObject *self, connectionObject *conn, if (conn->autocommit) { psyco_set_error(ProgrammingError, NULL, - "can't use a lobject outside of transactions", NULL, NULL); + "can't use a lobject outside of transactions"); return -1; } @@ -392,12 +388,6 @@ lobject_new(PyTypeObject *type, PyObject *args, PyObject *kwds) return type->tp_alloc(type, 0); } -static void -lobject_del(PyObject* self) -{ - PyObject_Del(self); -} - static PyObject * lobject_repr(lobjectObject *self) { @@ -414,8 +404,7 @@ lobject_repr(lobjectObject *self) PyTypeObject lobjectType = { PyVarObject_HEAD_INIT(NULL, 0) "psycopg2._psycopg.lobject", - sizeof(lobjectObject), - 0, + sizeof(lobjectObject), 0, lobject_dealloc, /*tp_dealloc*/ 0, /*tp_print*/ 0, /*tp_getattr*/ @@ -426,47 +415,30 @@ PyTypeObject lobjectType = { 0, /*tp_as_sequence*/ 0, /*tp_as_mapping*/ 0, /*tp_hash */ - 0, /*tp_call*/ (reprfunc)lobject_repr, /*tp_str*/ 0, /*tp_getattro*/ 0, /*tp_setattro*/ 0, /*tp_as_buffer*/ - Py_TPFLAGS_DEFAULT|Py_TPFLAGS_BASETYPE|Py_TPFLAGS_HAVE_ITER, /*tp_flags*/ lobjectType_doc, /*tp_doc*/ - 0, /*tp_traverse*/ 0, /*tp_clear*/ - 0, /*tp_richcompare*/ 0, /*tp_weaklistoffset*/ - 0, /*tp_iter*/ 0, /*tp_iternext*/ - - /* Attribute descriptor and subclassing stuff */ - lobjectObject_methods, /*tp_methods*/ lobjectObject_members, /*tp_members*/ lobjectObject_getsets, /*tp_getset*/ 0, /*tp_base*/ 0, /*tp_dict*/ - 0, /*tp_descr_get*/ 0, /*tp_descr_set*/ 0, /*tp_dictoffset*/ - lobject_init, /*tp_init*/ - 0, /*tp_alloc Will be set to PyType_GenericAlloc in module init*/ + 0, /*tp_alloc*/ lobject_new, /*tp_new*/ - (freefunc)lobject_del, /*tp_free Low-level free-memory routine */ - 0, /*tp_is_gc For PyObject_IS_GC */ - 0, /*tp_bases*/ - 0, /*tp_mro method resolution order */ - 0, /*tp_cache*/ - 0, /*tp_subclasses*/ - 0 /*tp_weaklist*/ }; #endif diff --git a/psycopg/microprotocols.c b/psycopg/microprotocols.c index 23f12794..1687bc26 100644 --- a/psycopg/microprotocols.c +++ b/psycopg/microprotocols.c @@ -203,7 +203,7 @@ microprotocols_adapt(PyObject *obj, PyObject *proto, PyObject *alt) /* else set the right exception and return NULL */ PyOS_snprintf(buffer, 255, "can't adapt type '%s'", Py_TYPE(obj)->tp_name); - psyco_set_error(ProgrammingError, NULL, buffer, NULL, NULL); + psyco_set_error(ProgrammingError, NULL, buffer); return NULL; } diff --git a/psycopg/microprotocols_proto.c b/psycopg/microprotocols_proto.c index 775889d9..f30da3f4 100644 --- a/psycopg/microprotocols_proto.c +++ b/psycopg/microprotocols_proto.c @@ -42,8 +42,7 @@ static PyObject * psyco_isqlquote_getquoted(isqlquoteObject *self, PyObject *args) { - Py_INCREF(Py_None); - return Py_None; + Py_RETURN_NONE; } /* getbinary - return quoted representation for object */ @@ -54,8 +53,7 @@ psyco_isqlquote_getquoted(isqlquoteObject *self, PyObject *args) static PyObject * psyco_isqlquote_getbinary(isqlquoteObject *self, PyObject *args) { - Py_INCREF(Py_None); - return Py_None; + Py_RETURN_NONE; } /* getbuffer - return quoted representation for object */ @@ -66,8 +64,7 @@ psyco_isqlquote_getbinary(isqlquoteObject *self, PyObject *args) static PyObject * psyco_isqlquote_getbuffer(isqlquoteObject *self, PyObject *args) { - Py_INCREF(Py_None); - return Py_None; + Py_RETURN_NONE; } @@ -135,12 +132,6 @@ isqlquote_new(PyTypeObject *type, PyObject *args, PyObject *kwds) return type->tp_alloc(type, 0); } -static void -isqlquote_del(PyObject* self) -{ - PyObject_Del(self); -} - /* object type */ @@ -152,8 +143,7 @@ isqlquote_del(PyObject* self) PyTypeObject isqlquoteType = { PyVarObject_HEAD_INIT(NULL, 0) "psycopg2._psycopg.ISQLQuote", - sizeof(isqlquoteObject), - 0, + sizeof(isqlquoteObject), 0, isqlquote_dealloc, /*tp_dealloc*/ 0, /*tp_print*/ 0, /*tp_getattr*/ @@ -164,45 +154,28 @@ PyTypeObject isqlquoteType = { 0, /*tp_as_sequence*/ 0, /*tp_as_mapping*/ 0, /*tp_hash */ - 0, /*tp_call*/ 0, /*tp_str*/ 0, /*tp_getattro*/ 0, /*tp_setattro*/ 0, /*tp_as_buffer*/ - Py_TPFLAGS_DEFAULT|Py_TPFLAGS_BASETYPE, /*tp_flags*/ isqlquoteType_doc, /*tp_doc*/ - 0, /*tp_traverse*/ 0, /*tp_clear*/ - 0, /*tp_richcompare*/ 0, /*tp_weaklistoffset*/ - 0, /*tp_iter*/ 0, /*tp_iternext*/ - - /* Attribute descriptor and subclassing stuff */ - isqlquoteObject_methods, /*tp_methods*/ isqlquoteObject_members, /*tp_members*/ 0, /*tp_getset*/ 0, /*tp_base*/ 0, /*tp_dict*/ - 0, /*tp_descr_get*/ 0, /*tp_descr_set*/ 0, /*tp_dictoffset*/ - isqlquote_init, /*tp_init*/ - 0, /*tp_alloc will be set to PyType_GenericAlloc in module init*/ + 0, /*tp_alloc*/ isqlquote_new, /*tp_new*/ - (freefunc)isqlquote_del, /*tp_free Low-level free-memory routine */ - 0, /*tp_is_gc For PyObject_IS_GC */ - 0, /*tp_bases*/ - 0, /*tp_mro method resolution order */ - 0, /*tp_cache*/ - 0, /*tp_subclasses*/ - 0 /*tp_weaklist*/ }; diff --git a/psycopg/notify.h b/psycopg/notify.h index 645fdd73..2f4c20f5 100644 --- a/psycopg/notify.h +++ b/psycopg/notify.h @@ -26,7 +26,7 @@ #ifndef PSYCOPG_NOTIFY_H #define PSYCOPG_NOTIFY_H 1 -extern HIDDEN PyTypeObject NotifyType; +extern HIDDEN PyTypeObject notifyType; typedef struct { PyObject_HEAD @@ -35,6 +35,6 @@ typedef struct { PyObject *channel; PyObject *payload; -} NotifyObject; +} notifyObject; #endif /* PSYCOPG_NOTIFY_H */ diff --git a/psycopg/notify_type.c b/psycopg/notify_type.c index f97a5af3..e2589a6a 100644 --- a/psycopg/notify_type.c +++ b/psycopg/notify_type.c @@ -52,22 +52,20 @@ static const char payload_doc[] = "of the server this member is always the empty string."; static PyMemberDef notify_members[] = { - { "pid", T_OBJECT, offsetof(NotifyObject, pid), READONLY, (char *)pid_doc }, - { "channel", T_OBJECT, offsetof(NotifyObject, channel), READONLY, (char *)channel_doc }, - { "payload", T_OBJECT, offsetof(NotifyObject, payload), READONLY, (char *)payload_doc }, + { "pid", T_OBJECT, offsetof(notifyObject, pid), READONLY, (char *)pid_doc }, + { "channel", T_OBJECT, offsetof(notifyObject, channel), READONLY, (char *)channel_doc }, + { "payload", T_OBJECT, offsetof(notifyObject, payload), READONLY, (char *)payload_doc }, { NULL } }; static PyObject * notify_new(PyTypeObject *type, PyObject *args, PyObject *kwargs) { - NotifyObject *self = (NotifyObject *)type->tp_alloc(type, 0); - - return (PyObject *)self; + return type->tp_alloc(type, 0); } static int -notify_init(NotifyObject *self, PyObject *args, PyObject *kwargs) +notify_init(notifyObject *self, PyObject *args, PyObject *kwargs) { static char *kwlist[] = {"pid", "channel", "payload", NULL}; PyObject *pid = NULL, *channel = NULL, *payload = NULL; @@ -81,32 +79,20 @@ notify_init(NotifyObject *self, PyObject *args, PyObject *kwargs) payload = Text_FromUTF8(""); } - Py_CLEAR(self->pid); Py_INCREF(pid); self->pid = pid; - Py_CLEAR(self->channel); Py_INCREF(channel); self->channel = channel; - Py_CLEAR(self->payload); Py_INCREF(payload); self->payload = payload; return 0; } -static int -notify_traverse(NotifyObject *self, visitproc visit, void *arg) -{ - Py_VISIT(self->pid); - Py_VISIT(self->channel); - Py_VISIT(self->payload); - return 0; -} - static void -notify_dealloc(NotifyObject *self) +notify_dealloc(notifyObject *self) { Py_CLEAR(self->pid); Py_CLEAR(self->channel); @@ -115,16 +101,10 @@ notify_dealloc(NotifyObject *self) Py_TYPE(self)->tp_free((PyObject *)self); } -static void -notify_del(PyObject *self) -{ - PyObject_GC_Del(self); -} - /* Convert a notify into a 2 or 3 items tuple. */ static PyObject * -notify_astuple(NotifyObject *self, int with_payload) +notify_astuple(notifyObject *self, int with_payload) { PyObject *tself; if (!(tself = PyTuple_New(with_payload ? 3 : 2))) { return NULL; } @@ -162,15 +142,15 @@ notify_astuple(NotifyObject *self, int with_payload) * the (pid, channel) pair is no more equivalent as dict key to the Notify. */ static PyObject * -notify_richcompare(NotifyObject *self, PyObject *other, int op) +notify_richcompare(notifyObject *self, PyObject *other, int op) { PyObject *rv = NULL; PyObject *tself = NULL; PyObject *tother = NULL; - if (Py_TYPE(other) == &NotifyType) { + if (Py_TYPE(other) == ¬ifyType) { if (!(tself = notify_astuple(self, 1))) { goto exit; } - if (!(tother = notify_astuple((NotifyObject *)other, 1))) { goto exit; } + if (!(tother = notify_astuple((notifyObject *)other, 1))) { goto exit; } rv = PyObject_RichCompare(tself, tother, op); } else if (PyTuple_Check(other)) { @@ -190,7 +170,7 @@ exit: static Py_hash_t -notify_hash(NotifyObject *self) +notify_hash(notifyObject *self) { Py_hash_t rv = -1L; PyObject *tself = NULL; @@ -207,7 +187,7 @@ exit: static PyObject* -notify_repr(NotifyObject *self) +notify_repr(notifyObject *self) { PyObject *rv = NULL; PyObject *format = NULL; @@ -237,13 +217,13 @@ exit: /* Notify can be accessed as a 2 items tuple for backward compatibility */ static Py_ssize_t -notify_len(NotifyObject *self) +notify_len(notifyObject *self) { return 2; } static PyObject * -notify_getitem(NotifyObject *self, Py_ssize_t item) +notify_getitem(notifyObject *self, Py_ssize_t item) { if (item < 0) item += 2; @@ -275,66 +255,45 @@ static PySequenceMethods notify_sequence = { }; -PyTypeObject NotifyType = { +PyTypeObject notifyType = { PyVarObject_HEAD_INIT(NULL, 0) "psycopg2.extensions.Notify", - sizeof(NotifyObject), - 0, + sizeof(notifyObject), 0, (destructor)notify_dealloc, /* tp_dealloc */ 0, /*tp_print*/ - 0, /*tp_getattr*/ 0, /*tp_setattr*/ - 0, /*tp_compare*/ - (reprfunc)notify_repr, /*tp_repr*/ 0, /*tp_as_number*/ ¬ify_sequence, /*tp_as_sequence*/ 0, /*tp_as_mapping*/ (hashfunc)notify_hash, /*tp_hash */ - 0, /*tp_call*/ 0, /*tp_str*/ - 0, /*tp_getattro*/ 0, /*tp_setattro*/ 0, /*tp_as_buffer*/ - - Py_TPFLAGS_DEFAULT|Py_TPFLAGS_HAVE_GC, /*tp_flags*/ + /* Notify is not GC as it only has string attributes */ + Py_TPFLAGS_DEFAULT|Py_TPFLAGS_BASETYPE, /*tp_flags*/ notify_doc, /*tp_doc*/ - - (traverseproc)notify_traverse, /*tp_traverse*/ + 0, /*tp_traverse*/ 0, /*tp_clear*/ - (richcmpfunc)notify_richcompare, /*tp_richcompare*/ 0, /*tp_weaklistoffset*/ - 0, /*tp_iter*/ 0, /*tp_iternext*/ - - /* Attribute descriptor and subclassing stuff */ - 0, /*tp_methods*/ notify_members, /*tp_members*/ 0, /*tp_getset*/ 0, /*tp_base*/ 0, /*tp_dict*/ - 0, /*tp_descr_get*/ 0, /*tp_descr_set*/ 0, /*tp_dictoffset*/ - (initproc)notify_init, /*tp_init*/ - 0, /*tp_alloc will be set to PyType_GenericAlloc in module init*/ + 0, /*tp_alloc*/ notify_new, /*tp_new*/ - (freefunc)notify_del, /*tp_free Low-level free-memory routine */ - 0, /*tp_is_gc For PyObject_IS_GC */ - 0, /*tp_bases*/ - 0, /*tp_mro method resolution order */ - 0, /*tp_cache*/ - 0, /*tp_subclasses*/ - 0 /*tp_weaklist*/ }; diff --git a/psycopg/pqpath.c b/psycopg/pqpath.c index ea8bac01..521fc460 100644 --- a/psycopg/pqpath.c +++ b/psycopg/pqpath.c @@ -38,6 +38,7 @@ #include "psycopg/green.h" #include "psycopg/typecast.h" #include "psycopg/pgtypes.h" +#include "psycopg/error.h" #include @@ -149,15 +150,20 @@ exception_from_sqlstate(const char *sqlstate) /* pq_raise - raise a python exception of the right kind - This function should be called while holding the GIL. */ + This function should be called while holding the GIL. + + The function passes the ownership of the pgres to the returned exception, + wherer the pgres was the explicit argument or taken from the cursor. + So, after calling it curs->pgres will be set to null */ RAISES static void -pq_raise(connectionObject *conn, cursorObject *curs, PGresult *pgres) +pq_raise(connectionObject *conn, cursorObject *curs, PGresult **pgres) { PyObject *exc = NULL; const char *err = NULL; const char *err2 = NULL; const char *code = NULL; + PyObject *pyerr = NULL; if (conn == NULL) { PyErr_SetString(DatabaseError, @@ -171,13 +177,13 @@ pq_raise(connectionObject *conn, cursorObject *curs, PGresult *pgres) conn->closed = 2; if (pgres == NULL && curs != NULL) - pgres = curs->pgres; + pgres = &curs->pgres; - if (pgres) { - err = PQresultErrorMessage(pgres); + if (pgres && *pgres) { + err = PQresultErrorMessage(*pgres); if (err != NULL) { Dprintf("pq_raise: PQresultErrorMessage: err=%s", err); - code = PQresultErrorField(pgres, PG_DIAG_SQLSTATE); + code = PQresultErrorField(*pgres, PG_DIAG_SQLSTATE); } } if (err == NULL) { @@ -210,7 +216,26 @@ pq_raise(connectionObject *conn, cursorObject *curs, PGresult *pgres) err2 = strip_severity(err); Dprintf("pq_raise: err2=%s", err2); - psyco_set_error(exc, curs, err2, err, code); + pyerr = psyco_set_error(exc, curs, err2); + + if (pyerr && PyObject_TypeCheck(pyerr, &errorType)) { + errorObject *perr = (errorObject *)pyerr; + + PyMem_Free(perr->codec); + psycopg_strdup(&perr->codec, conn->codec, 0); + + Py_CLEAR(perr->pgerror); + perr->pgerror = error_text_from_chars(perr, err); + + Py_CLEAR(perr->pgcode); + perr->pgcode = error_text_from_chars(perr, code); + + CLEARPGRES(perr->pgres); + if (pgres && *pgres) { + perr->pgres = *pgres; + *pgres = NULL; + } + } } /* pq_set_critical, pq_resolve_critical - manage critical errors @@ -369,7 +394,7 @@ pq_execute_command_locked(connectionObject *conn, const char *query, } retvalue = 0; - IFCLEARPGRES(*pgres); + CLEARPGRES(*pgres); cleanup: return retvalue; @@ -388,14 +413,16 @@ pq_complete_error(connectionObject *conn, PGresult **pgres, char **error) { Dprintf("pq_complete_error: pgconn = %p, pgres = %p, error = %s", conn->pgconn, *pgres, *error ? *error : "(null)"); - if (*pgres != NULL) - pq_raise(conn, NULL, *pgres); + if (*pgres != NULL) { + pq_raise(conn, NULL, pgres); + /* now *pgres is null */ + } else if (*error != NULL) { PyErr_SetString(OperationalError, *error); } else { PyErr_SetString(OperationalError, "unknown error"); } - IFCLEARPGRES(*pgres); + if (*error) { free(*error); *error = NULL; @@ -557,12 +584,18 @@ pq_reset_locked(connectionObject *conn, PGresult **pgres, char **error, if (retvalue != 0) return retvalue; } - retvalue = pq_execute_command_locked(conn, "RESET ALL", pgres, error, tstate); - if (retvalue != 0) return retvalue; + if (conn->server_version >= 80300) { + retvalue = pq_execute_command_locked(conn, "DISCARD ALL", pgres, error, tstate); + if (retvalue != 0) return retvalue; + } + else { + retvalue = pq_execute_command_locked(conn, "RESET ALL", pgres, error, tstate); + if (retvalue != 0) return retvalue; - retvalue = pq_execute_command_locked(conn, - "SET SESSION AUTHORIZATION DEFAULT", pgres, error, tstate); - if (retvalue != 0) return retvalue; + retvalue = pq_execute_command_locked(conn, + "SET SESSION AUTHORIZATION DEFAULT", pgres, error, tstate); + if (retvalue != 0) return retvalue; + } /* should set the tpc xid to null: postponed until we get the GIL again */ conn->status = CONN_STATUS_READY; @@ -717,7 +750,7 @@ pq_tpc_command_locked(connectionObject *conn, const char *cmd, const char *tid, PyEval_RestoreThread(*tstate); /* convert the xid into the postgres transaction_id and quote it. */ - if (!(etid = psycopg_escape_string((PyObject *)conn, tid, 0, NULL, NULL))) + if (!(etid = psycopg_escape_string(conn, tid, 0, NULL, NULL))) { goto exit; } /* prepare the command to the server */ @@ -869,7 +902,7 @@ pq_execute(cursorObject *curs, const char *query, int async, int no_result) } if (async == 0) { - IFCLEARPGRES(curs->pgres); + CLEARPGRES(curs->pgres); Dprintf("pq_execute: executing SYNC query: pgconn = %p", curs->conn->pgconn); Dprintf(" %-.200s", query); if (!psyco_green()) { @@ -908,7 +941,7 @@ pq_execute(cursorObject *curs, const char *query, int async, int no_result) Dprintf("pq_execute: executing ASYNC query: pgconn = %p", curs->conn->pgconn); Dprintf(" %-.200s", query); - IFCLEARPGRES(curs->pgres); + CLEARPGRES(curs->pgres); if (PQsendQuery(curs->conn->pgconn, query) == 0) { pthread_mutex_unlock(&(curs->conn->lock)); Py_BLOCK_THREADS; @@ -1305,7 +1338,7 @@ _pq_copy_in_v3(cursorObject *curs) /* XXX would be nice to propagate the exeption */ res = PQputCopyEnd(curs->conn->pgconn, "error in .read() call"); - IFCLEARPGRES(curs->pgres); + CLEARPGRES(curs->pgres); Dprintf("_pq_copy_in_v3: copy ended; res = %d", res); @@ -1329,7 +1362,7 @@ _pq_copy_in_v3(cursorObject *curs) break; if (PQresultStatus(curs->pgres) == PGRES_FATAL_ERROR) pq_raise(curs->conn, curs, NULL); - IFCLEARPGRES(curs->pgres); + CLEARPGRES(curs->pgres); } } @@ -1395,7 +1428,7 @@ _pq_copy_out_v3(cursorObject *curs) } /* and finally we grab the operation result from the backend */ - IFCLEARPGRES(curs->pgres); + CLEARPGRES(curs->pgres); for (;;) { Py_BEGIN_ALLOW_THREADS; curs->pgres = PQgetResult(curs->conn->pgconn); @@ -1405,7 +1438,7 @@ _pq_copy_out_v3(cursorObject *curs) break; if (PQresultStatus(curs->pgres) == PGRES_FATAL_ERROR) pq_raise(curs->conn, curs, NULL); - IFCLEARPGRES(curs->pgres); + CLEARPGRES(curs->pgres); } ret = 1; @@ -1466,7 +1499,7 @@ pq_fetch(cursorObject *curs, int no_result) curs->rowcount = -1; /* error caught by out glorious notice handler */ if (PyErr_Occurred()) ex = -1; - IFCLEARPGRES(curs->pgres); + CLEARPGRES(curs->pgres); break; case PGRES_COPY_IN: @@ -1475,7 +1508,7 @@ pq_fetch(cursorObject *curs, int no_result) curs->rowcount = -1; /* error caught by out glorious notice handler */ if (PyErr_Occurred()) ex = -1; - IFCLEARPGRES(curs->pgres); + CLEARPGRES(curs->pgres); break; case PGRES_TUPLES_OK: @@ -1487,7 +1520,7 @@ pq_fetch(cursorObject *curs, int no_result) } else { Dprintf("pq_fetch: got tuples, discarding them"); - IFCLEARPGRES(curs->pgres); + CLEARPGRES(curs->pgres); curs->rowcount = -1; ex = 0; } @@ -1496,14 +1529,13 @@ pq_fetch(cursorObject *curs, int no_result) case PGRES_EMPTY_QUERY: PyErr_SetString(ProgrammingError, "can't execute an empty query"); - IFCLEARPGRES(curs->pgres); + CLEARPGRES(curs->pgres); ex = -1; break; default: Dprintf("pq_fetch: uh-oh, something FAILED: pgconn = %p", curs->conn); pq_raise(curs->conn, curs, NULL); - IFCLEARPGRES(curs->pgres); ex = -1; break; } diff --git a/psycopg/pqpath.h b/psycopg/pqpath.h index d697e48f..40beea19 100644 --- a/psycopg/pqpath.h +++ b/psycopg/pqpath.h @@ -29,9 +29,8 @@ #include "psycopg/cursor.h" #include "psycopg/connection.h" -/* macros to clean the pg result */ -#define IFCLEARPGRES(pgres) if (pgres) {PQclear(pgres); pgres = NULL;} -#define CLEARPGRES(pgres) PQclear(pgres); pgres = NULL +/* macro to clean the pg result */ +#define CLEARPGRES(pgres) do { PQclear(pgres); pgres = NULL; } while (0) /* exported functions */ HIDDEN PGresult *pq_get_last_result(connectionObject *conn); diff --git a/psycopg/psycopg.h b/psycopg/psycopg.h index 2e86dca8..5a6bf243 100644 --- a/psycopg/psycopg.h +++ b/psycopg/psycopg.h @@ -116,14 +116,14 @@ typedef struct { /* the Decimal type, used by the DECIMAL typecaster */ HIDDEN PyObject *psyco_GetDecimalType(void); -/* forward declaration */ +/* forward declarations */ typedef struct cursorObject cursorObject; +typedef struct connectionObject connectionObject; /* some utility functions */ -RAISES HIDDEN void psyco_set_error(PyObject *exc, cursorObject *curs, const char *msg, - const char *pgerror, const char *pgcode); +RAISES HIDDEN PyObject *psyco_set_error(PyObject *exc, cursorObject *curs, const char *msg); -HIDDEN char *psycopg_escape_string(PyObject *conn, +HIDDEN char *psycopg_escape_string(connectionObject *conn, const char *from, Py_ssize_t len, char *to, Py_ssize_t *tolen); HIDDEN char *psycopg_escape_identifier_easy(const char *from, Py_ssize_t len); HIDDEN int psycopg_strdup(char **to, const char *from, Py_ssize_t len); diff --git a/psycopg/psycopgmodule.c b/psycopg/psycopgmodule.c index b1b4979f..206a7883 100644 --- a/psycopg/psycopgmodule.c +++ b/psycopg/psycopgmodule.c @@ -35,6 +35,8 @@ #include "psycopg/typecast.h" #include "psycopg/microprotocols.h" #include "psycopg/microprotocols_proto.h" +#include "psycopg/error.h" +#include "psycopg/diagnostics.h" #include "psycopg/adapter_qstring.h" #include "psycopg/adapter_binary.h" @@ -175,8 +177,7 @@ psyco_register_type(PyObject *self, PyObject *args) if (0 > typecast_add(type, NULL, 0)) { return NULL; } } - Py_INCREF(Py_None); - return Py_None; + Py_RETURN_NONE; } @@ -408,8 +409,8 @@ static struct { PyObject **base; const char *docstr; } exctable[] = { - { "psycopg2.Error", &Error, 0, Error_doc }, - { "psycopg2.Warning", &Warning, 0, Warning_doc }, + { "psycopg2.Error", &Error, NULL, Error_doc }, + { "psycopg2.Warning", &Warning, NULL, Warning_doc }, { "psycopg2.InterfaceError", &InterfaceError, &Error, InterfaceError_doc }, { "psycopg2.DatabaseError", &DatabaseError, &Error, DatabaseError_doc }, { "psycopg2.InternalError", &InternalError, &DatabaseError, InternalError_doc }, @@ -433,61 +434,6 @@ static struct { }; -#if PY_VERSION_HEX >= 0x02050000 - -/* Error.__reduce_ex__ - * - * The method is required to make exceptions picklable: set the cursor - * attribute to None. Only working from Py 2.5: previous versions - * would require implementing __getstate__, and as of 2012 it's a little - * bit too late to care. */ -static PyObject * -psyco_error_reduce_ex(PyObject *self, PyObject *args) -{ - PyObject *proto = NULL; - PyObject *super = NULL; - PyObject *tuple = NULL; - PyObject *dict = NULL; - PyObject *rv = NULL; - - /* tuple = Exception.__reduce_ex__(self, proto) */ - if (!PyArg_ParseTuple(args, "O", &proto)) { - goto error; - } - if (!(super = PyObject_GetAttrString(PyExc_Exception, "__reduce_ex__"))) { - goto error; - } - if (!(tuple = PyObject_CallFunctionObjArgs(super, self, proto, NULL))) { - goto error; - } - - /* tuple[2]['cursor'] = None - * - * If these checks fail, we can still return a valid object. Pickle - * will likely fail downstream, but there's nothing else we can do here */ - if (!PyTuple_Check(tuple)) { goto exit; } - if (3 > PyTuple_GET_SIZE(tuple)) { goto exit; } - dict = PyTuple_GET_ITEM(tuple, 2); /* borrowed */ - if (!PyDict_Check(dict)) { goto exit; } - - /* Modify the tuple inplace and return it */ - if (0 != PyDict_SetItemString(dict, "cursor", Py_None)) { - goto error; - } - -exit: - rv = tuple; - tuple = NULL; - -error: - Py_XDECREF(tuple); - Py_XDECREF(super); - - return rv; -} - -#endif /* PY_VERSION_HEX >= 0x02050000 */ - static int psyco_errors_init(void) { @@ -497,17 +443,13 @@ psyco_errors_init(void) int i; PyObject *dict = NULL; - PyObject *base; PyObject *str = NULL; - PyObject *descr = NULL; int rv = -1; -#if PY_VERSION_HEX >= 0x02050000 - static PyMethodDef psyco_error_reduce_ex_def = - {"__reduce_ex__", psyco_error_reduce_ex, METH_VARARGS, "pickle helper"}; -#endif + /* 'Error' has been defined elsewhere: only init the other classes */ + Error = (PyObject *)&errorType; - for (i=0; exctable[i].name; i++) { + for (i = 1; exctable[i].name; i++) { if (!(dict = PyDict_New())) { goto exit; } if (exctable[i].docstr) { @@ -516,51 +458,20 @@ psyco_errors_init(void) Py_CLEAR(str); } - if (exctable[i].base == 0) { - #if PY_MAJOR_VERSION < 3 - base = PyExc_StandardError; - #else - /* StandardError is gone in 3.0 */ - base = NULL; - #endif - } - else - base = *exctable[i].base; - + /* can't put PyExc_StandardError in the static exctable: + * windows build will fail */ if (!(*exctable[i].exc = PyErr_NewException( - exctable[i].name, base, dict))) { + exctable[i].name, + exctable[i].base ? *exctable[i].base : PyExc_StandardError, + dict))) { goto exit; } Py_CLEAR(dict); } - /* Make pgerror, pgcode and cursor default to None on psycopg - error objects. This simplifies error handling code that checks - these attributes. */ - PyObject_SetAttrString(Error, "pgerror", Py_None); - PyObject_SetAttrString(Error, "pgcode", Py_None); - PyObject_SetAttrString(Error, "cursor", Py_None); - - /* install __reduce_ex__ on Error to make all the subclasses picklable. - * - * Don't install it on Py 2.4: it is not used by the pickle - * protocol, and if called manually fails in an unsettling way, - * probably because the exceptions were old-style classes. */ -#if PY_VERSION_HEX >= 0x02050000 - if (!(descr = PyDescr_NewMethod((PyTypeObject *)Error, - &psyco_error_reduce_ex_def))) { - goto exit; - } - if (0 != PyObject_SetAttrString(Error, - psyco_error_reduce_ex_def.ml_name, descr)) { - goto exit; - } -#endif - rv = 0; exit: - Py_XDECREF(descr); Py_XDECREF(str); Py_XDECREF(dict); return rv; @@ -604,11 +515,10 @@ psyco_errors_set(PyObject *type) Create a new error of the given type with extra attributes. */ -RAISES void -psyco_set_error(PyObject *exc, cursorObject *curs, const char *msg, - const char *pgerror, const char *pgcode) +/* TODO: may have been changed to BORROWED */ +RAISES PyObject * +psyco_set_error(PyObject *exc, cursorObject *curs, const char *msg) { - PyObject *t; PyObject *pymsg; PyObject *err = NULL; connectionObject *conn = NULL; @@ -624,31 +534,24 @@ psyco_set_error(PyObject *exc, cursorObject *curs, const char *msg, else { /* what's better than an error in an error handler in the morning? * Anyway, some error was set, refcount is ok... get outta here. */ - return; + return NULL; + } + + if (err && PyObject_TypeCheck(err, &errorType)) { + errorObject *perr = (errorObject *)err; + if (curs) { + Py_CLEAR(perr->cursor); + Py_INCREF(curs); + perr->cursor = curs; + } } if (err) { - if (curs) { - PyObject_SetAttrString(err, "cursor", (PyObject *)curs); - } - - if (pgerror) { - if ((t = conn_text_from_chars(conn, pgerror))) { - PyObject_SetAttrString(err, "pgerror", t); - Py_DECREF(t); - } - } - - if (pgcode) { - if ((t = conn_text_from_chars(conn, pgcode))) { - PyObject_SetAttrString(err, "pgcode", t); - Py_DECREF(t); - } - } - PyErr_SetObject(exc, err); Py_DECREF(err); } + + return err; } @@ -868,39 +771,59 @@ INIT_MODULE(_psycopg)(void) /* initialize all the new types and then the module */ Py_TYPE(&connectionType) = &PyType_Type; - Py_TYPE(&cursorType) = &PyType_Type; - Py_TYPE(&typecastType) = &PyType_Type; - Py_TYPE(&qstringType) = &PyType_Type; - Py_TYPE(&binaryType) = &PyType_Type; - Py_TYPE(&isqlquoteType) = &PyType_Type; - Py_TYPE(&pbooleanType) = &PyType_Type; - Py_TYPE(&pintType) = &PyType_Type; - Py_TYPE(&pfloatType) = &PyType_Type; - Py_TYPE(&pdecimalType) = &PyType_Type; - Py_TYPE(&asisType) = &PyType_Type; - Py_TYPE(&listType) = &PyType_Type; - Py_TYPE(&chunkType) = &PyType_Type; - Py_TYPE(&NotifyType) = &PyType_Type; - Py_TYPE(&XidType) = &PyType_Type; - if (PyType_Ready(&connectionType) == -1) goto exit; + + Py_TYPE(&cursorType) = &PyType_Type; if (PyType_Ready(&cursorType) == -1) goto exit; + + Py_TYPE(&typecastType) = &PyType_Type; if (PyType_Ready(&typecastType) == -1) goto exit; + + Py_TYPE(&qstringType) = &PyType_Type; if (PyType_Ready(&qstringType) == -1) goto exit; + + Py_TYPE(&binaryType) = &PyType_Type; if (PyType_Ready(&binaryType) == -1) goto exit; + + Py_TYPE(&isqlquoteType) = &PyType_Type; if (PyType_Ready(&isqlquoteType) == -1) goto exit; + + Py_TYPE(&pbooleanType) = &PyType_Type; if (PyType_Ready(&pbooleanType) == -1) goto exit; + + Py_TYPE(&pintType) = &PyType_Type; if (PyType_Ready(&pintType) == -1) goto exit; + + Py_TYPE(&pfloatType) = &PyType_Type; if (PyType_Ready(&pfloatType) == -1) goto exit; + + Py_TYPE(&pdecimalType) = &PyType_Type; if (PyType_Ready(&pdecimalType) == -1) goto exit; + + Py_TYPE(&asisType) = &PyType_Type; if (PyType_Ready(&asisType) == -1) goto exit; + + Py_TYPE(&listType) = &PyType_Type; if (PyType_Ready(&listType) == -1) goto exit; + + Py_TYPE(&chunkType) = &PyType_Type; if (PyType_Ready(&chunkType) == -1) goto exit; - if (PyType_Ready(&NotifyType) == -1) goto exit; - if (PyType_Ready(&XidType) == -1) goto exit; + + Py_TYPE(¬ifyType) = &PyType_Type; + if (PyType_Ready(¬ifyType) == -1) goto exit; + + Py_TYPE(&xidType) = &PyType_Type; + if (PyType_Ready(&xidType) == -1) goto exit; + + Py_TYPE(&errorType) = &PyType_Type; + errorType.tp_base = (PyTypeObject *)PyExc_StandardError; + if (PyType_Ready(&errorType) == -1) goto exit; + + Py_TYPE(&diagnosticsType) = &PyType_Type; + if (PyType_Ready(&diagnosticsType) == -1) goto exit; #ifdef PSYCOPG_EXTENSIONS - Py_TYPE(&lobjectType) = &PyType_Type; + Py_TYPE(&lobjectType) = &PyType_Type; if (PyType_Ready(&lobjectType) == -1) goto exit; #endif @@ -908,6 +831,7 @@ INIT_MODULE(_psycopg)(void) #ifdef HAVE_MXDATETIME Py_TYPE(&mxdatetimeType) = &PyType_Type; if (PyType_Ready(&mxdatetimeType) == -1) goto exit; + if (0 != mxDateTime_ImportModuleAndAPI()) { PyErr_Clear(); @@ -985,8 +909,9 @@ INIT_MODULE(_psycopg)(void) PyModule_AddObject(module, "connection", (PyObject*)&connectionType); PyModule_AddObject(module, "cursor", (PyObject*)&cursorType); PyModule_AddObject(module, "ISQLQuote", (PyObject*)&isqlquoteType); - PyModule_AddObject(module, "Notify", (PyObject*)&NotifyType); - PyModule_AddObject(module, "Xid", (PyObject*)&XidType); + PyModule_AddObject(module, "Notify", (PyObject*)¬ifyType); + PyModule_AddObject(module, "Xid", (PyObject*)&xidType); + PyModule_AddObject(module, "Diagnostics", (PyObject*)&diagnosticsType); #ifdef PSYCOPG_EXTENSIONS PyModule_AddObject(module, "lobject", (PyObject*)&lobjectType); #endif @@ -1015,31 +940,6 @@ INIT_MODULE(_psycopg)(void) if (0 != psyco_errors_init()) { goto exit; } psyco_errors_fill(dict); - /* Solve win32 build issue about non-constant initializer element */ - cursorType.tp_alloc = PyType_GenericAlloc; - binaryType.tp_alloc = PyType_GenericAlloc; - isqlquoteType.tp_alloc = PyType_GenericAlloc; - pbooleanType.tp_alloc = PyType_GenericAlloc; - pintType.tp_alloc = PyType_GenericAlloc; - pfloatType.tp_alloc = PyType_GenericAlloc; - pdecimalType.tp_alloc = PyType_GenericAlloc; - connectionType.tp_alloc = PyType_GenericAlloc; - asisType.tp_alloc = PyType_GenericAlloc; - qstringType.tp_alloc = PyType_GenericAlloc; - listType.tp_alloc = PyType_GenericAlloc; - chunkType.tp_alloc = PyType_GenericAlloc; - pydatetimeType.tp_alloc = PyType_GenericAlloc; - NotifyType.tp_alloc = PyType_GenericAlloc; - XidType.tp_alloc = PyType_GenericAlloc; - -#ifdef PSYCOPG_EXTENSIONS - lobjectType.tp_alloc = PyType_GenericAlloc; -#endif - -#ifdef HAVE_MXDATETIME - mxdatetimeType.tp_alloc = PyType_GenericAlloc; -#endif - Dprintf("initpsycopg: module initialization complete"); exit: diff --git a/psycopg/python.h b/psycopg/python.h index f6d6be0a..90c82516 100644 --- a/psycopg/python.h +++ b/psycopg/python.h @@ -31,36 +31,12 @@ #include #endif -#if PY_VERSION_HEX < 0x02040000 -# error "psycopg requires Python >= 2.4" -#endif - #if PY_VERSION_HEX < 0x02050000 -/* Function missing in Py 2.4 */ -#define PyErr_WarnEx(cat,msg,lvl) PyErr_Warn(cat,msg) -#endif - -#if PY_VERSION_HEX < 0x02050000 && !defined(PY_SSIZE_T_MIN) - typedef int Py_ssize_t; - #define PY_SSIZE_T_MIN INT_MIN - #define PY_SSIZE_T_MAX INT_MAX - #define PY_FORMAT_SIZE_T "" - #define PyInt_FromSsize_t(x) PyInt_FromLong((x)) - - #define lenfunc inquiry - #define ssizeargfunc intargfunc - #define readbufferproc getreadbufferproc - #define writebufferproc getwritebufferproc - #define segcountproc getsegcountproc - #define charbufferproc getcharbufferproc - - #define CONV_CODE_PY_SSIZE_T "i" -#else - #define CONV_CODE_PY_SSIZE_T "n" +# error "psycopg requires Python >= 2.5" #endif /* hash() return size changed around version 3.2a4 on 64bit platforms. Before - * this, the return size was always a long, regardless of arch. ~3.2 + * this, the return size was always a long, regardless of arch. ~3.2 * introduced the Py_hash_t & Py_uhash_t typedefs with the resulting sizes * based upon arch. */ #if PY_VERSION_HEX < 0x030200A4 @@ -76,11 +52,6 @@ typedef unsigned long Py_uhash_t; #define PyVarObject_HEAD_INIT(x,n) PyObject_HEAD_INIT(x) n, #endif -/* Missing at least in Python 2.4 */ -#ifndef Py_MEMCPY -#define Py_MEMCPY memcpy -#endif - /* FORMAT_CODE_PY_SSIZE_T is for Py_ssize_t: */ #define FORMAT_CODE_PY_SSIZE_T "%" PY_FORMAT_SIZE_T "d" @@ -114,6 +85,7 @@ typedef unsigned long Py_uhash_t; #define PyInt_AsLong PyLong_AsLong #define PyInt_FromLong PyLong_FromLong #define PyInt_FromSsize_t PyLong_FromSsize_t +#define PyExc_StandardError PyExc_Exception #define PyString_FromFormat PyUnicode_FromFormat #define Py_TPFLAGS_HAVE_ITER 0L #define Py_TPFLAGS_HAVE_RICHCOMPARE 0L diff --git a/psycopg/typecast.c b/psycopg/typecast.c index 8504631b..9678a36b 100644 --- a/psycopg/typecast.c +++ b/psycopg/typecast.c @@ -414,26 +414,27 @@ static struct PyMemberDef typecastObject_members[] = { {NULL} }; -static void -typecast_dealloc(PyObject *obj) +static int +typecast_clear(typecastObject *self) { - typecastObject *self = (typecastObject*)obj; - - PyObject_GC_UnTrack(self); - Py_CLEAR(self->values); Py_CLEAR(self->name); Py_CLEAR(self->pcast); Py_CLEAR(self->bcast); + return 0; +} - Py_TYPE(obj)->tp_free(obj); +static void +typecast_dealloc(typecastObject *self) +{ + PyObject_GC_UnTrack(self); + typecast_clear(self); + Py_TYPE(self)->tp_free((PyObject *)self); } static int -typecast_traverse(PyObject *obj, visitproc visit, void *arg) +typecast_traverse(typecastObject *self, visitproc visit, void *arg) { - typecastObject *self = (typecastObject*)obj; - Py_VISIT(self->values); Py_VISIT(self->name); Py_VISIT(self->pcast); @@ -441,12 +442,6 @@ typecast_traverse(PyObject *obj, visitproc visit, void *arg) return 0; } -static void -typecast_del(void *self) -{ - PyObject_GC_Del(self); -} - static PyObject * typecast_repr(PyObject *self) { @@ -479,8 +474,7 @@ typecast_call(PyObject *obj, PyObject *args, PyObject *kwargs) // If the string is not a string but a None value we're being called // from a Python-defined caster. if (!string) { - Py_INCREF(Py_None); - return Py_None; + Py_RETURN_NONE; } return typecast_cast(obj, string, length, cursor); @@ -489,10 +483,8 @@ typecast_call(PyObject *obj, PyObject *args, PyObject *kwargs) PyTypeObject typecastType = { PyVarObject_HEAD_INIT(NULL, 0) "psycopg2._psycopg.type", - sizeof(typecastObject), - 0, - - typecast_dealloc, /*tp_dealloc*/ + sizeof(typecastObject), 0, + (destructor)typecast_dealloc, /*tp_dealloc*/ 0, /*tp_print*/ 0, /*tp_getattr*/ 0, /*tp_setattr*/ @@ -502,48 +494,31 @@ PyTypeObject typecastType = { 0, /*tp_as_sequence*/ 0, /*tp_as_mapping*/ 0, /*tp_hash */ - typecast_call, /*tp_call*/ 0, /*tp_str*/ 0, /*tp_getattro*/ 0, /*tp_setattro*/ 0, /*tp_as_buffer*/ - Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_RICHCOMPARE | Py_TPFLAGS_HAVE_GC, /*tp_flags*/ "psycopg type-casting object", /*tp_doc*/ - - typecast_traverse, /*tp_traverse*/ - 0, /*tp_clear*/ - + (traverseproc)typecast_traverse, /*tp_traverse*/ + (inquiry)typecast_clear, /*tp_clear*/ typecast_richcompare, /*tp_richcompare*/ 0, /*tp_weaklistoffset*/ - 0, /*tp_iter*/ 0, /*tp_iternext*/ - - /* Attribute descriptor and subclassing stuff */ - - 0, /*tp_methods*/ + 0, /*tp_methods*/ typecastObject_members, /*tp_members*/ 0, /*tp_getset*/ 0, /*tp_base*/ 0, /*tp_dict*/ - 0, /*tp_descr_get*/ 0, /*tp_descr_set*/ 0, /*tp_dictoffset*/ - - 0, /*tp_init*/ - 0, /*tp_alloc will be set to PyType_GenericAlloc in module init*/ - 0, /*tp_new*/ - typecast_del, /*tp_free Low-level free-memory routine */ - 0, /*tp_is_gc For PyObject_IS_GC */ - 0, /*tp_bases*/ - 0, /*tp_mro method resolution order */ - 0, /*tp_cache*/ - 0, /*tp_subclasses*/ - 0 /*tp_weaklist*/ + 0, /*tp_init*/ + 0, /*tp_alloc*/ + 0, /*tp_new*/ }; static PyObject * diff --git a/psycopg/typecast_array.c b/psycopg/typecast_array.c index 21e2affc..adf07eee 100644 --- a/psycopg/typecast_array.c +++ b/psycopg/typecast_array.c @@ -253,7 +253,7 @@ typecast_GENERIC_ARRAY_cast(const char *str, Py_ssize_t len, PyObject *curs) Dprintf("typecast_GENERIC_ARRAY_cast: str = '%s'," " len = " FORMAT_CODE_PY_SSIZE_T, str, len); - if (str == NULL) {Py_INCREF(Py_None); return Py_None;} + if (str == NULL) { Py_RETURN_NONE; } if (str[0] == '[') typecast_array_cleanup(&str, &len); if (str[0] != '{') { diff --git a/psycopg/typecast_basic.c b/psycopg/typecast_basic.c index 5e2a93ea..a31047f3 100644 --- a/psycopg/typecast_basic.c +++ b/psycopg/typecast_basic.c @@ -31,7 +31,7 @@ typecast_INTEGER_cast(const char *s, Py_ssize_t len, PyObject *curs) { char buffer[12]; - if (s == NULL) {Py_INCREF(Py_None); return Py_None;} + if (s == NULL) { Py_RETURN_NONE; } if (s[len] != '\0') { strncpy(buffer, s, (size_t) len); buffer[len] = '\0'; s = buffer; @@ -49,7 +49,7 @@ typecast_LONGINTEGER_cast(const char *s, Py_ssize_t len, PyObject *curs) { char buffer[24]; - if (s == NULL) {Py_INCREF(Py_None); return Py_None;} + if (s == NULL) { Py_RETURN_NONE; } if (s[len] != '\0') { strncpy(buffer, s, (size_t) len); buffer[len] = '\0'; s = buffer; @@ -64,7 +64,7 @@ typecast_FLOAT_cast(const char *s, Py_ssize_t len, PyObject *curs) { PyObject *str = NULL, *flo = NULL; - if (s == NULL) {Py_INCREF(Py_None); return Py_None;} + if (s == NULL) { Py_RETURN_NONE; } if (!(str = Text_FromUTF8AndSize(s, len))) { return NULL; } #if PY_MAJOR_VERSION < 3 flo = PyFloat_FromString(str, NULL); @@ -81,7 +81,7 @@ typecast_FLOAT_cast(const char *s, Py_ssize_t len, PyObject *curs) static PyObject * typecast_STRING_cast(const char *s, Py_ssize_t len, PyObject *curs) { - if (s == NULL) {Py_INCREF(Py_None); return Py_None;} + if (s == NULL) { Py_RETURN_NONE; } return PyString_FromStringAndSize(s, len); } #else @@ -95,7 +95,7 @@ typecast_UNICODE_cast(const char *s, Py_ssize_t len, PyObject *curs) { char *enc; - if (s == NULL) {Py_INCREF(Py_None); return Py_None;} + if (s == NULL) { Py_RETURN_NONE; } enc = ((cursorObject*)curs)->conn->codec; return PyUnicode_Decode(s, len, enc, NULL); @@ -108,7 +108,7 @@ typecast_BOOLEAN_cast(const char *s, Py_ssize_t len, PyObject *curs) { PyObject *res; - if (s == NULL) {Py_INCREF(Py_None); return Py_None;} + if (s == NULL) { Py_RETURN_NONE; } if (s[0] == 't') res = Py_True; @@ -128,7 +128,7 @@ typecast_DECIMAL_cast(const char *s, Py_ssize_t len, PyObject *curs) PyObject *decimalType; char *buffer; - if (s == NULL) {Py_INCREF(Py_None); return Py_None;} + if (s == NULL) { Py_RETURN_NONE; } if ((buffer = PyMem_Malloc(len+1)) == NULL) return PyErr_NoMemory(); diff --git a/psycopg/typecast_binary.c b/psycopg/typecast_binary.c index 49eb547f..ce68fb8f 100644 --- a/psycopg/typecast_binary.c +++ b/psycopg/typecast_binary.c @@ -55,7 +55,6 @@ chunk_repr(chunkObject *self) #if PY_MAJOR_VERSION < 3 -/* XXX support 3.0 buffer protocol */ static Py_ssize_t chunk_getreadbuffer(chunkObject *self, Py_ssize_t segment, void **ptr) { @@ -90,9 +89,15 @@ static PyBufferProcs chunk_as_buffer = /* 3.0 buffer interface */ int chunk_getbuffer(PyObject *_self, Py_buffer *view, int flags) { + int rv; chunkObject *self = (chunkObject*)_self; - return PyBuffer_FillInfo(view, _self, self->base, self->len, 1, flags); + rv = PyBuffer_FillInfo(view, _self, self->base, self->len, 1, flags); + if (rv == 0) { + view->format = "c"; + } + return rv; } + static PyBufferProcs chunk_as_buffer = { chunk_getbuffer, @@ -105,9 +110,8 @@ static PyBufferProcs chunk_as_buffer = PyTypeObject chunkType = { PyVarObject_HEAD_INIT(NULL, 0) - "psycopg2._psycopg.chunk", /* tp_name */ - sizeof(chunkObject), /* tp_basicsize */ - 0, /* tp_itemsize */ + "psycopg2._psycopg.chunk", + sizeof(chunkObject), 0, (destructor) chunk_dealloc, /* tp_dealloc*/ 0, /* tp_print */ 0, /* tp_getattr */ @@ -142,7 +146,7 @@ typecast_BINARY_cast(const char *s, Py_ssize_t l, PyObject *curs) char *buffer = NULL; Py_ssize_t len; - if (s == NULL) {Py_INCREF(Py_None); return Py_None;} + if (s == NULL) { Py_RETURN_NONE; } if (s[0] == '\\' && s[1] == 'x') { /* This is a buffer escaped in hex format: libpq before 9.0 can't diff --git a/psycopg/typecast_datetime.c b/psycopg/typecast_datetime.c index 18e9d0d8..ad74101a 100644 --- a/psycopg/typecast_datetime.c +++ b/psycopg/typecast_datetime.c @@ -48,7 +48,7 @@ typecast_PYDATE_cast(const char *str, Py_ssize_t len, PyObject *curs) PyObject* obj = NULL; int n, y=0, m=0, d=0; - if (str == NULL) {Py_INCREF(Py_None); return Py_None;} + if (str == NULL) { Py_RETURN_NONE; } if (!strcmp(str, "infinity") || !strcmp(str, "-infinity")) { if (str[0] == '-') { @@ -92,7 +92,7 @@ typecast_PYDATETIME_cast(const char *str, Py_ssize_t len, PyObject *curs) int hh=0, mm=0, ss=0, us=0, tz=0; const char *tp = NULL; - if (str == NULL) {Py_INCREF(Py_None); return Py_None;} + if (str == NULL) { Py_RETURN_NONE; } /* check for infinity */ if (!strcmp(str, "infinity") || !strcmp(str, "-infinity")) { @@ -177,7 +177,7 @@ typecast_PYTIME_cast(const char *str, Py_ssize_t len, PyObject *curs) PyObject *tzinfo_factory; int n, hh=0, mm=0, ss=0, us=0, tz=0; - if (str == NULL) {Py_INCREF(Py_None); return Py_None;} + if (str == NULL) { Py_RETURN_NONE; } n = typecast_parse_time(str, NULL, &len, &hh, &mm, &ss, &us, &tz); Dprintf("typecast_PYTIME_cast: n = %d, len = " FORMAT_CODE_PY_SSIZE_T ", " @@ -226,7 +226,7 @@ typecast_PYINTERVAL_cast(const char *str, Py_ssize_t len, PyObject *curs) int part = 0, sec; double micro; - if (str == NULL) {Py_INCREF(Py_None); return Py_None;} + if (str == NULL) { Py_RETURN_NONE; } Dprintf("typecast_PYINTERVAL_cast: s = %s", str); diff --git a/psycopg/typecast_mxdatetime.c b/psycopg/typecast_mxdatetime.c index bf69af5a..4b03d158 100644 --- a/psycopg/typecast_mxdatetime.c +++ b/psycopg/typecast_mxdatetime.c @@ -51,7 +51,7 @@ typecast_MXDATE_cast(const char *str, Py_ssize_t len, PyObject *curs) int hh=0, mm=0, ss=0, us=0, tz=0; const char *tp = NULL; - if (str == NULL) {Py_INCREF(Py_None); return Py_None;} + if (str == NULL) { Py_RETURN_NONE; } Dprintf("typecast_MXDATE_cast: s = %s", str); @@ -99,7 +99,7 @@ typecast_MXTIME_cast(const char *str, Py_ssize_t len, PyObject *curs) { int n, hh=0, mm=0, ss=0, us=0, tz=0; - if (str == NULL) {Py_INCREF(Py_None); return Py_None;} + if (str == NULL) { Py_RETURN_NONE; } Dprintf("typecast_MXTIME_cast: s = %s", str); @@ -129,7 +129,7 @@ typecast_MXINTERVAL_cast(const char *str, Py_ssize_t len, PyObject *curs) double v = 0.0, sign = 1.0; int part = 0; - if (str == NULL) {Py_INCREF(Py_None); return Py_None;} + if (str == NULL) { Py_RETURN_NONE; } Dprintf("typecast_MXINTERVAL_cast: s = %s", str); diff --git a/psycopg/utils.c b/psycopg/utils.c index 57586c57..6b035cfa 100644 --- a/psycopg/utils.c +++ b/psycopg/utils.c @@ -32,21 +32,33 @@ #include #include +/* Escape a string for sql inclusion. + * + * The function must be called holding the GIL. + * + * Return a pointer to a new string on the Python heap on success, else NULL + * and set an exception. The returned string includes quotes and leading E if + * needed. + * + * If tolen is set, it will contain the length of the escaped string, + * including quotes. + */ char * -psycopg_escape_string(PyObject *obj, const char *from, Py_ssize_t len, +psycopg_escape_string(connectionObject *conn, const char *from, Py_ssize_t len, char *to, Py_ssize_t *tolen) { Py_ssize_t ql; - connectionObject *conn = (connectionObject*)obj; - int eq = (conn && (conn->equote)) ? 1 : 0; + int eq = (conn && (conn->equote)) ? 1 : 0; if (len == 0) len = strlen(from); - + if (to == NULL) { to = (char *)PyMem_Malloc((len * 2 + 4) * sizeof(char)); - if (to == NULL) + if (to == NULL) { + PyErr_NoMemory(); return NULL; + } } { @@ -59,15 +71,19 @@ psycopg_escape_string(PyObject *obj, const char *from, Py_ssize_t len, ql = PQescapeString(to+eq+1, from, len); } - if (eq) + if (eq) { to[0] = 'E'; - to[eq] = '\''; - to[ql+eq+1] = '\''; - to[ql+eq+2] = '\0'; + to[1] = to[ql+2] = '\''; + to[ql+3] = '\0'; + } + else { + to[0] = to[ql+1] = '\''; + to[ql+2] = '\0'; + } if (tolen) *tolen = ql+eq+2; - + return to; } @@ -115,10 +131,16 @@ psycopg_escape_identifier_easy(const char *from, Py_ssize_t len) * * Store the return in 'to' and return 0 in case of success, else return -1 * and raise an exception. + * + * If from is null, store null into to. */ RAISES_NEG int psycopg_strdup(char **to, const char *from, Py_ssize_t len) { + if (!from) { + *to = NULL; + return 0; + } if (!len) { len = strlen(from); } if (!(*to = PyMem_Malloc(len + 1))) { PyErr_NoMemory(); diff --git a/psycopg/xid.h b/psycopg/xid.h index e63db619..6f641beb 100644 --- a/psycopg/xid.h +++ b/psycopg/xid.h @@ -27,7 +27,7 @@ #ifndef PSYCOPG_XID_H #define PSYCOPG_XID_H 1 -extern HIDDEN PyTypeObject XidType; +extern HIDDEN PyTypeObject xidType; typedef struct { PyObject_HEAD @@ -41,11 +41,11 @@ typedef struct { PyObject *prepared; PyObject *owner; PyObject *database; -} XidObject; +} xidObject; -HIDDEN XidObject *xid_ensure(PyObject *oxid); -HIDDEN XidObject *xid_from_string(PyObject *s); -HIDDEN PyObject *xid_get_tid(XidObject *self); +HIDDEN xidObject *xid_ensure(PyObject *oxid); +HIDDEN xidObject *xid_from_string(PyObject *s); +HIDDEN PyObject *xid_get_tid(xidObject *self); HIDDEN PyObject *xid_recover(PyObject *conn); #endif /* PSYCOPG_XID_H */ diff --git a/psycopg/xid_type.c b/psycopg/xid_type.c index b38d9145..ab6c33e2 100644 --- a/psycopg/xid_type.c +++ b/psycopg/xid_type.c @@ -67,46 +67,28 @@ static const char database_doc[] = "Database the recovered transaction belongs to."; static PyMemberDef xid_members[] = { - { "format_id", T_OBJECT, offsetof(XidObject, format_id), READONLY, (char *)format_id_doc }, - { "gtrid", T_OBJECT, offsetof(XidObject, gtrid), READONLY, (char *)gtrid_doc }, - { "bqual", T_OBJECT, offsetof(XidObject, bqual), READONLY, (char *)bqual_doc }, - { "prepared", T_OBJECT, offsetof(XidObject, prepared), READONLY, (char *)prepared_doc }, - { "owner", T_OBJECT, offsetof(XidObject, owner), READONLY, (char *)owner_doc }, - { "database", T_OBJECT, offsetof(XidObject, database), READONLY, (char *)database_doc }, + { "format_id", T_OBJECT, offsetof(xidObject, format_id), READONLY, (char *)format_id_doc }, + { "gtrid", T_OBJECT, offsetof(xidObject, gtrid), READONLY, (char *)gtrid_doc }, + { "bqual", T_OBJECT, offsetof(xidObject, bqual), READONLY, (char *)bqual_doc }, + { "prepared", T_OBJECT, offsetof(xidObject, prepared), READONLY, (char *)prepared_doc }, + { "owner", T_OBJECT, offsetof(xidObject, owner), READONLY, (char *)owner_doc }, + { "database", T_OBJECT, offsetof(xidObject, database), READONLY, (char *)database_doc }, { NULL } }; static PyObject * xid_new(PyTypeObject *type, PyObject *args, PyObject *kwargs) { - XidObject *self; - - if (!(self = (XidObject *)type->tp_alloc(type, 0))) { return NULL; } - - Py_INCREF(Py_None); - self->format_id = Py_None; - Py_INCREF(Py_None); - self->gtrid = Py_None; - Py_INCREF(Py_None); - self->bqual = Py_None; - Py_INCREF(Py_None); - self->prepared = Py_None; - Py_INCREF(Py_None); - self->owner = Py_None; - Py_INCREF(Py_None); - self->database = Py_None; - - return (PyObject *)self; + return type->tp_alloc(type, 0); } static int -xid_init(XidObject *self, PyObject *args, PyObject *kwargs) +xid_init(xidObject *self, PyObject *args, PyObject *kwargs) { static char *kwlist[] = {"format_id", "gtrid", "bqual", NULL}; int format_id; size_t i, gtrid_len, bqual_len; const char *gtrid, *bqual; - PyObject *tmp; if (!PyArg_ParseTupleAndKeywords(args, kwargs, "iss", kwlist, &format_id, >rid, &bqual)) @@ -149,35 +131,18 @@ xid_init(XidObject *self, PyObject *args, PyObject *kwargs) } } - tmp = self->format_id; - self->format_id = PyInt_FromLong(format_id); - Py_XDECREF(tmp); + if (!(self->format_id = PyInt_FromLong(format_id))) { return -1; } + if (!(self->gtrid = Text_FromUTF8(gtrid))) { return -1; } + if (!(self->bqual = Text_FromUTF8(bqual))) { return -1; } + Py_INCREF(Py_None); self->prepared = Py_None; + Py_INCREF(Py_None); self->owner = Py_None; + Py_INCREF(Py_None); self->database = Py_None; - tmp = self->gtrid; - self->gtrid = Text_FromUTF8(gtrid); - Py_XDECREF(tmp); - - tmp = self->bqual; - self->bqual = Text_FromUTF8(bqual); - Py_XDECREF(tmp); - - return 0; -} - -static int -xid_traverse(XidObject *self, visitproc visit, void *arg) -{ - Py_VISIT(self->format_id); - Py_VISIT(self->gtrid); - Py_VISIT(self->bqual); - Py_VISIT(self->prepared); - Py_VISIT(self->owner); - Py_VISIT(self->database); return 0; } static void -xid_dealloc(XidObject *self) +xid_dealloc(xidObject *self) { Py_CLEAR(self->format_id); Py_CLEAR(self->gtrid); @@ -189,20 +154,14 @@ xid_dealloc(XidObject *self) Py_TYPE(self)->tp_free((PyObject *)self); } -static void -xid_del(PyObject *self) -{ - PyObject_GC_Del(self); -} - static Py_ssize_t -xid_len(XidObject *self) +xid_len(xidObject *self) { return 3; } static PyObject * -xid_getitem(XidObject *self, Py_ssize_t item) +xid_getitem(xidObject *self, Py_ssize_t item) { if (item < 0) item += 3; @@ -224,13 +183,13 @@ xid_getitem(XidObject *self, Py_ssize_t item) } static PyObject * -xid_str(XidObject *self) +xid_str(xidObject *self) { return xid_get_tid(self); } static PyObject * -xid_repr(XidObject *self) +xid_repr(xidObject *self) { PyObject *rv = NULL; PyObject *format = NULL; @@ -305,66 +264,45 @@ static struct PyMethodDef xid_methods[] = { {NULL} }; -PyTypeObject XidType = { +PyTypeObject xidType = { PyVarObject_HEAD_INIT(NULL, 0) "psycopg2.extensions.Xid", - sizeof(XidObject), - 0, + sizeof(xidObject), 0, (destructor)xid_dealloc, /* tp_dealloc */ 0, /*tp_print*/ - 0, /*tp_getattr*/ 0, /*tp_setattr*/ - 0, /*tp_compare*/ - (reprfunc)xid_repr, /*tp_repr*/ 0, /*tp_as_number*/ &xid_sequence, /*tp_as_sequence*/ 0, /*tp_as_mapping*/ 0, /*tp_hash */ - 0, /*tp_call*/ (reprfunc)xid_str, /*tp_str*/ - 0, /*tp_getattro*/ 0, /*tp_setattro*/ 0, /*tp_as_buffer*/ - - Py_TPFLAGS_DEFAULT|Py_TPFLAGS_HAVE_GC, /*tp_flags*/ + /* Notify is not GC as it only has string attributes */ + Py_TPFLAGS_DEFAULT|Py_TPFLAGS_BASETYPE, /*tp_flags*/ xid_doc, /*tp_doc*/ - - (traverseproc)xid_traverse, /*tp_traverse*/ + 0, /*tp_traverse*/ 0, /*tp_clear*/ - 0, /*tp_richcompare*/ 0, /*tp_weaklistoffset*/ - 0, /*tp_iter*/ 0, /*tp_iternext*/ - - /* Attribute descriptor and subclassing stuff */ - xid_methods, /*tp_methods*/ xid_members, /*tp_members*/ 0, /*tp_getset*/ 0, /*tp_base*/ 0, /*tp_dict*/ - 0, /*tp_descr_get*/ 0, /*tp_descr_set*/ 0, /*tp_dictoffset*/ - (initproc)xid_init, /*tp_init*/ - 0, /*tp_alloc will be set to PyType_GenericAlloc in module init*/ + 0, /*tp_alloc*/ xid_new, /*tp_new*/ - (freefunc)xid_del, /*tp_free Low-level free-memory routine */ - 0, /*tp_is_gc For PyObject_IS_GC */ - 0, /*tp_bases*/ - 0, /*tp_mro method resolution order */ - 0, /*tp_cache*/ - 0, /*tp_subclasses*/ - 0 /*tp_weaklist*/ }; @@ -376,13 +314,13 @@ PyTypeObject XidType = { * or use a regular string they have found in PostgreSQL's pg_prepared_xacts * in order to recover a transaction not generated by psycopg. */ -XidObject *xid_ensure(PyObject *oxid) +xidObject *xid_ensure(PyObject *oxid) { - XidObject *rv = NULL; + xidObject *rv = NULL; - if (PyObject_TypeCheck(oxid, &XidType)) { + if (PyObject_TypeCheck(oxid, &xidType)) { Py_INCREF(oxid); - rv = (XidObject *)oxid; + rv = (xidObject *)oxid; } else { rv = xid_from_string(oxid); @@ -445,7 +383,7 @@ _xid_decode64(PyObject *s) * http://cvs.pgfoundry.org/cgi-bin/cvsweb.cgi/jdbc/pgjdbc/org/postgresql/xa/RecoveredXid.java?rev=1.2 */ PyObject * -xid_get_tid(XidObject *self) +xid_get_tid(xidObject *self) { PyObject *rv = NULL; PyObject *egtrid = NULL; @@ -525,7 +463,7 @@ exit: * * Return NULL + exception if parsing failed. Else a new Xid object. */ -static XidObject * +static xidObject * _xid_parse_string(PyObject *str) { PyObject *regex; PyObject *m = NULL; @@ -536,7 +474,7 @@ _xid_parse_string(PyObject *str) { PyObject *ebqual = NULL; PyObject *gtrid = NULL; PyObject *bqual = NULL; - XidObject *rv = NULL; + xidObject *rv = NULL; /* check if the string is a possible XA triple with a regexp */ if (!(regex = _xid_get_parse_regex())) { goto exit; } @@ -560,7 +498,7 @@ _xid_parse_string(PyObject *str) { if (!(bqual = _xid_decode64(ebqual))) { goto exit; } /* Try to build the xid with the parsed material */ - rv = (XidObject *)PyObject_CallFunctionObjArgs((PyObject *)&XidType, + rv = (xidObject *)PyObject_CallFunctionObjArgs((PyObject *)&xidType, format_id, gtrid, bqual, NULL); exit: @@ -579,35 +517,31 @@ exit: /* Return a new Xid object representing a transaction ID not conform to * the XA specifications. */ -static XidObject * +static xidObject * _xid_unparsed_from_string(PyObject *str) { - XidObject *xid = NULL; - XidObject *rv = NULL; - PyObject *tmp; + xidObject *xid = NULL; + xidObject *rv = NULL; /* fake args to work around the checks performed by the xid init */ - if (!(xid = (XidObject *)PyObject_CallFunction((PyObject *)&XidType, + if (!(xid = (xidObject *)PyObject_CallFunction((PyObject *)&xidType, "iss", 0, "", ""))) { goto exit; } /* set xid.gtrid = str */ - tmp = xid->gtrid; + Py_CLEAR(xid->gtrid); Py_INCREF(str); xid->gtrid = str; - Py_DECREF(tmp); /* set xid.format_id = None */ - tmp = xid->format_id; + Py_CLEAR(xid->format_id); Py_INCREF(Py_None); xid->format_id = Py_None; - Py_DECREF(tmp); /* set xid.bqual = None */ - tmp = xid->bqual; + Py_CLEAR(xid->bqual); Py_INCREF(Py_None); xid->bqual = Py_None; - Py_DECREF(tmp); /* return the finished object */ rv = xid; @@ -624,9 +558,9 @@ exit: * If the xid is in the format generated by Psycopg, unpack the tuple into * the struct members. Otherwise generate an "unparsed" xid. */ -XidObject * +xidObject * xid_from_string(PyObject *str) { - XidObject *rv; + xidObject *rv; if (!(Bytes_Check(str) || PyUnicode_Check(str))) { PyErr_SetString(PyExc_TypeError, "not a valid transaction id"); @@ -654,7 +588,7 @@ xid_recover(PyObject *conn) PyObject *rv = NULL; PyObject *curs = NULL; PyObject *xids = NULL; - XidObject *xid = NULL; + xidObject *xid = NULL; PyObject *recs = NULL; PyObject *rec = NULL; PyObject *item = NULL; @@ -693,34 +627,25 @@ xid_recover(PyObject *conn) /* Get the xid with the XA triple set */ if (!(item = PySequence_GetItem(rec, 0))) { goto exit; } if (!(xid = xid_from_string(item))) { goto exit; } - Py_DECREF(item); item = NULL; + Py_CLEAR(item); /* set xid.prepared */ - if (!(item = PySequence_GetItem(rec, 1))) { goto exit; } - tmp = xid->prepared; - xid->prepared = item; - Py_DECREF(tmp); - item = NULL; + Py_CLEAR(xid->prepared); + if (!(xid->prepared = PySequence_GetItem(rec, 1))) { goto exit; } /* set xid.owner */ - if (!(item = PySequence_GetItem(rec, 2))) { goto exit; } - tmp = xid->owner; - xid->owner = item; - Py_DECREF(tmp); - item = NULL; + Py_CLEAR(xid->owner); + if (!(xid->owner = PySequence_GetItem(rec, 2))) { goto exit; } /* set xid.database */ - if (!(item = PySequence_GetItem(rec, 3))) { goto exit; } - tmp = xid->database; - xid->database = item; - Py_DECREF(tmp); - item = NULL; + Py_CLEAR(xid->database); + if (!(xid->database = PySequence_GetItem(rec, 3))) { goto exit; } /* xid finished: add it to the returned list */ PyList_SET_ITEM(xids, i, (PyObject *)xid); xid = NULL; /* ref stolen */ - Py_DECREF(rec); rec = NULL; + Py_CLEAR(rec); } /* set the return value. */ diff --git a/psycopg2da/DEPENDENCIES.cfg b/psycopg2da/DEPENDENCIES.cfg deleted file mode 100644 index f4edbac9..00000000 --- a/psycopg2da/DEPENDENCIES.cfg +++ /dev/null @@ -1,2 +0,0 @@ -psycopg2 -zope.app diff --git a/psycopg2da/PACKAGE.cfg b/psycopg2da/PACKAGE.cfg deleted file mode 100644 index 7e1bbfc1..00000000 --- a/psycopg2da/PACKAGE.cfg +++ /dev/null @@ -1,24 +0,0 @@ -# Load the license from an external source, so we don't have to keep a -# copy of it sitting around: - - LICENSE.txt http://svn.zope.org/*checkout*/Zope3/trunk/ZopePublicLicense.txt?rev=25177 - - -# Add a few things to the distribution root. - - README.txt - - -# Specify what is included in the component. - - - # Documentation files of the package: - *.txt - - # Python modules from the package: - *.py - - # Configuration files of the package: - *.zcml - - diff --git a/psycopg2da/PUBLICATION.cfg b/psycopg2da/PUBLICATION.cfg deleted file mode 100644 index d4083144..00000000 --- a/psycopg2da/PUBLICATION.cfg +++ /dev/null @@ -1,9 +0,0 @@ -Metadata-Version: 1.0 -Name: psycopg2da -Summary: Psycopg2 Database Adapter for Zope 3 -Author: Fabio Tranchitella -Author-email: kobold@debian.org -License: ZPL 2.1 -Description: - This package allows Zope 3 to connect to any PostGreSQL database via - the common Zope 3 RDB connection facilities. diff --git a/psycopg2da/README.txt b/psycopg2da/README.txt deleted file mode 100644 index 48d2fdda..00000000 --- a/psycopg2da/README.txt +++ /dev/null @@ -1,79 +0,0 @@ -========== -psycopg2da -========== - -This file outlines the basics of using Zope3 with PostgreSQL via PsycopgDA. - -Installing PsycopgDA --------------------- - -1. Check out the psycopg2da package into a directory in your - PYTHONPATH. INSTANCE_HOME/lib/python or Zope3/src is usually the - most convenient place: - - - svn co svn://svn.zope.org/repos/main/psycopg2da/trunk psycopg2da - - -2. Copy `psycopg2-configure.zcml` to the `package-includes` directory - of your Zope instance. - - -Creating Database Connections ------------------------------- - -It is time to add some connections. A connection in Zope 3 is -registered as a utility. - -3. Open a web browser on your Zope root folder (http://localhost:8080/ - if you use the default settings in zope.conf.in). - -4. Click on the 'Manage Site' action on the right side of the - screen. You should see a screen which reads 'Common Site Management - Tasks' - -5. Around the middle of that page, you should see a link named 'Add - Utility'. Click on it. - -6. Select 'Psycopg DA' and type in a name at the bottom of the page. - -7. Enter the database connection string. It looks like this: - - dbi://username:password@host:port/databasename - -8. Click on the 'Add' button. - -9. You should be on a page which reads 'Add Database Connection - Registration'. There you can configure the permission needed to use - the database connection, the name of the registration and the - registration status. You can use any name for 'Register As' field, - as long as it doesn't clash with an existing one. Choose a - permission. Choose between 'Registered' and 'Active' for the - 'Registration Status'. Only one component of a kind can be 'Active' - at a time, so be careful. - -10. You should be redirected to the 'Edit' screen of the connection - utility. - -11. If you want to, you can go to the Test page and execute arbitrary - SQL queries to see whether the connection is working as expected. - - -Using SQL Scripts ------------------ - -You can create SQL Scripts in the content space. For example: - -12. Go to Zope root. - -13. Add an SQL script (you can use the Common Tasks box on the left, - or the Add action on the right). - -14. Click on the name of your new SQL script. - -15. Choose a connection name (the one you entered in step 29) from the - drop-down. - -16. Enter your query and click on the 'Save Changes' button. - -17. You can test the script in the -- surprise! -- Test page. diff --git a/psycopg2da/__init__.py b/psycopg2da/__init__.py deleted file mode 100644 index fa81adaf..00000000 --- a/psycopg2da/__init__.py +++ /dev/null @@ -1 +0,0 @@ -# empty file diff --git a/psycopg2da/adapter.py b/psycopg2da/adapter.py deleted file mode 100644 index 2ddc0a2a..00000000 --- a/psycopg2da/adapter.py +++ /dev/null @@ -1,408 +0,0 @@ -# Copyright (C) 2006 Fabio Tranchitella -# -# psycopg2da is free software: you can redistribute it and/or modify it -# under the terms of the GNU Lesser General Public License as published -# by the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# In addition, as a special exception, the copyright holders give -# permission to link this program with the OpenSSL library (or with -# modified versions of OpenSSL that use the same license as OpenSSL), -# and distribute linked combinations including the two. -# -# You must obey the GNU Lesser General Public License in all respects for -# all of the code used other than OpenSSL. -# -# psycopg2da is distributed in the hope that it will be useful, but WITHOUT -# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or -# FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public -# License for more details. -# -# Based on ZPsycopgDA. -# -# If you prefer you can use this package using the ZPL license as -# published on the Zope web site, http://www.zope.org/Resources/ZPL. - -"""PostgreSQL Database Adapter for Zope 3""" - -from zope.interface import implements -from zope.rdb import ZopeDatabaseAdapter, parseDSN, ZopeConnection, ZopeCursor -from zope.rdb.interfaces import DatabaseException, IZopeConnection -from zope.publisher.interfaces import Retry - -from datetime import date, time, datetime, timedelta - -import psycopg2 -import psycopg2.extensions -import re -import sys - -# OIDs from psycopg/pgtypes.h -DATE_OID = 1082 -TIME_OID = 1083 -TIMETZ_OID = 1266 -TIMESTAMP_OID = 1114 -TIMESTAMPTZ_OID = 1184 -INTERVAL_OID = 1186 -CHAR_OID = 18 -TEXT_OID = 25 -BPCHAR_OID = 1042 -VARCHAR_OID = 1043 - -# date/time parsing functions -_dateFmt = re.compile(r"^(\d\d\d\d)-?([01]\d)-?([0-3]\d)$") - -def parse_date(s): - """Parses ISO-8601 compliant dates and returns a tuple (year, month, - day). - - The following formats are accepted: - YYYY-MM-DD (extended format) - YYYYMMDD (basic format) - """ - m = _dateFmt.match(s) - if m is None: - raise ValueError, 'invalid date string: %s' % s - year, month, day = m.groups() - return int(year), int(month), int(day) - - -_timeFmt = re.compile( - r"^([0-2]\d)(?::?([0-5]\d)(?::?([0-5]\d)(?:[.,](\d+))?)?)?$") - -def parse_time(s): - """Parses ISO-8601 compliant times and returns a tuple (hour, minute, - second). - - The following formats are accepted: - HH:MM:SS.ssss or HHMMSS.ssss - HH:MM:SS,ssss or HHMMSS,ssss - HH:MM:SS or HHMMSS - HH:MM or HHMM - HH - """ - m = _timeFmt.match(s) - if m is None: - raise ValueError, 'invalid time string: %s' % s - hr, mn, sc, msc = m.groups(0) - if msc != 0: - sc = float("%s.%s" % (sc, msc)) - else: - sc = int(sc) - return int(hr), int(mn), sc - - -_tzFmt = re.compile(r"^([+-])([0-2]\d)(?::?([0-5]\d))?$") - -def parse_tz(s): - """Parses ISO-8601 timezones and returns the offset east of UTC in - minutes. - - The following formats are accepted: - +/-HH:MM - +/-HHMM - +/-HH - Z (equivalent to +0000) - """ - if s == 'Z': - return 0 - m = _tzFmt.match(s) - if m is None: - raise ValueError, 'invalid time zone: %s' % s - d, hoff, moff = m.groups(0) - if d == "-": - return - int(hoff) * 60 - int(moff) - return int(hoff) * 60 + int(moff) - - -_tzPos = re.compile(r"[Z+-]") - -def parse_timetz(s): - """Parses ISO-8601 compliant times that may include timezone information - and returns a tuple (hour, minute, second, tzoffset). - - tzoffset is the offset east of UTC in minutes. It will be None if s does - not include time zone information. - - Formats accepted are those listed in the descriptions of parse_time() and - parse_tz(). Time zone should immediatelly follow time without intervening - spaces. - """ - m = _tzPos.search(s) - if m is None: - return parse_time(s) + (None,) - pos = m.start() - return parse_time(s[:pos]) + (parse_tz(s[pos:]),) - - -_datetimeFmt = re.compile(r"[T ]") - -def _split_datetime(s): - """Split date and time parts of ISO-8601 compliant timestamp and - return a tuple (date, time). - - ' ' or 'T' used to separate date and time parts. - """ - m = _datetimeFmt.search(s) - if m is None: - raise ValueError, 'time part of datetime missing: %s' % s - pos = m.start() - return s[:pos], s[pos + 1:] - - -def parse_datetime(s): - """Parses ISO-8601 compliant timestamp and returns a tuple (year, month, - day, hour, minute, second). - - Formats accepted are those listed in the descriptions of parse_date() and - parse_time() with ' ' or 'T' used to separate date and time parts. - """ - dt, tm = _split_datetime(s) - return parse_date(dt) + parse_time(tm) - - -def parse_datetimetz(s): - """Parses ISO-8601 compliant timestamp that may include timezone - information and returns a tuple (year, month, day, hour, minute, second, - tzoffset). - - tzoffset is the offset east of UTC in minutes. It will be None if s does - not include time zone information. - - Formats accepted are those listed in the descriptions of parse_date() and - parse_timetz() with ' ' or 'T' used to separate date and time parts. - """ - dt, tm = _split_datetime(s) - return parse_date(dt) + parse_timetz(tm) - - -def parse_interval(s): - """Parses PostgreSQL interval notation and returns a tuple (years, months, - days, hours, minutes, seconds). - - Values accepted: - interval ::= date - | time - | date time - date ::= date_comp - | date date_comp - date_comp ::= 1 'day' - | number 'days' - | 1 'month' - | 1 'mon' - | number 'months' - | number 'mons' - | 1 'year' - | number 'years' - time ::= number ':' number - | number ':' number ':' number - | number ':' number ':' number '.' fraction - """ - years = months = days = 0 - hours = minutes = seconds = 0 - elements = s.split() - # Tests with 7.4.6 on Ubuntu 5.4 interval output returns 'mon' and 'mons' - # and not 'month' or 'months' as expected. I've fixed this and left - # the original matches there too in case this is dependant on - # OS or PostgreSQL release. - for i in range(0, len(elements) - 1, 2): - count, unit = elements[i:i+2] - if unit == 'day' and count == '1': - days += 1 - elif unit == 'days': - days += int(count) - elif unit == 'month' and count == '1': - months += 1 - elif unit == 'mon' and count == '1': - months += 1 - elif unit == 'months': - months += int(count) - elif unit == 'mons': - months += int(count) - elif unit == 'year' and count == '1': - years += 1 - elif unit == 'years': - years += int(count) - else: - raise ValueError, 'unknown time interval %s %s' % (count, unit) - if len(elements) % 2 == 1: - hours, minutes, seconds = parse_time(elements[-1]) - return (years, months, days, hours, minutes, seconds) - - -# Type conversions -def _conv_date(s, cursor): - if s: - return date(*parse_date(s)) - -def _conv_time(s, cursor): - if s: - hr, mn, sc = parse_time(s) - sc, micro = divmod(sc, 1.0) - micro = round(micro * 1000000) - return time(hr, mn, int(sc), int(micro)) - -def _conv_timetz(s, cursor): - if s: - from zope.datetime import tzinfo - hr, mn, sc, tz = parse_timetz(s) - sc, micro = divmod(sc, 1.0) - micro = round(micro * 1000000) - if tz: tz = tzinfo(tz) - return time(hr, mn, int(sc), int(micro), tz) - -def _conv_timestamp(s, cursor): - if s: - y, m, d, hr, mn, sc = parse_datetime(s) - sc, micro = divmod(sc, 1.0) - micro = round(micro * 1000000) - return datetime(y, m, d, hr, mn, int(sc), int(micro)) - -def _conv_timestamptz(s, cursor): - if s: - from zope.datetime import tzinfo - y, m, d, hr, mn, sc, tz = parse_datetimetz(s) - sc, micro = divmod(sc, 1.0) - micro = round(micro * 1000000) - if tz: tz = tzinfo(tz) - return datetime(y, m, d, hr, mn, int(sc), int(micro), tz) - -def _conv_interval(s, cursor): - if s: - y, m, d, hr, mn, sc = parse_interval(s) - if (y, m) != (0, 0): - # XXX: Currently there's no way to represent years and months as - # timedeltas - return s - else: - return timedelta(days=d, hours=hr, minutes=mn, seconds=sc) - -def _get_string_conv(encoding): - def _conv_string(s, cursor): - if s is not None: - s = s.decode(encoding) - return s - return _conv_string - -# User-defined types -DATE = psycopg2.extensions.new_type((DATE_OID,), "ZDATE", _conv_date) -TIME = psycopg2.extensions.new_type((TIME_OID,), "ZTIME", _conv_time) -TIMETZ = psycopg2.extensions.new_type((TIMETZ_OID,), "ZTIMETZ", _conv_timetz) -TIMESTAMP = psycopg2.extensions.new_type((TIMESTAMP_OID,), "ZTIMESTAMP", _conv_timestamp) -TIMESTAMPTZ = psycopg2.extensions.new_type((TIMESTAMPTZ_OID,), "ZTIMESTAMPTZ", _conv_timestamptz) -INTERVAL = psycopg2.extensions.new_type((INTERVAL_OID,), "ZINTERVAL", _conv_interval) - -def registerTypes(encoding): - """Register type conversions for psycopg""" - psycopg2.extensions.register_type(DATE) - psycopg2.extensions.register_type(TIME) - psycopg2.extensions.register_type(TIMETZ) - psycopg2.extensions.register_type(TIMESTAMP) - psycopg2.extensions.register_type(TIMESTAMPTZ) - psycopg2.extensions.register_type(INTERVAL) - STRING = psycopg2.extensions.new_type((CHAR_OID, TEXT_OID, BPCHAR_OID, VARCHAR_OID), "ZSTRING", _get_string_conv(encoding)) - psycopg2.extensions.register_type(STRING) - psycopg2.extensions.register_type(psycopg2.extensions.UNICODEARRAY) - - -dsn2option_mapping = {'host': 'host', - 'port': 'port', - 'dbname': 'dbname', - 'username': 'user', - 'password': 'password'} - -class Psycopg2Adapter(ZopeDatabaseAdapter): - """A psycopg2 adapter for Zope3. - - The following type conversions are performed: - - DATE -> datetime.date - TIME -> datetime.time - TIMETZ -> datetime.time - TIMESTAMP -> datetime.datetime - TIMESTAMPTZ -> datetime.datetime - - XXX: INTERVAL cannot be represented exactly as datetime.timedelta since - it might be something like '1 month', which is a variable number of days. - """ - - def connect(self): - if not self.isConnected(): - try: - self._v_connection = Psycopg2Connection( - self._connection_factory(), self - ) - except psycopg2.Error, error: - raise DatabaseException, str(error) - - def registerTypes(self): - registerTypes(self.getEncoding()) - - def _connection_factory(self): - """Create a psycopg2 DBI connection based on the DSN""" - self.registerTypes() - conn_info = parseDSN(self.dsn) - conn_list = [] - for dsnname, optname in dsn2option_mapping.iteritems(): - if conn_info[dsnname]: - conn_list.append('%s=%s' % (optname, conn_info[dsnname])) - conn_str = ' '.join(conn_list) - connection = psycopg2.connect(conn_str) - connection.set_isolation_level(psycopg2.extensions.ISOLATION_LEVEL_SERIALIZABLE) - return connection - - def disconnect(self): - if self.isConnected(): - try: - self._v_connection.close() - except psycopg2.InterfaceError: - pass - self._v_connection = None - - -def _handle_psycopg_exception(error): - """Called from a exception handler for psycopg2.Error. - - If we have a serialization exception or a deadlock, we should retry the - transaction by raising a Retry exception. Otherwise, we reraise. - """ - if isinstance(error, psycopg2.extensions.TransactionRollbackError): - raise Retry(sys.exc_info()) - raise - - -class IPsycopg2ZopeConnection(IZopeConnection): - """A marker interface stating that this connection uses PostgreSQL.""" - - -class Psycopg2Connection(ZopeConnection): - - implements(IPsycopg2ZopeConnection) - - def cursor(self): - """See IZopeConnection""" - return Psycopg2Cursor(self.conn.cursor(), self) - - def commit(self): - try: - ZopeConnection.commit(self) - except psycopg2.Error, error: - _handle_psycopg_exception(error) - - -class Psycopg2Cursor(ZopeCursor): - - def execute(self, operation, parameters=None): - """See IZopeCursor""" - try: - return ZopeCursor.execute(self, operation, parameters) - except psycopg2.Error, error: - _handle_psycopg_exception(error) - - def executemany(operation, seq_of_parameters=None): - """See IZopeCursor""" - raise RuntimeError, 'Oos' - try: - return ZopeCursor.execute(self, operation, seq_of_parameters) - except psycopg2.Error, error: - _handle_psycopg_exception(error) diff --git a/psycopg2da/configure.zcml b/psycopg2da/configure.zcml deleted file mode 100644 index 7671fb75..00000000 --- a/psycopg2da/configure.zcml +++ /dev/null @@ -1,51 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - diff --git a/psycopg2da/psycopg2da-configure.zcml b/psycopg2da/psycopg2da-configure.zcml deleted file mode 100644 index 2a395437..00000000 --- a/psycopg2da/psycopg2da-configure.zcml +++ /dev/null @@ -1 +0,0 @@ - diff --git a/psycopg2da/tests.py b/psycopg2da/tests.py deleted file mode 100644 index f8620cbb..00000000 --- a/psycopg2da/tests.py +++ /dev/null @@ -1,395 +0,0 @@ -# Copyright (C) 2006 Fabio Tranchitella -# -# psycopg2da is free software: you can redistribute it and/or modify it -# under the terms of the GNU Lesser General Public License as published -# by the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# In addition, as a special exception, the copyright holders give -# permission to link this program with the OpenSSL library (or with -# modified versions of OpenSSL that use the same license as OpenSSL), -# and distribute linked combinations including the two. -# -# You must obey the GNU Lesser General Public License in all respects for -# all of the code used other than OpenSSL. -# -# psycopg2da is distributed in the hope that it will be useful, but WITHOUT -# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or -# FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public -# License for more details. -# -# If you prefer you can use this package using the ZPL license as -# published on the Zope web site, http://www.zope.org/Resources/ZPL. - -"""Unit tests for Psycopg2DA.""" - -from unittest import TestCase, TestSuite, main, makeSuite -from datetime import tzinfo, timedelta - -import psycopg2 -import psycopg2.extensions - - -class Stub(object): - - def __init__(self, **kw): - self.__dict__.update(kw) - -class TZStub(tzinfo): - - def __init__(self, h, m): - self.offset = h * 60 + m - - def utcoffset(self, dt): - return timedelta(minutes=self.offset) - - def dst(self, dt): - return 0 - - def tzname(self, dt): - return '' - - def __repr__(self): - return 'tzinfo(%d)' % self.offset - - def __reduce__(self): - return type(self), (), self.__dict__ - -class ConnectionStub(object): - - def set_isolation_level(self, level): - pass - -class Psycopg2Stub(object): - - __shared_state = {} # 'Borg' design pattern - - DATE = psycopg2.extensions.DATE - TIME = psycopg2.extensions.TIME - DATETIME = psycopg2.DATETIME - INTERVAL = psycopg2.extensions.INTERVAL - ISOLATION_LEVEL_SERIALIZABLE = psycopg2.extensions.ISOLATION_LEVEL_SERIALIZABLE - - def __init__(self): - self.__dict__ = self.__shared_state - self.types = {} - - def connect(self, connection_string): - self.last_connection_string = connection_string - return ConnectionStub() - - def new_type(self, values, name, converter): - return Stub(name=name, values=values) - - def register_type(self, type): - for typeid in type.values: - self.types[typeid] = type - - def getExtensions(self): - return self - extensions = property(getExtensions) - - -class TestPsycopg2TypeConversion(TestCase): - - def test_conv_date(self): - from psycopg2da.adapter import _conv_date - from datetime import date - def c(s): - return _conv_date(s, None) - self.assertEquals(c(''), None) - self.assertEquals(c('2001-03-02'), date(2001, 3, 2)) - - def test_conv_time(self): - from psycopg2da.adapter import _conv_time - from datetime import time - def c(s): - return _conv_time(s, None) - self.assertEquals(c(''), None) - self.assertEquals(c('23:17:57'), time(23, 17, 57)) - self.assertEquals(c('23:17:57.037'), time(23, 17, 57, 37000)) - - def test_conv_timetz(self): - from psycopg2da.adapter import _conv_timetz - from datetime import time - def c(s): - return _conv_timetz(s, None) - self.assertEquals(c(''), None) - self.assertEquals(c('12:44:01+01:00'), time(12, 44, 01, 0, TZStub(1,0))) - self.assertEquals(c('12:44:01.037-00:30'), time(12, 44, 01, 37000, TZStub(0,-30))) - - def test_conv_timestamp(self): - from psycopg2da.adapter import _conv_timestamp - from datetime import datetime - def c(s): - return _conv_timestamp(s, None) - self.assertEquals(c(''), None) - self.assertEquals(c('2001-03-02 12:44:01'), - datetime(2001, 3, 2, 12, 44, 01)) - self.assertEquals(c('2001-03-02 12:44:01.037'), - datetime(2001, 3, 2, 12, 44, 01, 37000)) - self.assertEquals(c('2001-03-02 12:44:01.000001'), - datetime(2001, 3, 2, 12, 44, 01, 1)) - - def test_conv_timestamptz(self): - from psycopg2da.adapter import _conv_timestamptz - from datetime import datetime - def c(s): - return _conv_timestamptz(s, None) - self.assertEquals(c(''), None) - - self.assertEquals(c('2001-03-02 12:44:01+01:00'), - datetime(2001, 3, 2, 12, 44, 01, 0, TZStub(1,0))) - self.assertEquals(c('2001-03-02 12:44:01.037-00:30'), - datetime(2001, 3, 2, 12, 44, 01, 37000, TZStub(0,-30))) - self.assertEquals(c('2001-03-02 12:44:01.000001+12:00'), - datetime(2001, 3, 2, 12, 44, 01, 1, TZStub(12,0))) - self.assertEquals(c('2001-06-25 12:14:00-07'), - datetime(2001, 6, 25, 12, 14, 00, 0, TZStub(-7,0))) - - def test_conv_interval(self): - from psycopg2da.adapter import _conv_interval - from datetime import timedelta - def c(s): - return _conv_interval(s, None) - - self.assertEquals(c(''), None) - self.assertEquals(c('01:00'), timedelta(hours=1)) - self.assertEquals(c('00:15'), timedelta(minutes=15)) - self.assertEquals(c('00:00:47'), timedelta(seconds=47)) - self.assertEquals(c('00:00:00.037'), timedelta(microseconds=37000)) - self.assertEquals(c('00:00:00.111111'), timedelta(microseconds=111111)) - self.assertEquals(c('1 day'), timedelta(days=1)) - self.assertEquals(c('2 days'), timedelta(days=2)) - self.assertEquals(c('374 days'), timedelta(days=374)) - self.assertEquals(c('2 days 03:20:15.123456'), - timedelta(days=2, hours=3, minutes=20, - seconds=15, microseconds=123456)) - # XXX There's a problem with years and months. Currently timedelta - # cannot represent them accurately - self.assertEquals(c('1 month'), '1 month') - self.assertEquals(c('2 months'), '2 months') - self.assertEquals(c('1 mon'), '1 mon') - self.assertEquals(c('2 mons'), '2 mons') - self.assertEquals(c('1 year'), '1 year') - self.assertEquals(c('3 years'), '3 years') - # Later we might be able to use - ## self.assertEquals(c('1 month'), timedelta(months=1)) - ## self.assertEquals(c('2 months'), timedelta(months=2)) - ## self.assertEquals(c('1 year'), timedelta(years=1)) - ## self.assertEquals(c('3 years'), timedelta(years=3)) - - self.assertRaises(ValueError, c, '2 day') - self.assertRaises(ValueError, c, '2days') - self.assertRaises(ValueError, c, '123') - - def test_conv_string(self): - from psycopg2da.adapter import _get_string_conv - _conv_string = _get_string_conv("utf-8") - def c(s): - return _conv_string(s, None) - self.assertEquals(c(None), None) - self.assertEquals(c(''), u'') - self.assertEquals(c('c'), u'c') - self.assertEquals(c('\xc3\x82\xc2\xa2'), u'\xc2\xa2') - self.assertEquals(c('c\xc3\x82\xc2\xa2'), u'c\xc2\xa2') - -class TestPsycopg2Adapter(TestCase): - - def setUp(self): - import psycopg2da.adapter as adapter - self.real_psycopg2 = adapter.psycopg2 - adapter.psycopg2 = self.psycopg2_stub = Psycopg2Stub() - - def tearDown(self): - import psycopg2da.adapter as adapter - adapter.psycopg2 = self.real_psycopg2 - - def test_connection_factory(self): - from psycopg2da.adapter import Psycopg2Adapter - a = Psycopg2Adapter('dbi://username:password@hostname:port/dbname;junk=ignored') - c = a._connection_factory() - args = self.psycopg2_stub.last_connection_string.split() - args.sort() - self.assertEquals(args, ['dbname=dbname', 'host=hostname', - 'password=password', 'port=port', - 'user=username']) - - def test_registerTypes(self): - import psycopg2da.adapter as adapter - from psycopg2da.adapter import Psycopg2Adapter - a = Psycopg2Adapter('dbi://') - a.registerTypes() - for typename in ('DATE', 'TIME', 'TIMETZ', 'TIMESTAMP', - 'TIMESTAMPTZ', 'INTERVAL'): - typeid = getattr(adapter, '%s_OID' % typename) - result = self.psycopg2_stub.types.get(typeid, None) - if not result: - # comparing None with psycopg2.type object segfaults - self.fail("did not register %s (%d): got None, not Z%s" - % (typename, typeid, typename)) - else: - result_name = getattr(result, 'name', 'None') - self.assertEquals(result, getattr(adapter, typename), - "did not register %s (%d): got %s, not Z%s" - % (typename, typeid, result_name, typename)) - - -class TestISODateTime(TestCase): - - # Test if date/time parsing functions accept a sensible subset of ISO-8601 - # compliant date/time strings. - # - # Resources: - # http://www.cl.cam.ac.uk/~mgk25/iso-time.html - # http://www.mcs.vuw.ac.nz/technical/software/SGML/doc/iso8601/ISO8601.html - # http://www.w3.org/TR/NOTE-datetime - # http://www.ietf.org/rfc/rfc3339.txt - - basic_dates = (('20020304', (2002, 03, 04)), - ('20000229', (2000, 02, 29))) - - extended_dates = (('2002-03-04', (2002, 03, 04)), - ('2000-02-29', (2000, 02, 29))) - - basic_times = (('12', (12, 0, 0)), - ('1234', (12, 34, 0)), - ('123417', (12, 34, 17)), - ('123417.5', (12, 34, 17.5)), - ('123417,5', (12, 34, 17.5))) - - extended_times = (('12', (12, 0, 0)), - ('12:34', (12, 34, 0)), - ('12:34:17', (12, 34, 17)), - ('12:34:17.5', (12, 34, 17.5)), - ('12:34:17,5', (12, 34, 17.5))) - - basic_tzs = (('Z', 0), - ('+02', 2*60), - ('+1130', 11*60+30), - ('-05', -5*60), - ('-0030', -30)) - - extended_tzs = (('Z', 0), - ('+02', 2*60), - ('+11:30', 11*60+30), - ('-05', -5*60), - ('-00:30', -30)) - - time_separators = (' ', 'T') - - bad_dates = ('', 'foo', 'XXXXXXXX', 'XXXX-XX-XX', '2001-2-29', - '1990/13/14') - - bad_times = ('', 'foo', 'XXXXXX', '12:34,5', '12:34:56,') - - bad_timetzs = ('12+12 34', '15:45 +1234', '18:00-12:34:56', '18:00+123', '18:00Q') - - bad_datetimes = ('', 'foo', '2002-03-0412:33') - - bad_datetimetzs = ('', 'foo', '2002-03-04T12:33 +1200') - - exception_type = ValueError - - # We need the following funcions: - # parse_date -> (year, month, day) - # parse_time -> (hour, minute, second) - # parse_timetz -> (hour, minute, second, tzoffset) - # parse_datetime -> (year, month, day, hour, minute, second) - # parse_datetimetz -> (year, month, day, hour, minute, second, tzoffset) - # second can be a float, all other values are ints - # tzoffset is offset in minutes east of UTC - - def setUp(self): - from psycopg2da.adapter import parse_date, parse_time, \ - parse_timetz, parse_datetime, parse_datetimetz - self.parse_date = parse_date - self.parse_time = parse_time - self.parse_timetz = parse_timetz - self.parse_datetime = parse_datetime - self.parse_datetimetz = parse_datetimetz - - def test_basic_date(self): - for s, d in self.basic_dates: - self.assertEqual(self.parse_date(s), d) - - def test_extended_date(self): - for s, d in self.extended_dates: - self.assertEqual(self.parse_date(s), d) - - def test_bad_date(self): - for s in self.bad_dates: - self.assertRaises(self.exception_type, self.parse_date, s) - - def test_basic_time(self): - for s, t in self.basic_times: - self.assertEqual(self.parse_time(s), t) - - def test_extended_time(self): - for s, t in self.extended_times: - self.assertEqual(self.parse_time(s), t) - - def test_bad_time(self): - for s in self.bad_times: - self.assertRaises(self.exception_type, self.parse_time, s) - - def test_basic_timetz(self): - for s, t in self.basic_times: - for tz, off in self.basic_tzs: - self.assertEqual(self.parse_timetz(s+tz), t + (off,)) - - def test_extended_timetz(self): - for s, t in self.extended_times: - for tz, off in self.extended_tzs: - self.assertEqual(self.parse_timetz(s+tz), t + (off,)) - - def test_bad_timetzs(self): - for s in self.bad_timetzs: - self.assertRaises(self.exception_type, self.parse_timetz, s) - - def test_basic_datetime(self): - for ds, d in self.basic_dates: - for ts, t in self.basic_times: - for sep in self.time_separators: - self.assertEqual(self.parse_datetime(ds+sep+ts), d + t) - - def test_extended_datetime(self): - for ds, d in self.extended_dates: - for ts, t in self.extended_times: - for sep in self.time_separators: - self.assertEqual(self.parse_datetime(ds+sep+ts), d + t) - - def test_bad_datetimes(self): - for s in self.bad_datetimes: - self.assertRaises(self.exception_type, self.parse_datetime, s) - - def test_basic_datetimetz(self): - for ds, d in self.basic_dates: - for ts, t in self.basic_times: - for tz, off in self.basic_tzs: - for sep in self.time_separators: - self.assertEqual(self.parse_datetimetz(ds+sep+ts+tz), - d + t + (off,)) - - def test_extended_datetimetz(self): - for ds, d in self.extended_dates: - for ts, t in self.extended_times: - for tz, off in self.extended_tzs: - for sep in self.time_separators: - self.assertEqual(self.parse_datetimetz(ds+sep+ts+tz), - d + t + (off,)) - - def test_bad_datetimetzs(self): - for s in self.bad_datetimetzs: - self.assertRaises(self.exception_type, self.parse_datetimetz, s) - - -def test_suite(): - return TestSuite(( - makeSuite(TestPsycopg2TypeConversion), - makeSuite(TestPsycopg2Adapter), - makeSuite(TestISODateTime), - )) - -if __name__=='__main__': - main(defaultTest='test_suite') diff --git a/scripts/make_errorcodes.py b/scripts/make_errorcodes.py index 01fbf90c..fa37fd8e 100755 --- a/scripts/make_errorcodes.py +++ b/scripts/make_errorcodes.py @@ -30,7 +30,8 @@ def main(): filename = sys.argv[1] file_start = read_base_file(filename) - classes, errors = fetch_errors(['8.1', '8.2', '8.3', '8.4', '9.0', '9.1']) + classes, errors = fetch_errors( + ['8.1', '8.2', '8.3', '8.4', '9.0', '9.1', '9.2']) f = open(filename, "w") for line in file_start: diff --git a/setup.cfg b/setup.cfg index db4e771f..637ed418 100644 --- a/setup.cfg +++ b/setup.cfg @@ -7,6 +7,12 @@ define=PSYCOPG_EXTENSIONS,PSYCOPG_NEW_BOOLEAN,HAVE_PQFREEMEM # PSYCOPG_DEBUG can be added to enable verbose debug information # PSYCOPG_NEW_BOOLEAN to format booleans as true/false vs 't'/'f' +# "pg_config" is required to locate PostgreSQL headers and libraries needed to +# build psycopg2. If pg_config is not in the path or is installed under a +# different name uncomment the following option and set it to the pg_config +# full path. +#pg_config= + # Set to 1 to use Python datetime objects for default date/time representation. use_pydatetime=1 @@ -22,26 +28,5 @@ have_ssl=0 # Statically link against the postgresql client library. #static_libpq=1 -# "pg_config" is the preferred method to locate PostgreSQL headers and -# libraries needed to build psycopg2. If pg_config is not in the path or -# is installed under a different name uncomment the following option and -# set it to the pg_config full path. -#pg_config= - -# If "pg_config" is not available, "include_dirs" can be used to locate -# postgresql headers and libraries. Some extra checks on sys.platform will -# still be done in setup.py. -# The next line is the default as used on psycopg author Debian laptop: -#include_dirs=/usr/include/postgresql:/usr/include/postgresql/server - -# Uncomment next line on Mandrake 10.x (and comment previous ones): -#include_dirs=/usr/include/pgsql/8.0:/usr/include/pgsql/8.0/server - -# Uncomment next line on SUSE 9.3 (and comment previous ones): -#include_dirs=/usr/include/pgsql:/usr/include/pgsql/server - -# If postgresql is installed somewhere weird (i.e., not in your runtime library -# path like /usr/lib), just add the right path in "library_dirs" and any extra -# libraries required to link in "libraries". -#library_dirs= +# Add here eventual extra libreries required to link the module. #libraries= diff --git a/setup.py b/setup.py index 9ff77363..9ef421c1 100644 --- a/setup.py +++ b/setup.py @@ -55,14 +55,18 @@ from distutils.ccompiler import get_default_compiler from distutils.util import get_platform try: - from distutils.command.build_py import build_py_2to3 as build_py + from distutils.command.build_py import build_py_2to3 except ImportError: from distutils.command.build_py import build_py else: + class build_py(build_py_2to3): + # workaround subclass for ticket #153 + pass + # Configure distutils to run our custom 2to3 fixers as well from lib2to3.refactor import get_fixers_from_package - build_py.fixer_names = get_fixers_from_package('lib2to3.fixes') - build_py.fixer_names.append('fix_b') + build_py.fixer_names = get_fixers_from_package('lib2to3.fixes') \ + + [ 'fix_b' ] sys.path.insert(0, 'scripts') try: @@ -73,7 +77,7 @@ except ImportError: # Take a look at http://www.python.org/dev/peps/pep-0386/ # for a consistent versioning pattern. -PSYCOPG_VERSION = '2.4.6' +PSYCOPG_VERSION = '2.5' version_flags = ['dt', 'dec'] @@ -347,14 +351,15 @@ class psycopg_build_ext(build_ext): self.libraries.append('ssl') self.libraries.append('crypto') - def finalize_linux2(self): + def finalize_linux(self): """Finalize build system configuration on GNU/Linux platform.""" # tell piro that GCC is fine and dandy, but not so MS compilers for extension in self.extensions: extension.extra_compile_args.append( '-Wdeclaration-after-statement') - finalize_linux3 = finalize_linux2 + finalize_linux2 = finalize_linux + finalize_linux3 = finalize_linux def finalize_options(self): """Complete the build system configuation.""" @@ -423,6 +428,7 @@ sources = [ 'connection_int.c', 'connection_type.c', 'cursor_int.c', 'cursor_type.c', + 'diagnostics_type.c', 'error_type.c', 'lobject_int.c', 'lobject_type.c', 'notify_type.c', 'xid_type.c', @@ -435,8 +441,8 @@ sources = [ depends = [ # headers - 'config.h', 'pgtypes.h', 'psycopg.h', 'python.h', - 'connection.h', 'cursor.h', 'green.h', 'lobject.h', + 'config.h', 'pgtypes.h', 'psycopg.h', 'python.h', 'connection.h', + 'cursor.h', 'diagnostics.h', 'error.h', 'green.h', 'lobject.h', 'notify.h', 'pqpath.h', 'xid.h', 'adapter_asis.h', 'adapter_binary.h', 'adapter_datetime.h', diff --git a/tests/__init__.py b/tests/__init__.py index df8e8cd1..3e677d85 100755 --- a/tests/__init__.py +++ b/tests/__init__.py @@ -45,6 +45,11 @@ import test_transaction import test_types_basic import test_types_extras +if sys.version_info[:2] >= (2, 5): + import test_with +else: + test_with = None + def test_suite(): # If connection to test db fails, bail out early. import psycopg2 @@ -76,6 +81,8 @@ def test_suite(): suite.addTest(test_transaction.test_suite()) suite.addTest(test_types_basic.test_suite()) suite.addTest(test_types_extras.test_suite()) + if test_with: + suite.addTest(test_with.test_suite()) return suite if __name__ == '__main__': diff --git a/tests/test_async.py b/tests/test_async.py index 08113c4f..09604b5c 100755 --- a/tests/test_async.py +++ b/tests/test_async.py @@ -32,7 +32,7 @@ import time import select import StringIO -from testconfig import dsn +from testutils import ConnectingTestCase class PollableStub(object): """A 'pollable' wrapper allowing analysis of the `poll()` calls.""" @@ -49,11 +49,13 @@ class PollableStub(object): return rv -class AsyncTests(unittest.TestCase): +class AsyncTests(ConnectingTestCase): def setUp(self): - self.sync_conn = psycopg2.connect(dsn) - self.conn = psycopg2.connect(dsn, async=True) + ConnectingTestCase.setUp(self) + + self.sync_conn = self.conn + self.conn = self.connect(async=True) self.wait(self.conn) @@ -64,10 +66,6 @@ class AsyncTests(unittest.TestCase): )''') self.wait(curs) - def tearDown(self): - self.sync_conn.close() - self.conn.close() - def wait(self, cur_or_conn): pollable = cur_or_conn if not hasattr(pollable, 'poll'): @@ -328,7 +326,7 @@ class AsyncTests(unittest.TestCase): def __init__(self, dsn, async=0): psycopg2.extensions.connection.__init__(self, dsn, async=async) - conn = psycopg2.connect(dsn, connection_factory=MyConn, async=True) + conn = self.connect(connection_factory=MyConn, async=True) self.assert_(isinstance(conn, MyConn)) self.assert_(conn.async) conn.close() @@ -429,6 +427,9 @@ class AsyncTests(unittest.TestCase): def test_notices(self): del self.conn.notices[:] cur = self.conn.cursor() + if self.conn.server_version >= 90300: + cur.execute("set client_min_messages=debug1") + self.wait(cur) cur.execute("create temp table chatty (id serial primary key);") self.wait(cur) self.assertEqual("CREATE TABLE", cur.statusmessage) diff --git a/tests/test_bug_gc.py b/tests/test_bug_gc.py index 798b7e77..1551dc47 100755 --- a/tests/test_bug_gc.py +++ b/tests/test_bug_gc.py @@ -24,21 +24,12 @@ import psycopg2 import psycopg2.extensions -import time import unittest import gc -from testconfig import dsn - -from testutils import skip_if_no_uuid - -class StolenReferenceTestCase(unittest.TestCase): - def setUp(self): - self.conn = psycopg2.connect(dsn) - - def tearDown(self): - self.conn.close() +from testutils import ConnectingTestCase, skip_if_no_uuid +class StolenReferenceTestCase(ConnectingTestCase): @skip_if_no_uuid def test_stolen_reference_bug(self): def fish(val, cur): diff --git a/tests/test_cancel.py b/tests/test_cancel.py index 6d58ddcf..0ffa742a 100755 --- a/tests/test_cancel.py +++ b/tests/test_cancel.py @@ -23,7 +23,6 @@ # FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public # License for more details. -import time import threading import psycopg2 @@ -31,12 +30,13 @@ import psycopg2.extensions from psycopg2 import extras from testconfig import dsn -from testutils import unittest, skip_before_postgres +from testutils import unittest, ConnectingTestCase, skip_before_postgres -class CancelTests(unittest.TestCase): +class CancelTests(ConnectingTestCase): def setUp(self): - self.conn = psycopg2.connect(dsn) + ConnectingTestCase.setUp(self) + cur = self.conn.cursor() cur.execute(''' CREATE TEMPORARY TABLE table1 ( @@ -44,9 +44,6 @@ class CancelTests(unittest.TestCase): )''') self.conn.commit() - def tearDown(self): - self.conn.close() - def test_empty_cancel(self): self.conn.cancel() diff --git a/tests/test_connection.py b/tests/test_connection.py index e4b7d83c..26ad9329 100755 --- a/tests/test_connection.py +++ b/tests/test_connection.py @@ -25,23 +25,19 @@ import os import time import threading -from testutils import unittest, decorate_all_tests -from testutils import skip_before_postgres, skip_after_postgres from operator import attrgetter import psycopg2 +import psycopg2.errorcodes import psycopg2.extensions + +from testutils import unittest, decorate_all_tests, skip_if_no_superuser +from testutils import skip_before_postgres, skip_after_postgres +from testutils import ConnectingTestCase, skip_if_tpc_disabled from testconfig import dsn, dbname -class ConnectionTests(unittest.TestCase): - - def setUp(self): - self.conn = psycopg2.connect(dsn) - - def tearDown(self): - if not self.conn.closed: - self.conn.close() +class ConnectionTests(ConnectingTestCase): def test_closed_attribute(self): conn = self.conn self.assertEqual(conn.closed, False) @@ -66,6 +62,27 @@ class ConnectionTests(unittest.TestCase): conn.close() self.assertEqual(curs.closed, True) + @skip_before_postgres(8, 4) + @skip_if_no_superuser + def test_cleanup_on_badconn_close(self): + # ticket #148 + conn = self.conn + cur = conn.cursor() + try: + cur.execute("select pg_terminate_backend(pg_backend_pid())") + except psycopg2.OperationalError, e: + if e.pgcode != psycopg2.errorcodes.ADMIN_SHUTDOWN: + raise + except psycopg2.DatabaseError, e: + # curiously when disconnected in green mode we get a DatabaseError + # without pgcode. + if e.pgcode is not None: + raise + + self.assertEqual(conn.closed, 2) + conn.close() + self.assertEqual(conn.closed, 1) + def test_reset(self): conn = self.conn # switch isolation level, then reset @@ -79,6 +96,8 @@ class ConnectionTests(unittest.TestCase): def test_notices(self): conn = self.conn cur = conn.cursor() + if self.conn.server_version >= 90300: + cur.execute("set client_min_messages=debug1") cur.execute("create temp table chatty (id serial primary key);") self.assertEqual("CREATE TABLE", cur.statusmessage) self.assert_(conn.notices) @@ -86,6 +105,8 @@ class ConnectionTests(unittest.TestCase): def test_notices_consistent_order(self): conn = self.conn cur = conn.cursor() + if self.conn.server_version >= 90300: + cur.execute("set client_min_messages=debug1") cur.execute("create temp table table1 (id serial); create temp table table2 (id serial);") cur.execute("create temp table table3 (id serial); create temp table table4 (id serial);") self.assertEqual(4, len(conn.notices)) @@ -97,6 +118,8 @@ class ConnectionTests(unittest.TestCase): def test_notices_limited(self): conn = self.conn cur = conn.cursor() + if self.conn.server_version >= 90300: + cur.execute("set client_min_messages=debug1") for i in range(0, 100, 10): sql = " ".join(["create temp table table%d (id serial);" % j for j in range(i, i+10)]) cur.execute(sql) @@ -125,7 +148,7 @@ class ConnectionTests(unittest.TestCase): @skip_before_postgres(8, 2) def test_concurrent_execution(self): def slave(): - cnn = psycopg2.connect(dsn) + cnn = self.connect() cur = cnn.cursor() cur.execute("select pg_sleep(4)") cur.close() @@ -155,7 +178,7 @@ class ConnectionTests(unittest.TestCase): oldenc = os.environ.get('PGCLIENTENCODING') os.environ['PGCLIENTENCODING'] = 'utf-8' # malformed spelling try: - self.conn = psycopg2.connect(dsn) + self.conn = self.connect() finally: if oldenc is not None: os.environ['PGCLIENTENCODING'] = oldenc @@ -201,11 +224,37 @@ class ConnectionTests(unittest.TestCase): self.assert_(not notices, "%d notices raised" % len(notices)) + def test_connect_cursor_factory(self): + import psycopg2.extras + conn = self.connect(cursor_factory=psycopg2.extras.DictCursor) + cur = conn.cursor() + cur.execute("select 1 as a") + self.assertEqual(cur.fetchone()['a'], 1) -class IsolationLevelsTestCase(unittest.TestCase): + def test_cursor_factory(self): + self.assertEqual(self.conn.cursor_factory, None) + cur = self.conn.cursor() + cur.execute("select 1 as a") + self.assertRaises(TypeError, (lambda r: r['a']), cur.fetchone()) + + self.conn.cursor_factory = psycopg2.extras.DictCursor + self.assertEqual(self.conn.cursor_factory, psycopg2.extras.DictCursor) + cur = self.conn.cursor() + cur.execute("select 1 as a") + self.assertEqual(cur.fetchone()['a'], 1) + + self.conn.cursor_factory = None + self.assertEqual(self.conn.cursor_factory, None) + cur = self.conn.cursor() + cur.execute("select 1 as a") + self.assertRaises(TypeError, (lambda r: r['a']), cur.fetchone()) + + +class IsolationLevelsTestCase(ConnectingTestCase): def setUp(self): - self._conns = [] + ConnectingTestCase.setUp(self) + conn = self.connect() cur = conn.cursor() try: @@ -216,17 +265,6 @@ class IsolationLevelsTestCase(unittest.TestCase): conn.commit() conn.close() - def tearDown(self): - # close the connections used in the test - for conn in self._conns: - if not conn.closed: - conn.close() - - def connect(self): - conn = psycopg2.connect(dsn) - self._conns.append(conn) - return conn - def test_isolation_level(self): conn = self.connect() self.assertEqual( @@ -392,20 +430,16 @@ class IsolationLevelsTestCase(unittest.TestCase): cnn.set_isolation_level, 1) -class ConnectionTwoPhaseTests(unittest.TestCase): +class ConnectionTwoPhaseTests(ConnectingTestCase): def setUp(self): - self._conns = [] + ConnectingTestCase.setUp(self) self.make_test_table() self.clear_test_xacts() def tearDown(self): self.clear_test_xacts() - - # close the connections used in the test - for conn in self._conns: - if not conn.closed: - conn.close() + ConnectingTestCase.tearDown(self) def clear_test_xacts(self): """Rollback all the prepared transaction in the testing db.""" @@ -458,11 +492,6 @@ class ConnectionTwoPhaseTests(unittest.TestCase): cnn.close() return rv - def connect(self, **kwargs): - conn = psycopg2.connect(dsn, **kwargs) - self._conns.append(conn) - return conn - def test_tpc_commit(self): cnn = self.connect() xid = cnn.xid(1, "gtrid", "bqual") @@ -774,18 +803,10 @@ class ConnectionTwoPhaseTests(unittest.TestCase): self.assertEqual(None, xid.bqual) -from testutils import skip_if_tpc_disabled decorate_all_tests(ConnectionTwoPhaseTests, skip_if_tpc_disabled) -class TransactionControlTests(unittest.TestCase): - def setUp(self): - self.conn = psycopg2.connect(dsn) - - def tearDown(self): - if not self.conn.closed: - self.conn.close() - +class TransactionControlTests(ConnectingTestCase): def test_closed(self): self.conn.close() self.assertRaises(psycopg2.InterfaceError, @@ -927,14 +948,7 @@ class TransactionControlTests(unittest.TestCase): self.conn.set_session, readonly=True, deferrable=True) -class AutocommitTests(unittest.TestCase): - def setUp(self): - self.conn = psycopg2.connect(dsn) - - def tearDown(self): - if not self.conn.closed: - self.conn.close() - +class AutocommitTests(ConnectingTestCase): def test_closed(self): self.conn.close() self.assertRaises(psycopg2.InterfaceError, diff --git a/tests/test_copy.py b/tests/test_copy.py index 4ed253e9..fd6c14a1 100755 --- a/tests/test_copy.py +++ b/tests/test_copy.py @@ -22,26 +22,16 @@ # FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public # License for more details. -import os import sys import string -from testutils import unittest, decorate_all_tests, skip_if_no_iobase +from testutils import unittest, ConnectingTestCase, decorate_all_tests +from testutils import skip_if_no_iobase from cStringIO import StringIO from itertools import cycle, izip import psycopg2 import psycopg2.extensions -from testconfig import dsn, green - -def skip_if_green(f): - def skip_if_green_(self): - if green: - return self.skipTest("copy in async mode currently not supported") - else: - return f(self) - - return skip_if_green_ - +from testutils import skip_copy_if_green if sys.version_info[0] < 3: _base = object @@ -68,10 +58,10 @@ class MinimalWrite(_base): return self.f.write(data) -class CopyTests(unittest.TestCase): +class CopyTests(ConnectingTestCase): def setUp(self): - self.conn = psycopg2.connect(dsn) + ConnectingTestCase.setUp(self) self._create_temp_table() def _create_temp_table(self): @@ -82,9 +72,6 @@ class CopyTests(unittest.TestCase): data text )''') - def tearDown(self): - self.conn.close() - def test_copy_from(self): curs = self.conn.cursor() try: @@ -272,7 +259,7 @@ class CopyTests(unittest.TestCase): self.assertEqual(curs.fetchone()[0], 2) -decorate_all_tests(CopyTests, skip_if_green) +decorate_all_tests(CopyTests, skip_copy_if_green) def test_suite(): diff --git a/tests/test_cursor.py b/tests/test_cursor.py index b1c5c5c3..893f3970 100755 --- a/tests/test_cursor.py +++ b/tests/test_cursor.py @@ -26,16 +26,10 @@ import time import psycopg2 import psycopg2.extensions from psycopg2.extensions import b -from testconfig import dsn -from testutils import unittest, skip_before_postgres, skip_if_no_namedtuple +from testutils import unittest, ConnectingTestCase, skip_before_postgres +from testutils import skip_if_no_namedtuple -class CursorTests(unittest.TestCase): - - def setUp(self): - self.conn = psycopg2.connect(dsn) - - def tearDown(self): - self.conn.close() +class CursorTests(ConnectingTestCase): def test_close_idempotent(self): cur = self.conn.cursor() @@ -213,6 +207,71 @@ class CursorTests(unittest.TestCase): curs.execute("drop table withhold") self.conn.commit() + def test_scrollable(self): + self.assertRaises(psycopg2.ProgrammingError, self.conn.cursor, + scrollable=True) + + curs = self.conn.cursor() + curs.execute("create table scrollable (data int)") + curs.executemany("insert into scrollable values (%s)", + [ (i,) for i in range(100) ]) + curs.close() + + for t in range(2): + if not t: + curs = self.conn.cursor("S") + self.assertEqual(curs.scrollable, None); + curs.scrollable = True + else: + curs = self.conn.cursor("S", scrollable=True) + + self.assertEqual(curs.scrollable, True); + curs.itersize = 10 + + # complex enough to make postgres cursors declare without + # scroll/no scroll to fail + curs.execute(""" + select x.data + from scrollable x + join scrollable y on x.data = y.data + order by y.data""") + for i, (n,) in enumerate(curs): + self.assertEqual(i, n) + + curs.scroll(-1) + for i in range(99, -1, -1): + curs.scroll(-1) + self.assertEqual(i, curs.fetchone()[0]) + curs.scroll(-1) + + curs.close() + + def test_not_scrollable(self): + self.assertRaises(psycopg2.ProgrammingError, self.conn.cursor, + scrollable=False) + + curs = self.conn.cursor() + curs.execute("create table scrollable (data int)") + curs.executemany("insert into scrollable values (%s)", + [ (i,) for i in range(100) ]) + curs.close() + + curs = self.conn.cursor("S") # default scrollability + curs.execute("select * from scrollable") + self.assertEqual(curs.scrollable, None) + curs.scroll(2) + try: + curs.scroll(-1) + except psycopg2.OperationalError: + return self.skipTest("can't evaluate non-scrollable cursor") + curs.close() + + curs = self.conn.cursor("S", scrollable=False) + self.assertEqual(curs.scrollable, False) + curs.execute("select * from scrollable") + curs.scroll(2) + self.assertRaises(psycopg2.OperationalError, curs.scroll, -1) + @skip_before_postgres(8, 2) def test_iter_named_cursor_efficient(self): curs = self.conn.cursor('tmp') diff --git a/tests/test_dates.py b/tests/test_dates.py index 0d18f663..cc183c57 100755 --- a/tests/test_dates.py +++ b/tests/test_dates.py @@ -23,10 +23,9 @@ # License for more details. import math -import unittest import psycopg2 from psycopg2.tz import FixedOffsetTimezone, ZERO -from testconfig import dsn +from testutils import unittest, ConnectingTestCase class CommonDatetimeTestsMixin: @@ -93,20 +92,17 @@ class CommonDatetimeTestsMixin: self.assertEqual(value, None) -class DatetimeTests(unittest.TestCase, CommonDatetimeTestsMixin): +class DatetimeTests(ConnectingTestCase, CommonDatetimeTestsMixin): """Tests for the datetime based date handling in psycopg2.""" def setUp(self): - self.conn = psycopg2.connect(dsn) + ConnectingTestCase.setUp(self) self.curs = self.conn.cursor() self.DATE = psycopg2.extensions.PYDATE self.TIME = psycopg2.extensions.PYTIME self.DATETIME = psycopg2.extensions.PYDATETIME self.INTERVAL = psycopg2.extensions.PYINTERVAL - def tearDown(self): - self.conn.close() - def test_parse_bc_date(self): # datetime does not support BC dates self.assertRaises(ValueError, self.DATE, '00042-01-01 BC', self.curs) @@ -311,11 +307,11 @@ if not hasattr(psycopg2.extensions, 'PYDATETIME'): del DatetimeTests -class mxDateTimeTests(unittest.TestCase, CommonDatetimeTestsMixin): +class mxDateTimeTests(ConnectingTestCase, CommonDatetimeTestsMixin): """Tests for the mx.DateTime based date handling in psycopg2.""" def setUp(self): - self.conn = psycopg2.connect(dsn) + ConnectingTestCase.setUp(self) self.curs = self.conn.cursor() self.DATE = psycopg2._psycopg.MXDATE self.TIME = psycopg2._psycopg.MXTIME @@ -557,6 +553,7 @@ class FixedOffsetTimezoneTests(unittest.TestCase): self.assertEqual(tz11, tz21) self.assertEqual(tz12, tz22) + def test_suite(): return unittest.TestLoader().loadTestsFromName(__name__) diff --git a/tests/test_extras_dictcursor.py b/tests/test_extras_dictcursor.py index 4846deae..f2feffff 100755 --- a/tests/test_extras_dictcursor.py +++ b/tests/test_extras_dictcursor.py @@ -18,26 +18,23 @@ import time from datetime import timedelta import psycopg2 import psycopg2.extras -from testutils import unittest, skip_before_postgres, skip_if_no_namedtuple -from testconfig import dsn +from testutils import unittest, ConnectingTestCase, skip_before_postgres +from testutils import skip_if_no_namedtuple -class ExtrasDictCursorTests(unittest.TestCase): +class ExtrasDictCursorTests(ConnectingTestCase): """Test if DictCursor extension class works.""" def setUp(self): - self.conn = psycopg2.connect(dsn) + ConnectingTestCase.setUp(self) curs = self.conn.cursor() curs.execute("CREATE TEMPORARY TABLE ExtrasDictCursorTests (foo text)") curs.execute("INSERT INTO ExtrasDictCursorTests VALUES ('bar')") self.conn.commit() - def tearDown(self): - self.conn.close() - def testDictConnCursorArgs(self): self.conn.close() - self.conn = psycopg2.connect(dsn, connection_factory=psycopg2.extras.DictConnection) + self.conn = self.connect(connection_factory=psycopg2.extras.DictConnection) cur = self.conn.cursor() self.assert_(isinstance(cur, psycopg2.extras.DictCursor)) self.assertEqual(cur.name, None) @@ -232,18 +229,17 @@ class ExtrasDictCursorTests(unittest.TestCase): self.assertEqual(r._column_mapping, r1._column_mapping) -class NamedTupleCursorTest(unittest.TestCase): +class NamedTupleCursorTest(ConnectingTestCase): def setUp(self): + ConnectingTestCase.setUp(self) from psycopg2.extras import NamedTupleConnection try: from collections import namedtuple except ImportError: - self.conn = None return - self.conn = psycopg2.connect(dsn, - connection_factory=NamedTupleConnection) + self.conn = self.connect(connection_factory=NamedTupleConnection) curs = self.conn.cursor() curs.execute("CREATE TEMPORARY TABLE nttest (i int, s text)") curs.execute("INSERT INTO nttest VALUES (1, 'foo')") @@ -251,10 +247,6 @@ class NamedTupleCursorTest(unittest.TestCase): curs.execute("INSERT INTO nttest VALUES (3, 'baz')") self.conn.commit() - def tearDown(self): - if self.conn is not None: - self.conn.close() - @skip_if_no_namedtuple def test_cursor_args(self): cur = self.conn.cursor('foo', cursor_factory=psycopg2.extras.DictCursor) @@ -359,9 +351,7 @@ class NamedTupleCursorTest(unittest.TestCase): # an import error somewhere from psycopg2.extras import NamedTupleConnection try: - if self.conn is not None: - self.conn.close() - self.conn = psycopg2.connect(dsn, + self.conn = self.connect( connection_factory=NamedTupleConnection) curs = self.conn.cursor() curs.execute("select 1") @@ -371,8 +361,7 @@ class NamedTupleCursorTest(unittest.TestCase): else: self.fail("expecting ImportError") else: - # skip the test - pass + return self.skipTest("namedtuple available") @skip_if_no_namedtuple def test_record_updated(self): diff --git a/tests/test_green.py b/tests/test_green.py index e0cd57de..506b38fe 100755 --- a/tests/test_green.py +++ b/tests/test_green.py @@ -26,7 +26,8 @@ import unittest import psycopg2 import psycopg2.extensions import psycopg2.extras -from testconfig import dsn + +from testutils import ConnectingTestCase class ConnectionStub(object): """A `connection` wrapper allowing analysis of the `poll()` calls.""" @@ -42,14 +43,14 @@ class ConnectionStub(object): self.polls.append(rv) return rv -class GreenTests(unittest.TestCase): +class GreenTestCase(ConnectingTestCase): def setUp(self): self._cb = psycopg2.extensions.get_wait_callback() psycopg2.extensions.set_wait_callback(psycopg2.extras.wait_select) - self.conn = psycopg2.connect(dsn) + ConnectingTestCase.setUp(self) def tearDown(self): - self.conn.close() + ConnectingTestCase.tearDown(self) psycopg2.extensions.set_wait_callback(self._cb) def set_stub_wait_callback(self, conn): diff --git a/tests/test_lobject.py b/tests/test_lobject.py index 9c1c44f2..83e9e73c 100755 --- a/tests/test_lobject.py +++ b/tests/test_lobject.py @@ -25,14 +25,16 @@ import os import shutil import tempfile +from functools import wraps import psycopg2 import psycopg2.extensions from psycopg2.extensions import b -from testconfig import dsn, green from testutils import unittest, decorate_all_tests, skip_if_tpc_disabled +from testutils import ConnectingTestCase, skip_if_green def skip_if_no_lo(f): + @wraps(f) def skip_if_no_lo_(self): if self.conn.server_version < 80100: return self.skipTest("large objects only supported from PG 8.1") @@ -41,20 +43,12 @@ def skip_if_no_lo(f): return skip_if_no_lo_ -def skip_if_green(f): - def skip_if_green_(self): - if green: - return self.skipTest("libpq doesn't support LO in async mode") - else: - return f(self) - - return skip_if_green_ +skip_lo_if_green = skip_if_green("libpq doesn't support LO in async mode") -class LargeObjectMixin(object): - # doesn't derive from TestCase to avoid repeating tests twice. +class LargeObjectTestCase(ConnectingTestCase): def setUp(self): - self.conn = self.connect() + ConnectingTestCase.setUp(self) self.lo_oid = None self.tmpdir = None @@ -73,13 +67,11 @@ class LargeObjectMixin(object): pass else: lo.unlink() - self.conn.close() - def connect(self): - return psycopg2.connect(dsn) + ConnectingTestCase.tearDown(self) -class LargeObjectTests(LargeObjectMixin, unittest.TestCase): +class LargeObjectTests(LargeObjectTestCase): def test_create(self): lo = self.conn.lobject() self.assertNotEqual(lo, None) @@ -378,11 +370,11 @@ class LargeObjectTests(LargeObjectMixin, unittest.TestCase): self.conn.tpc_commit() -decorate_all_tests(LargeObjectTests, skip_if_no_lo) -decorate_all_tests(LargeObjectTests, skip_if_green) +decorate_all_tests(LargeObjectTests, skip_if_no_lo, skip_lo_if_green) def skip_if_no_truncate(f): + @wraps(f) def skip_if_no_truncate_(self): if self.conn.server_version < 80300: return self.skipTest( @@ -395,10 +387,12 @@ def skip_if_no_truncate(f): return f(self) -class LargeObjectTruncateTests(LargeObjectMixin, unittest.TestCase): + return skip_if_no_truncate_ + +class LargeObjectTruncateTests(LargeObjectTestCase): def test_truncate(self): lo = self.conn.lobject() - lo.write(b("some data")) + lo.write("some data") lo.close() lo = self.conn.lobject(lo.oid, "w") @@ -407,17 +401,17 @@ class LargeObjectTruncateTests(LargeObjectMixin, unittest.TestCase): # seek position unchanged self.assertEqual(lo.tell(), 0) # data truncated - self.assertEqual(lo.read(), b("some")) + self.assertEqual(lo.read(), "some") lo.truncate(6) lo.seek(0) # large object extended with zeroes - self.assertEqual(lo.read(), b("some\x00\x00")) + self.assertEqual(lo.read(), "some\x00\x00") lo.truncate() lo.seek(0) # large object empty - self.assertEqual(lo.read(), b("")) + self.assertEqual(lo.read(), "") def test_truncate_after_close(self): lo = self.conn.lobject() @@ -431,9 +425,8 @@ class LargeObjectTruncateTests(LargeObjectMixin, unittest.TestCase): self.assertRaises(psycopg2.ProgrammingError, lo.truncate) -decorate_all_tests(LargeObjectTruncateTests, skip_if_no_lo) -decorate_all_tests(LargeObjectTruncateTests, skip_if_green) -decorate_all_tests(LargeObjectTruncateTests, skip_if_no_truncate) +decorate_all_tests(LargeObjectTruncateTests, + skip_if_no_lo, skip_lo_if_green, skip_if_no_truncate) def test_suite(): diff --git a/tests/test_module.py b/tests/test_module.py index 4083c368..e6d1a3d5 100755 --- a/tests/test_module.py +++ b/tests/test_module.py @@ -22,8 +22,8 @@ # FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public # License for more details. -from testutils import unittest, skip_before_python -from testconfig import dsn +from testutils import unittest, skip_before_python, skip_before_postgres +from testutils import ConnectingTestCase, skip_copy_if_green import psycopg2 @@ -136,13 +136,7 @@ class ConnectTestCase(unittest.TestCase): psycopg2.connect, 'dbname=foo', no_such_param='meh') -class ExceptionsTestCase(unittest.TestCase): - def setUp(self): - self.conn = psycopg2.connect(dsn) - - def tearDown(self): - self.conn.close() - +class ExceptionsTestCase(ConnectingTestCase): def test_attributes(self): cur = self.conn.cursor() try: @@ -154,6 +148,122 @@ class ExceptionsTestCase(unittest.TestCase): self.assert_(e.pgerror) self.assert_(e.cursor is cur) + def test_diagnostics_attributes(self): + cur = self.conn.cursor() + try: + cur.execute("select * from nonexist") + except psycopg2.Error, exc: + e = exc + + diag = e.diag + self.assert_(isinstance(diag, psycopg2.extensions.Diagnostics)) + for attr in [ + 'column_name', 'constraint_name', 'context', 'datatype_name', + 'internal_position', 'internal_query', 'message_detail', + 'message_hint', 'message_primary', 'schema_name', 'severity', + 'source_file', 'source_function', 'source_line', 'sqlstate', + 'statement_position', 'table_name', ]: + v = getattr(diag, attr) + if v is not None: + self.assert_(isinstance(v, str)) + + def test_diagnostics_values(self): + cur = self.conn.cursor() + try: + cur.execute("select * from nonexist") + except psycopg2.Error, exc: + e = exc + + self.assertEqual(e.diag.sqlstate, '42P01') + self.assertEqual(e.diag.severity, 'ERROR') + + def test_diagnostics_life(self): + import gc + from weakref import ref + + def tmp(): + cur = self.conn.cursor() + try: + cur.execute("select * from nonexist") + except psycopg2.Error, exc: + return cur, exc + + cur, e = tmp() + diag = e.diag + w = ref(cur) + + del e, cur + gc.collect() + assert(w() is not None) + + self.assertEqual(diag.sqlstate, '42P01') + + del diag + gc.collect() + assert(w() is None) + + @skip_copy_if_green + def test_diagnostics_copy(self): + from StringIO import StringIO + f = StringIO() + cur = self.conn.cursor() + try: + cur.copy_to(f, 'nonexist') + except psycopg2.Error, exc: + diag = exc.diag + + self.assertEqual(diag.sqlstate, '42P01') + + def test_diagnostics_independent(self): + cur = self.conn.cursor() + try: + cur.execute("l'acqua e' poca e 'a papera nun galleggia") + except Exception, exc: + diag1 = exc.diag + + self.conn.rollback() + + try: + cur.execute("select level from water where ducks > 1") + except psycopg2.Error, exc: + diag2 = exc.diag + + self.assertEqual(diag1.sqlstate, '42601') + self.assertEqual(diag2.sqlstate, '42P01') + + def test_diagnostics_from_commit(self): + cur = self.conn.cursor() + cur.execute(""" + create temp table test_deferred ( + data int primary key, + ref int references test_deferred (data) + deferrable initially deferred) + """) + cur.execute("insert into test_deferred values (1,2)") + try: + self.conn.commit() + except psycopg2.Error, exc: + e = exc + self.assertEqual(e.diag.sqlstate, '23503') + + @skip_before_postgres(9, 3) + def test_9_3_diagnostics(self): + cur = self.conn.cursor() + cur.execute(""" + create temp table test_exc ( + data int constraint chk_eq1 check (data = 1) + )""") + try: + cur.execute("insert into test_exc values(2)") + except psycopg2.Error, exc: + e = exc + self.assertEqual(e.pgcode, '23514') + self.assertEqual(e.diag.schema_name[:7], "pg_temp") + self.assertEqual(e.diag.table_name, "test_exc") + self.assertEqual(e.diag.column_name, None) + self.assertEqual(e.diag.constraint_name, "chk_eq1") + self.assertEqual(e.diag.datatype_name, None) + @skip_before_python(2, 5) def test_pickle(self): import pickle diff --git a/tests/test_notify.py b/tests/test_notify.py index 5b1b112a..d048241d 100755 --- a/tests/test_notify.py +++ b/tests/test_notify.py @@ -26,23 +26,16 @@ from testutils import unittest import psycopg2 from psycopg2 import extensions +from testutils import ConnectingTestCase, script_to_py3 from testconfig import dsn -from testutils import script_to_py3 import sys import time import select -import signal from subprocess import Popen, PIPE -class NotifiesTests(unittest.TestCase): - - def setUp(self): - self.conn = psycopg2.connect(dsn) - - def tearDown(self): - self.conn.close() +class NotifiesTests(ConnectingTestCase): def autocommit(self, conn): """Set a connection in autocommit mode.""" diff --git a/tests/test_quote.py b/tests/test_quote.py index 4b44a86a..e7b3c316 100755 --- a/tests/test_quote.py +++ b/tests/test_quote.py @@ -23,14 +23,13 @@ # License for more details. import sys -from testutils import unittest -from testconfig import dsn +from testutils import unittest, ConnectingTestCase import psycopg2 import psycopg2.extensions from psycopg2.extensions import b -class QuotingTestCase(unittest.TestCase): +class QuotingTestCase(ConnectingTestCase): r"""Checks the correct quoting of strings and binary objects. Since ver. 8.1, PostgreSQL is moving towards SQL standard conforming @@ -48,12 +47,6 @@ class QuotingTestCase(unittest.TestCase): http://www.postgresql.org/docs/current/static/sql-syntax-lexical.html#SQL-SYNTAX-STRINGS http://www.postgresql.org/docs/current/static/runtime-config-compatible.html """ - def setUp(self): - self.conn = psycopg2.connect(dsn) - - def tearDown(self): - self.conn.close() - def test_string(self): data = """some data with \t chars to escape into, 'quotes' and \\ a backslash too. @@ -162,6 +155,16 @@ class QuotingTestCase(unittest.TestCase): self.assert_(not self.conn.notices) +class TestQuotedString(ConnectingTestCase): + def test_encoding(self): + q = psycopg2.extensions.QuotedString('hi') + self.assertEqual(q.encoding, 'latin1') + + self.conn.set_client_encoding('utf_8') + q.prepare(self.conn) + self.assertEqual(q.encoding, 'utf_8') + + def test_suite(): return unittest.TestLoader().loadTestsFromName(__name__) diff --git a/tests/test_transaction.py b/tests/test_transaction.py index 90e159a6..724d0d80 100755 --- a/tests/test_transaction.py +++ b/tests/test_transaction.py @@ -23,17 +23,16 @@ # License for more details. import threading -from testutils import unittest, skip_before_postgres +from testutils import unittest, ConnectingTestCase, skip_before_postgres import psycopg2 from psycopg2.extensions import ( ISOLATION_LEVEL_SERIALIZABLE, STATUS_BEGIN, STATUS_READY) -from testconfig import dsn -class TransactionTests(unittest.TestCase): +class TransactionTests(ConnectingTestCase): def setUp(self): - self.conn = psycopg2.connect(dsn) + ConnectingTestCase.setUp(self) self.conn.set_isolation_level(ISOLATION_LEVEL_SERIALIZABLE) curs = self.conn.cursor() curs.execute(''' @@ -51,9 +50,6 @@ class TransactionTests(unittest.TestCase): curs.execute('INSERT INTO table2 VALUES (1, 1)') self.conn.commit() - def tearDown(self): - self.conn.close() - def test_rollback(self): # Test that rollback undoes changes curs = self.conn.cursor() @@ -93,16 +89,17 @@ class TransactionTests(unittest.TestCase): self.assertEqual(curs.fetchone()[0], 1) -class DeadlockSerializationTests(unittest.TestCase): +class DeadlockSerializationTests(ConnectingTestCase): """Test deadlock and serialization failure errors.""" def connect(self): - conn = psycopg2.connect(dsn) + conn = ConnectingTestCase.connect(self) conn.set_isolation_level(ISOLATION_LEVEL_SERIALIZABLE) return conn def setUp(self): - self.conn = self.connect() + ConnectingTestCase.setUp(self) + curs = self.conn.cursor() # Drop table if it already exists try: @@ -130,7 +127,8 @@ class DeadlockSerializationTests(unittest.TestCase): curs.execute("DROP TABLE table1") curs.execute("DROP TABLE table2") self.conn.commit() - self.conn.close() + + ConnectingTestCase.tearDown(self) def test_deadlock(self): self.thread1_error = self.thread2_error = None @@ -226,16 +224,13 @@ class DeadlockSerializationTests(unittest.TestCase): error, psycopg2.extensions.TransactionRollbackError)) -class QueryCancellationTests(unittest.TestCase): +class QueryCancellationTests(ConnectingTestCase): """Tests for query cancellation.""" def setUp(self): - self.conn = psycopg2.connect(dsn) + ConnectingTestCase.setUp(self) self.conn.set_isolation_level(ISOLATION_LEVEL_SERIALIZABLE) - def tearDown(self): - self.conn.close() - @skip_before_postgres(8, 2) def test_statement_timeout(self): curs = self.conn.cursor() diff --git a/tests/test_types_basic.py b/tests/test_types_basic.py index e442fc62..6c4cc970 100755 --- a/tests/test_types_basic.py +++ b/tests/test_types_basic.py @@ -22,28 +22,20 @@ # FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public # License for more details. -try: - import decimal -except: - pass +import decimal + import sys +from functools import wraps import testutils -from testutils import unittest, decorate_all_tests -from testconfig import dsn +from testutils import unittest, ConnectingTestCase, decorate_all_tests import psycopg2 from psycopg2.extensions import b -class TypesBasicTests(unittest.TestCase): +class TypesBasicTests(ConnectingTestCase): """Test that all type conversions are working.""" - def setUp(self): - self.conn = psycopg2.connect(dsn) - - def tearDown(self): - self.conn.close() - def execute(self, *args): curs = self.conn.cursor() curs.execute(*args) @@ -64,10 +56,6 @@ class TypesBasicTests(unittest.TestCase): self.failUnless(s == 1971, "wrong integer quoting: " + str(s)) s = self.execute("SELECT %s AS foo", (1971L,)) self.failUnless(s == 1971L, "wrong integer quoting: " + str(s)) - if sys.version_info[0:2] < (2, 4): - s = self.execute("SELECT %s AS foo", (19.10,)) - self.failUnless(abs(s - 19.10) < 0.001, - "wrong float quoting: " + str(s)) def testBoolean(self): x = self.execute("SELECT %s as foo", (False,)) @@ -76,21 +64,18 @@ class TypesBasicTests(unittest.TestCase): self.assert_(x is True) def testDecimal(self): - if sys.version_info[0:2] >= (2, 4): - s = self.execute("SELECT %s AS foo", (decimal.Decimal("19.10"),)) - self.failUnless(s - decimal.Decimal("19.10") == 0, - "wrong decimal quoting: " + str(s)) - s = self.execute("SELECT %s AS foo", (decimal.Decimal("NaN"),)) - self.failUnless(str(s) == "NaN", "wrong decimal quoting: " + str(s)) - self.failUnless(type(s) == decimal.Decimal, "wrong decimal conversion: " + repr(s)) - s = self.execute("SELECT %s AS foo", (decimal.Decimal("infinity"),)) - self.failUnless(str(s) == "NaN", "wrong decimal quoting: " + str(s)) - self.failUnless(type(s) == decimal.Decimal, "wrong decimal conversion: " + repr(s)) - s = self.execute("SELECT %s AS foo", (decimal.Decimal("-infinity"),)) - self.failUnless(str(s) == "NaN", "wrong decimal quoting: " + str(s)) - self.failUnless(type(s) == decimal.Decimal, "wrong decimal conversion: " + repr(s)) - else: - return self.skipTest("decimal not available") + s = self.execute("SELECT %s AS foo", (decimal.Decimal("19.10"),)) + self.failUnless(s - decimal.Decimal("19.10") == 0, + "wrong decimal quoting: " + str(s)) + s = self.execute("SELECT %s AS foo", (decimal.Decimal("NaN"),)) + self.failUnless(str(s) == "NaN", "wrong decimal quoting: " + str(s)) + self.failUnless(type(s) == decimal.Decimal, "wrong decimal conversion: " + repr(s)) + s = self.execute("SELECT %s AS foo", (decimal.Decimal("infinity"),)) + self.failUnless(str(s) == "NaN", "wrong decimal quoting: " + str(s)) + self.failUnless(type(s) == decimal.Decimal, "wrong decimal conversion: " + repr(s)) + s = self.execute("SELECT %s AS foo", (decimal.Decimal("-infinity"),)) + self.failUnless(str(s) == "NaN", "wrong decimal quoting: " + str(s)) + self.failUnless(type(s) == decimal.Decimal, "wrong decimal conversion: " + repr(s)) def testFloatNan(self): try: @@ -126,7 +111,7 @@ class TypesBasicTests(unittest.TestCase): s = bytes(range(256)) b = psycopg2.Binary(s) buf = self.execute("SELECT %s::bytea AS foo", (b,)) - self.assertEqual(s, buf) + self.assertEqual(s, buf.tobytes()) def testBinaryNone(self): b = psycopg2.Binary(None) @@ -154,7 +139,7 @@ class TypesBasicTests(unittest.TestCase): s = bytes(range(256)) buf = self.execute("SELECT %s::bytea AS foo", (psycopg2.Binary(s),)) buf2 = self.execute("SELECT %s::bytea AS foo", (buf,)) - self.assertEqual(s, buf2) + self.assertEqual(s, buf2.tobytes()) def testArray(self): s = self.execute("SELECT %s AS foo", ([[1,2],[3,4]],)) @@ -461,6 +446,7 @@ class ByteaParserTest(unittest.TestCase): self.assertEqual(rv, tgt) def skip_if_cant_cast(f): + @wraps(f) def skip_if_cant_cast_(self, *args, **kwargs): if self._cast is None: return self.skipTest("can't test bytea parser: %s - %s" diff --git a/tests/test_types_extras.py b/tests/test_types_extras.py index c4483f8f..98fbbadf 100755 --- a/tests/test_types_extras.py +++ b/tests/test_types_extras.py @@ -14,22 +14,19 @@ # FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public # License for more details. -try: - import decimal -except: - pass import re import sys -from datetime import date +from decimal import Decimal +from datetime import date, datetime +from functools import wraps from testutils import unittest, skip_if_no_uuid, skip_before_postgres +from testutils import ConnectingTestCase, decorate_all_tests import psycopg2 import psycopg2.extras from psycopg2.extensions import b -from testconfig import dsn - def filter_scs(conn, s): if conn.get_parameter_status("standard_conforming_strings") == 'off': @@ -37,15 +34,9 @@ def filter_scs(conn, s): else: return s.replace(b("E'"), b("'")) -class TypesExtrasTests(unittest.TestCase): +class TypesExtrasTests(ConnectingTestCase): """Test that all type conversions are working.""" - def setUp(self): - self.conn = psycopg2.connect(dsn) - - def tearDown(self): - self.conn.close() - def execute(self, *args): curs = self.conn.cursor() curs.execute(*args) @@ -126,6 +117,7 @@ class TypesExtrasTests(unittest.TestCase): def skip_if_no_hstore(f): + @wraps(f) def skip_if_no_hstore_(self): from psycopg2.extras import HstoreAdapter oids = HstoreAdapter.get_oids(self.conn) @@ -135,13 +127,7 @@ def skip_if_no_hstore(f): return skip_if_no_hstore_ -class HstoreTestCase(unittest.TestCase): - def setUp(self): - self.conn = psycopg2.connect(dsn) - - def tearDown(self): - self.conn.close() - +class HstoreTestCase(ConnectingTestCase): def test_adapt_8(self): if self.conn.server_version >= 90000: return self.skipTest("skipping dict adaptation with PG pre-9 syntax") @@ -276,7 +262,7 @@ class HstoreTestCase(unittest.TestCase): oids = HstoreAdapter.get_oids(self.conn) try: register_hstore(self.conn, globally=True) - conn2 = psycopg2.connect(dsn) + conn2 = self.connect() try: cur2 = self.conn.cursor() cur2.execute("select 'a => b'::hstore") @@ -429,7 +415,7 @@ class HstoreTestCase(unittest.TestCase): from psycopg2.extras import RealDictConnection from psycopg2.extras import register_hstore - conn = psycopg2.connect(dsn, connection_factory=RealDictConnection) + conn = self.connect(connection_factory=RealDictConnection) try: register_hstore(conn) curs = conn.cursor() @@ -438,7 +424,7 @@ class HstoreTestCase(unittest.TestCase): finally: conn.close() - conn = psycopg2.connect(dsn, connection_factory=RealDictConnection) + conn = self.connect(connection_factory=RealDictConnection) try: curs = conn.cursor() register_hstore(curs) @@ -449,6 +435,7 @@ class HstoreTestCase(unittest.TestCase): def skip_if_no_composite(f): + @wraps(f) def skip_if_no_composite_(self): if self.conn.server_version < 80000: return self.skipTest( @@ -457,16 +444,9 @@ def skip_if_no_composite(f): return f(self) - skip_if_no_composite_.__name__ = f.__name__ return skip_if_no_composite_ -class AdaptTypeTestCase(unittest.TestCase): - def setUp(self): - self.conn = psycopg2.connect(dsn) - - def tearDown(self): - self.conn.close() - +class AdaptTypeTestCase(ConnectingTestCase): @skip_if_no_composite def test_none_in_record(self): curs = self.conn.cursor() @@ -533,6 +513,7 @@ class AdaptTypeTestCase(unittest.TestCase): t = psycopg2.extras.register_composite("type_isd", self.conn) self.assertEqual(t.name, 'type_isd') + self.assertEqual(t.schema, 'public') self.assertEqual(t.oid, oid) self.assert_(issubclass(t.type, tuple)) self.assertEqual(t.attnames, ['anint', 'astring', 'adate']) @@ -620,8 +601,8 @@ class AdaptTypeTestCase(unittest.TestCase): def test_register_on_connection(self): self._create_type("type_ii", [("a", "integer"), ("b", "integer")]) - conn1 = psycopg2.connect(dsn) - conn2 = psycopg2.connect(dsn) + conn1 = self.connect() + conn2 = self.connect() try: psycopg2.extras.register_composite("type_ii", conn1) curs1 = conn1.cursor() @@ -638,8 +619,8 @@ class AdaptTypeTestCase(unittest.TestCase): def test_register_globally(self): self._create_type("type_ii", [("a", "integer"), ("b", "integer")]) - conn1 = psycopg2.connect(dsn) - conn2 = psycopg2.connect(dsn) + conn1 = self.connect() + conn2 = self.connect() try: t = psycopg2.extras.register_composite("type_ii", conn1, globally=True) try: @@ -676,6 +657,7 @@ class AdaptTypeTestCase(unittest.TestCase): [("a", "integer"), ("b", "integer")]) t = psycopg2.extras.register_composite( "typens.typens_ii", self.conn) + self.assertEqual(t.schema, 'typens') curs.execute("select (4,8)::typens.typens_ii") self.assertEqual(curs.fetchone()[0], (4,8)) @@ -757,13 +739,13 @@ class AdaptTypeTestCase(unittest.TestCase): self.assertEqual(r[0], (2, 'test2')) self.assertEqual(r[1], [(3, 'testc', 2), (4, 'testd', 2)]) - @skip_if_no_hstore + @skip_if_no_composite def test_non_dbapi_connection(self): from psycopg2.extras import RealDictConnection from psycopg2.extras import register_composite self._create_type("type_ii", [("a", "integer"), ("b", "integer")]) - conn = psycopg2.connect(dsn, connection_factory=RealDictConnection) + conn = self.connect(connection_factory=RealDictConnection) try: register_composite('type_ii', conn) curs = conn.cursor() @@ -772,7 +754,7 @@ class AdaptTypeTestCase(unittest.TestCase): finally: conn.close() - conn = psycopg2.connect(dsn, connection_factory=RealDictConnection) + conn = self.connect(connection_factory=RealDictConnection) try: curs = conn.cursor() register_composite('type_ii', conn) @@ -781,6 +763,31 @@ class AdaptTypeTestCase(unittest.TestCase): finally: conn.close() + @skip_if_no_composite + def test_subclass(self): + oid = self._create_type("type_isd", + [('anint', 'integer'), ('astring', 'text'), ('adate', 'date')]) + + from psycopg2.extras import register_composite, CompositeCaster + + class DictComposite(CompositeCaster): + def make(self, values): + return dict(zip(self.attnames, values)) + + t = register_composite('type_isd', self.conn, factory=DictComposite) + + self.assertEqual(t.name, 'type_isd') + self.assertEqual(t.oid, oid) + + curs = self.conn.cursor() + r = (10, 'hello', date(2011,1,2)) + curs.execute("select %s::type_isd;", (r,)) + v = curs.fetchone()[0] + self.assert_(isinstance(v, dict)) + self.assertEqual(v['anint'], 10) + self.assertEqual(v['astring'], "hello") + self.assertEqual(v['adate'], date(2011,1,2)) + def _create_type(self, name, fields): curs = self.conn.cursor() try: @@ -805,6 +812,735 @@ class AdaptTypeTestCase(unittest.TestCase): return oid +def skip_if_json_module(f): + """Skip a test if a Python json module *is* available""" + @wraps(f) + def skip_if_json_module_(self): + if psycopg2.extras.json is not None: + return self.skipTest("json module is available") + + return f(self) + + return skip_if_json_module_ + +def skip_if_no_json_module(f): + """Skip a test if no Python json module is available""" + @wraps(f) + def skip_if_no_json_module_(self): + if psycopg2.extras.json is None: + return self.skipTest("json module not available") + + return f(self) + + return skip_if_no_json_module_ + +def skip_if_no_json_type(f): + """Skip a test if PostgreSQL json type is not available""" + @wraps(f) + def skip_if_no_json_type_(self): + curs = self.conn.cursor() + curs.execute("select oid from pg_type where typname = 'json'") + if not curs.fetchone(): + return self.skipTest("json not available in test database") + + return f(self) + + return skip_if_no_json_type_ + +class JsonTestCase(ConnectingTestCase): + @skip_if_json_module + def test_module_not_available(self): + from psycopg2.extras import Json + self.assertRaises(ImportError, Json(None).getquoted) + + @skip_if_json_module + def test_customizable_with_module_not_available(self): + from psycopg2.extras import Json + class MyJson(Json): + def dumps(self, obj): + assert obj is None + return "hi" + + self.assertEqual(MyJson(None).getquoted(), "'hi'") + + @skip_if_no_json_module + def test_adapt(self): + from psycopg2.extras import json, Json + + objs = [None, "te'xt", 123, 123.45, + u'\xe0\u20ac', ['a', 100], {'a': 100} ] + + curs = self.conn.cursor() + for obj in enumerate(objs): + self.assertEqual(curs.mogrify("%s", (Json(obj),)), + psycopg2.extensions.QuotedString(json.dumps(obj)).getquoted()) + + @skip_if_no_json_module + def test_adapt_dumps(self): + from psycopg2.extras import json, Json + + class DecimalEncoder(json.JSONEncoder): + def default(self, obj): + if isinstance(obj, Decimal): + return float(obj) + return json.JSONEncoder.default(self, obj) + + curs = self.conn.cursor() + obj = Decimal('123.45') + dumps = lambda obj: json.dumps(obj, cls=DecimalEncoder) + self.assertEqual(curs.mogrify("%s", (Json(obj, dumps=dumps),)), + b("'123.45'")) + + @skip_if_no_json_module + def test_adapt_subclass(self): + from psycopg2.extras import json, Json + + class DecimalEncoder(json.JSONEncoder): + def default(self, obj): + if isinstance(obj, Decimal): + return float(obj) + return json.JSONEncoder.default(self, obj) + + class MyJson(Json): + def dumps(self, obj): + return json.dumps(obj, cls=DecimalEncoder) + + curs = self.conn.cursor() + obj = Decimal('123.45') + self.assertEqual(curs.mogrify("%s", (MyJson(obj),)), + b("'123.45'")) + + @skip_if_no_json_module + def test_register_on_dict(self): + from psycopg2.extras import Json + psycopg2.extensions.register_adapter(dict, Json) + + try: + curs = self.conn.cursor() + obj = {'a': 123} + self.assertEqual(curs.mogrify("%s", (obj,)), + b("""'{"a": 123}'""")) + finally: + del psycopg2.extensions.adapters[dict, psycopg2.extensions.ISQLQuote] + + + def test_type_not_available(self): + curs = self.conn.cursor() + curs.execute("select oid from pg_type where typname = 'json'") + if curs.fetchone(): + return self.skipTest("json available in test database") + + self.assertRaises(psycopg2.ProgrammingError, + psycopg2.extras.register_json, self.conn) + + @skip_if_no_json_module + @skip_before_postgres(9, 2) + def test_default_cast(self): + curs = self.conn.cursor() + + curs.execute("""select '{"a": 100.0, "b": null}'::json""") + self.assertEqual(curs.fetchone()[0], {'a': 100.0, 'b': None}) + + curs.execute("""select array['{"a": 100.0, "b": null}']::json[]""") + self.assertEqual(curs.fetchone()[0], [{'a': 100.0, 'b': None}]) + + @skip_if_no_json_module + @skip_if_no_json_type + def test_register_on_connection(self): + psycopg2.extras.register_json(self.conn) + curs = self.conn.cursor() + curs.execute("""select '{"a": 100.0, "b": null}'::json""") + self.assertEqual(curs.fetchone()[0], {'a': 100.0, 'b': None}) + + @skip_if_no_json_module + @skip_if_no_json_type + def test_register_on_cursor(self): + curs = self.conn.cursor() + psycopg2.extras.register_json(curs) + curs.execute("""select '{"a": 100.0, "b": null}'::json""") + self.assertEqual(curs.fetchone()[0], {'a': 100.0, 'b': None}) + + @skip_if_no_json_module + @skip_if_no_json_type + def test_register_globally(self): + old = psycopg2.extensions.string_types.get(114) + olda = psycopg2.extensions.string_types.get(199) + try: + new, newa = psycopg2.extras.register_json(self.conn, globally=True) + curs = self.conn.cursor() + curs.execute("""select '{"a": 100.0, "b": null}'::json""") + self.assertEqual(curs.fetchone()[0], {'a': 100.0, 'b': None}) + finally: + psycopg2.extensions.string_types.pop(new.values[0]) + psycopg2.extensions.string_types.pop(newa.values[0]) + if old: + psycopg2.extensions.register_type(old) + if olda: + psycopg2.extensions.register_type(olda) + + @skip_if_no_json_module + @skip_if_no_json_type + def test_loads(self): + json = psycopg2.extras.json + loads = lambda x: json.loads(x, parse_float=Decimal) + psycopg2.extras.register_json(self.conn, loads=loads) + curs = self.conn.cursor() + curs.execute("""select '{"a": 100.0, "b": null}'::json""") + data = curs.fetchone()[0] + self.assert_(isinstance(data['a'], Decimal)) + self.assertEqual(data['a'], Decimal('100.0')) + + @skip_if_no_json_module + @skip_if_no_json_type + def test_no_conn_curs(self): + from psycopg2._json import _get_json_oids + oid, array_oid = _get_json_oids(self.conn) + + old = psycopg2.extensions.string_types.get(114) + olda = psycopg2.extensions.string_types.get(199) + loads = lambda x: psycopg2.extras.json.loads(x, parse_float=Decimal) + try: + new, newa = psycopg2.extras.register_json( + loads=loads, oid=oid, array_oid=array_oid) + curs = self.conn.cursor() + curs.execute("""select '{"a": 100.0, "b": null}'::json""") + data = curs.fetchone()[0] + self.assert_(isinstance(data['a'], Decimal)) + self.assertEqual(data['a'], Decimal('100.0')) + finally: + psycopg2.extensions.string_types.pop(new.values[0]) + psycopg2.extensions.string_types.pop(newa.values[0]) + if old: + psycopg2.extensions.register_type(old) + if olda: + psycopg2.extensions.register_type(olda) + + @skip_if_no_json_module + @skip_before_postgres(9, 2) + def test_register_default(self): + curs = self.conn.cursor() + + loads = lambda x: psycopg2.extras.json.loads(x, parse_float=Decimal) + psycopg2.extras.register_default_json(curs, loads=loads) + + curs.execute("""select '{"a": 100.0, "b": null}'::json""") + data = curs.fetchone()[0] + self.assert_(isinstance(data['a'], Decimal)) + self.assertEqual(data['a'], Decimal('100.0')) + + curs.execute("""select array['{"a": 100.0, "b": null}']::json[]""") + data = curs.fetchone()[0] + self.assert_(isinstance(data[0]['a'], Decimal)) + self.assertEqual(data[0]['a'], Decimal('100.0')) + + @skip_if_no_json_module + @skip_if_no_json_type + def test_null(self): + psycopg2.extras.register_json(self.conn) + curs = self.conn.cursor() + curs.execute("""select NULL::json""") + self.assertEqual(curs.fetchone()[0], None) + curs.execute("""select NULL::json[]""") + self.assertEqual(curs.fetchone()[0], None) + + @skip_if_no_json_module + def test_no_array_oid(self): + curs = self.conn.cursor() + t1, t2 = psycopg2.extras.register_json(curs, oid=25) + self.assertEqual(t1.values[0], 25) + self.assertEqual(t2, None) + + curs.execute("""select '{"a": 100.0, "b": null}'::text""") + data = curs.fetchone()[0] + self.assertEqual(data['a'], 100) + self.assertEqual(data['b'], None) + + +class RangeTestCase(unittest.TestCase): + def test_noparam(self): + from psycopg2.extras import Range + r = Range() + + self.assert_(not r.isempty) + self.assertEqual(r.lower, None) + self.assertEqual(r.upper, None) + self.assert_(r.lower_inf) + self.assert_(r.upper_inf) + self.assert_(not r.lower_inc) + self.assert_(not r.upper_inc) + + def test_empty(self): + from psycopg2.extras import Range + r = Range(empty=True) + + self.assert_(r.isempty) + self.assertEqual(r.lower, None) + self.assertEqual(r.upper, None) + self.assert_(not r.lower_inf) + self.assert_(not r.upper_inf) + self.assert_(not r.lower_inc) + self.assert_(not r.upper_inc) + + def test_nobounds(self): + from psycopg2.extras import Range + r = Range(10, 20) + self.assertEqual(r.lower, 10) + self.assertEqual(r.upper, 20) + self.assert_(not r.isempty) + self.assert_(not r.lower_inf) + self.assert_(not r.upper_inf) + self.assert_(r.lower_inc) + self.assert_(not r.upper_inc) + + def test_bounds(self): + from psycopg2.extras import Range + for bounds, lower_inc, upper_inc in [ + ('[)', True, False), + ('(]', False, True), + ('()', False, False), + ('[]', True, True),]: + r = Range(10, 20, bounds) + self.assertEqual(r.lower, 10) + self.assertEqual(r.upper, 20) + self.assert_(not r.isempty) + self.assert_(not r.lower_inf) + self.assert_(not r.upper_inf) + self.assertEqual(r.lower_inc, lower_inc) + self.assertEqual(r.upper_inc, upper_inc) + + def test_keywords(self): + from psycopg2.extras import Range + r = Range(upper=20) + self.assertEqual(r.lower, None) + self.assertEqual(r.upper, 20) + self.assert_(not r.isempty) + self.assert_(r.lower_inf) + self.assert_(not r.upper_inf) + self.assert_(not r.lower_inc) + self.assert_(not r.upper_inc) + + r = Range(lower=10, bounds='(]') + self.assertEqual(r.lower, 10) + self.assertEqual(r.upper, None) + self.assert_(not r.isempty) + self.assert_(not r.lower_inf) + self.assert_(r.upper_inf) + self.assert_(not r.lower_inc) + self.assert_(not r.upper_inc) + + def test_bad_bounds(self): + from psycopg2.extras import Range + self.assertRaises(ValueError, Range, bounds='(') + self.assertRaises(ValueError, Range, bounds='[}') + + def test_in(self): + from psycopg2.extras import Range + r = Range(empty=True) + self.assert_(10 not in r) + + r = Range() + self.assert_(10 in r) + + r = Range(lower=10, bounds='[)') + self.assert_(9 not in r) + self.assert_(10 in r) + self.assert_(11 in r) + + r = Range(lower=10, bounds='()') + self.assert_(9 not in r) + self.assert_(10 not in r) + self.assert_(11 in r) + + r = Range(upper=20, bounds='()') + self.assert_(19 in r) + self.assert_(20 not in r) + self.assert_(21 not in r) + + r = Range(upper=20, bounds='(]') + self.assert_(19 in r) + self.assert_(20 in r) + self.assert_(21 not in r) + + r = Range(10, 20) + self.assert_(9 not in r) + self.assert_(10 in r) + self.assert_(11 in r) + self.assert_(19 in r) + self.assert_(20 not in r) + self.assert_(21 not in r) + + r = Range(10, 20, '(]') + self.assert_(9 not in r) + self.assert_(10 not in r) + self.assert_(11 in r) + self.assert_(19 in r) + self.assert_(20 in r) + self.assert_(21 not in r) + + r = Range(20, 10) + self.assert_(9 not in r) + self.assert_(10 not in r) + self.assert_(11 not in r) + self.assert_(19 not in r) + self.assert_(20 not in r) + self.assert_(21 not in r) + + def test_nonzero(self): + from psycopg2.extras import Range + self.assert_(Range()) + self.assert_(Range(10, 20)) + self.assert_(not Range(empty=True)) + + def test_eq_hash(self): + from psycopg2.extras import Range + def assert_equal(r1, r2): + self.assert_(r1 == r2) + self.assert_(hash(r1) == hash(r2)) + + assert_equal(Range(empty=True), Range(empty=True)) + assert_equal(Range(), Range()) + assert_equal(Range(10, None), Range(10, None)) + assert_equal(Range(10, 20), Range(10, 20)) + assert_equal(Range(10, 20), Range(10, 20, '[)')) + assert_equal(Range(10, 20, '[]'), Range(10, 20, '[]')) + + def assert_not_equal(r1, r2): + self.assert_(r1 != r2) + self.assert_(hash(r1) != hash(r2)) + + assert_not_equal(Range(10, 20), Range(10, 21)) + assert_not_equal(Range(10, 20), Range(11, 20)) + assert_not_equal(Range(10, 20, '[)'), Range(10, 20, '[]')) + + def test_not_ordered(self): + from psycopg2.extras import Range + self.assertRaises(TypeError, lambda: Range(empty=True) < Range(0,4)) + self.assertRaises(TypeError, lambda: Range(1,2) > Range(0,4)) + self.assertRaises(TypeError, lambda: Range(1,2) <= Range()) + self.assertRaises(TypeError, lambda: Range(1,2) >= Range()) + + +def skip_if_no_range(f): + @wraps(f) + def skip_if_no_range_(self): + if self.conn.server_version < 90200: + return self.skipTest( + "server version %s doesn't support range types" + % self.conn.server_version) + + return f(self) + + return skip_if_no_range_ + + +class RangeCasterTestCase(ConnectingTestCase): + + builtin_ranges = ('int4range', 'int8range', 'numrange', + 'daterange', 'tsrange', 'tstzrange') + + def test_cast_null(self): + cur = self.conn.cursor() + for type in self.builtin_ranges: + cur.execute("select NULL::%s" % type) + r = cur.fetchone()[0] + self.assertEqual(r, None) + + def test_cast_empty(self): + from psycopg2.extras import Range + cur = self.conn.cursor() + for type in self.builtin_ranges: + cur.execute("select 'empty'::%s" % type) + r = cur.fetchone()[0] + self.assert_(isinstance(r, Range), type) + self.assert_(r.isempty) + + def test_cast_inf(self): + from psycopg2.extras import Range + cur = self.conn.cursor() + for type in self.builtin_ranges: + cur.execute("select '(,)'::%s" % type) + r = cur.fetchone()[0] + self.assert_(isinstance(r, Range), type) + self.assert_(not r.isempty) + self.assert_(r.lower_inf) + self.assert_(r.upper_inf) + + def test_cast_numbers(self): + from psycopg2.extras import NumericRange + cur = self.conn.cursor() + for type in ('int4range', 'int8range'): + cur.execute("select '(10,20)'::%s" % type) + r = cur.fetchone()[0] + self.assert_(isinstance(r, NumericRange)) + self.assert_(not r.isempty) + self.assertEqual(r.lower, 11) + self.assertEqual(r.upper, 20) + self.assert_(not r.lower_inf) + self.assert_(not r.upper_inf) + self.assert_(r.lower_inc) + self.assert_(not r.upper_inc) + + cur.execute("select '(10.2,20.6)'::numrange") + r = cur.fetchone()[0] + self.assert_(isinstance(r, NumericRange)) + self.assert_(not r.isempty) + self.assertEqual(r.lower, Decimal('10.2')) + self.assertEqual(r.upper, Decimal('20.6')) + self.assert_(not r.lower_inf) + self.assert_(not r.upper_inf) + self.assert_(not r.lower_inc) + self.assert_(not r.upper_inc) + + def test_cast_date(self): + from psycopg2.extras import DateRange + cur = self.conn.cursor() + cur.execute("select '(2000-01-01,2012-12-31)'::daterange") + r = cur.fetchone()[0] + self.assert_(isinstance(r, DateRange)) + self.assert_(not r.isempty) + self.assertEqual(r.lower, date(2000,1,2)) + self.assertEqual(r.upper, date(2012,12,31)) + self.assert_(not r.lower_inf) + self.assert_(not r.upper_inf) + self.assert_(r.lower_inc) + self.assert_(not r.upper_inc) + + def test_cast_timestamp(self): + from psycopg2.extras import DateTimeRange + cur = self.conn.cursor() + ts1 = datetime(2000,1,1) + ts2 = datetime(2000,12,31,23,59,59,999) + cur.execute("select tsrange(%s, %s, '()')", (ts1, ts2)) + r = cur.fetchone()[0] + self.assert_(isinstance(r, DateTimeRange)) + self.assert_(not r.isempty) + self.assertEqual(r.lower, ts1) + self.assertEqual(r.upper, ts2) + self.assert_(not r.lower_inf) + self.assert_(not r.upper_inf) + self.assert_(not r.lower_inc) + self.assert_(not r.upper_inc) + + def test_cast_timestamptz(self): + from psycopg2.extras import DateTimeTZRange + from psycopg2.tz import FixedOffsetTimezone + cur = self.conn.cursor() + ts1 = datetime(2000,1,1, tzinfo=FixedOffsetTimezone(600)) + ts2 = datetime(2000,12,31,23,59,59,999, tzinfo=FixedOffsetTimezone(600)) + cur.execute("select tstzrange(%s, %s, '[]')", (ts1, ts2)) + r = cur.fetchone()[0] + self.assert_(isinstance(r, DateTimeTZRange)) + self.assert_(not r.isempty) + self.assertEqual(r.lower, ts1) + self.assertEqual(r.upper, ts2) + self.assert_(not r.lower_inf) + self.assert_(not r.upper_inf) + self.assert_(r.lower_inc) + self.assert_(r.upper_inc) + + def test_adapt_number_range(self): + from psycopg2.extras import NumericRange + cur = self.conn.cursor() + + r = NumericRange(empty=True) + cur.execute("select %s::int4range", (r,)) + r1 = cur.fetchone()[0] + self.assert_(isinstance(r1, NumericRange)) + self.assert_(r1.isempty) + + r = NumericRange(10, 20) + cur.execute("select %s::int8range", (r,)) + r1 = cur.fetchone()[0] + self.assert_(isinstance(r1, NumericRange)) + self.assertEqual(r1.lower, 10) + self.assertEqual(r1.upper, 20) + self.assert_(r1.lower_inc) + self.assert_(not r1.upper_inc) + + r = NumericRange(Decimal('10.2'), Decimal('20.5'), '(]') + cur.execute("select %s::numrange", (r,)) + r1 = cur.fetchone()[0] + self.assert_(isinstance(r1, NumericRange)) + self.assertEqual(r1.lower, Decimal('10.2')) + self.assertEqual(r1.upper, Decimal('20.5')) + self.assert_(not r1.lower_inc) + self.assert_(r1.upper_inc) + + def test_adapt_numeric_range(self): + from psycopg2.extras import NumericRange + cur = self.conn.cursor() + + r = NumericRange(empty=True) + cur.execute("select %s::int4range", (r,)) + r1 = cur.fetchone()[0] + self.assert_(isinstance(r1, NumericRange), r1) + self.assert_(r1.isempty) + + r = NumericRange(10, 20) + cur.execute("select %s::int8range", (r,)) + r1 = cur.fetchone()[0] + self.assert_(isinstance(r1, NumericRange)) + self.assertEqual(r1.lower, 10) + self.assertEqual(r1.upper, 20) + self.assert_(r1.lower_inc) + self.assert_(not r1.upper_inc) + + r = NumericRange(Decimal('10.2'), Decimal('20.5'), '(]') + cur.execute("select %s::numrange", (r,)) + r1 = cur.fetchone()[0] + self.assert_(isinstance(r1, NumericRange)) + self.assertEqual(r1.lower, Decimal('10.2')) + self.assertEqual(r1.upper, Decimal('20.5')) + self.assert_(not r1.lower_inc) + self.assert_(r1.upper_inc) + + def test_adapt_date_range(self): + from psycopg2.extras import DateRange, DateTimeRange, DateTimeTZRange + from psycopg2.tz import FixedOffsetTimezone + cur = self.conn.cursor() + + d1 = date(2012, 01, 01) + d2 = date(2012, 12, 31) + r = DateRange(d1, d2) + cur.execute("select %s", (r,)) + r1 = cur.fetchone()[0] + self.assert_(isinstance(r1, DateRange)) + self.assertEqual(r1.lower, d1) + self.assertEqual(r1.upper, d2) + self.assert_(r1.lower_inc) + self.assert_(not r1.upper_inc) + + r = DateTimeRange(empty=True) + cur.execute("select %s", (r,)) + r1 = cur.fetchone()[0] + self.assert_(isinstance(r1, DateTimeRange)) + self.assert_(r1.isempty) + + ts1 = datetime(2000,1,1, tzinfo=FixedOffsetTimezone(600)) + ts2 = datetime(2000,12,31,23,59,59,999, tzinfo=FixedOffsetTimezone(600)) + r = DateTimeTZRange(ts1, ts2, '(]') + cur.execute("select %s", (r,)) + r1 = cur.fetchone()[0] + self.assert_(isinstance(r1, DateTimeTZRange)) + self.assertEqual(r1.lower, ts1) + self.assertEqual(r1.upper, ts2) + self.assert_(not r1.lower_inc) + self.assert_(r1.upper_inc) + + def test_register_range_adapter(self): + from psycopg2.extras import Range, register_range + cur = self.conn.cursor() + cur.execute("create type textrange as range (subtype=text)") + rc = register_range('textrange', 'TextRange', cur) + + TextRange = rc.range + self.assert_(issubclass(TextRange, Range)) + self.assertEqual(TextRange.__name__, 'TextRange') + + r = TextRange('a', 'b', '(]') + cur.execute("select %s", (r,)) + r1 = cur.fetchone()[0] + self.assertEqual(r1.lower, 'a') + self.assertEqual(r1.upper, 'b') + self.assert_(not r1.lower_inc) + self.assert_(r1.upper_inc) + + cur.execute("select %s", ([r,r,r],)) + rs = cur.fetchone()[0] + self.assertEqual(len(rs), 3) + for r1 in rs: + self.assertEqual(r1.lower, 'a') + self.assertEqual(r1.upper, 'b') + self.assert_(not r1.lower_inc) + self.assert_(r1.upper_inc) + + def test_range_escaping(self): + from psycopg2.extras import register_range + cur = self.conn.cursor() + cur.execute("create type textrange as range (subtype=text)") + rc = register_range('textrange', 'TextRange', cur) + + TextRange = rc.range + cur.execute(""" + create table rangetest ( + id integer primary key, + range textrange)""") + + bounds = [ '[)', '(]', '()', '[]' ] + ranges = [ TextRange(low, up, bounds[i % 4]) + for i, (low, up) in enumerate(zip( + [None] + map(chr, range(1, 128)), + map(chr, range(1,128)) + [None], + ))] + ranges.append(TextRange()) + ranges.append(TextRange(empty=True)) + + errs = 0 + for i, r in enumerate(ranges): + # not all the ranges make sense: + # fun fact: select ascii('#') < ascii('$'), '#' < '$' + # yelds... t, f! At least in en_GB.UTF-8 collation. + # which seems suggesting a supremacy of the pound on the dollar. + # So some of these ranges will fail to insert. Be prepared but... + try: + cur.execute(""" + savepoint x; + insert into rangetest (id, range) values (%s, %s); + """, (i, r)) + except psycopg2.DataError: + errs += 1 + cur.execute("rollback to savepoint x;") + + # ...not too many errors! in the above collate there are 17 errors: + # assume in other collates we won't find more than 30 + self.assert_(errs < 30, + "too many collate errors. Is the test working?") + + cur.execute("select id, range from rangetest order by id") + for i, r in cur: + self.assertEqual(ranges[i].lower, r.lower) + self.assertEqual(ranges[i].upper, r.upper) + self.assertEqual(ranges[i].lower_inc, r.lower_inc) + self.assertEqual(ranges[i].upper_inc, r.upper_inc) + self.assertEqual(ranges[i].lower_inf, r.lower_inf) + self.assertEqual(ranges[i].upper_inf, r.upper_inf) + + def test_range_not_found(self): + from psycopg2.extras import register_range + cur = self.conn.cursor() + self.assertRaises(psycopg2.ProgrammingError, + register_range, 'nosuchrange', 'FailRange', cur) + + def test_schema_range(self): + cur = self.conn.cursor() + cur.execute("create schema rs") + cur.execute("create type r1 as range (subtype=text)") + cur.execute("create type r2 as range (subtype=text)") + cur.execute("create type rs.r2 as range (subtype=text)") + cur.execute("create type rs.r3 as range (subtype=text)") + cur.execute("savepoint x") + + from psycopg2.extras import register_range + ra1 = register_range('r1', 'r1', cur) + ra2 = register_range('r2', 'r2', cur) + rars2 = register_range('rs.r2', 'r2', cur) + rars3 = register_range('rs.r3', 'r3', cur) + + self.assertNotEqual( + ra2.typecaster.values[0], + rars2.typecaster.values[0]) + + self.assertRaises(psycopg2.ProgrammingError, + register_range, 'r3', 'FailRange', cur) + cur.execute("rollback to savepoint x;") + + self.assertRaises(psycopg2.ProgrammingError, + register_range, 'rs.r1', 'FailRange', cur) + cur.execute("rollback to savepoint x;") + +decorate_all_tests(RangeCasterTestCase, skip_if_no_range) + + def test_suite(): return unittest.TestLoader().loadTestsFromName(__name__) diff --git a/tests/test_with.py b/tests/test_with.py new file mode 100755 index 00000000..d39016c6 --- /dev/null +++ b/tests/test_with.py @@ -0,0 +1,208 @@ +#!/usr/bin/env python + +# test_ctxman.py - unit test for connection and cursor used as context manager +# +# Copyright (C) 2012 Daniele Varrazzo +# +# psycopg2 is free software: you can redistribute it and/or modify it +# under the terms of the GNU Lesser General Public License as published +# by the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# In addition, as a special exception, the copyright holders give +# permission to link this program with the OpenSSL library (or with +# modified versions of OpenSSL that use the same license as OpenSSL), +# and distribute linked combinations including the two. +# +# You must obey the GNU Lesser General Public License in all respects for +# all of the code used other than OpenSSL. +# +# psycopg2 is distributed in the hope that it will be useful, but WITHOUT +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +# FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public +# License for more details. + + +from __future__ import with_statement + +import psycopg2 +import psycopg2.extensions as ext + +from testutils import unittest, ConnectingTestCase + +class WithTestCase(ConnectingTestCase): + def setUp(self): + ConnectingTestCase.setUp(self) + curs = self.conn.cursor() + try: + curs.execute("delete from test_with") + self.conn.commit() + except psycopg2.ProgrammingError: + # assume table doesn't exist + self.conn.rollback() + curs.execute("create table test_with (id integer primary key)") + self.conn.commit() + + +class WithConnectionTestCase(WithTestCase): + def test_with_ok(self): + with self.conn as conn: + self.assert_(self.conn is conn) + self.assertEqual(conn.status, ext.STATUS_READY) + curs = conn.cursor() + curs.execute("insert into test_with values (1)") + self.assertEqual(conn.status, ext.STATUS_BEGIN) + + self.assertEqual(self.conn.status, ext.STATUS_READY) + self.assert_(not self.conn.closed) + + curs = self.conn.cursor() + curs.execute("select * from test_with") + self.assertEqual(curs.fetchall(), [(1,)]) + + def test_with_connect_idiom(self): + with self.connect() as conn: + self.assertEqual(conn.status, ext.STATUS_READY) + curs = conn.cursor() + curs.execute("insert into test_with values (2)") + self.assertEqual(conn.status, ext.STATUS_BEGIN) + + self.assertEqual(self.conn.status, ext.STATUS_READY) + self.assert_(not self.conn.closed) + + curs = self.conn.cursor() + curs.execute("select * from test_with") + self.assertEqual(curs.fetchall(), [(2,)]) + + def test_with_error_db(self): + def f(): + with self.conn as conn: + curs = conn.cursor() + curs.execute("insert into test_with values ('a')") + + self.assertRaises(psycopg2.DataError, f) + self.assertEqual(self.conn.status, ext.STATUS_READY) + self.assert_(not self.conn.closed) + + curs = self.conn.cursor() + curs.execute("select * from test_with") + self.assertEqual(curs.fetchall(), []) + + def test_with_error_python(self): + def f(): + with self.conn as conn: + curs = conn.cursor() + curs.execute("insert into test_with values (3)") + 1/0 + + self.assertRaises(ZeroDivisionError, f) + self.assertEqual(self.conn.status, ext.STATUS_READY) + self.assert_(not self.conn.closed) + + curs = self.conn.cursor() + curs.execute("select * from test_with") + self.assertEqual(curs.fetchall(), []) + + def test_with_closed(self): + def f(): + with self.conn: + pass + + self.conn.close() + self.assertRaises(psycopg2.InterfaceError, f) + + def test_subclass_commit(self): + commits = [] + class MyConn(ext.connection): + def commit(self): + commits.append(None) + super(MyConn, self).commit() + + with self.connect(connection_factory=MyConn) as conn: + curs = conn.cursor() + curs.execute("insert into test_with values (10)") + + self.assertEqual(conn.status, ext.STATUS_READY) + self.assert_(commits) + + curs = self.conn.cursor() + curs.execute("select * from test_with") + self.assertEqual(curs.fetchall(), [(10,)]) + + def test_subclass_rollback(self): + rollbacks = [] + class MyConn(ext.connection): + def rollback(self): + rollbacks.append(None) + super(MyConn, self).rollback() + + try: + with self.connect(connection_factory=MyConn) as conn: + curs = conn.cursor() + curs.execute("insert into test_with values (11)") + 1/0 + except ZeroDivisionError: + pass + else: + self.assert_("exception not raised") + + self.assertEqual(conn.status, ext.STATUS_READY) + self.assert_(rollbacks) + + curs = conn.cursor() + curs.execute("select * from test_with") + self.assertEqual(curs.fetchall(), []) + + +class WithCursorTestCase(WithTestCase): + def test_with_ok(self): + with self.conn as conn: + with conn.cursor() as curs: + curs.execute("insert into test_with values (4)") + self.assert_(not curs.closed) + self.assertEqual(self.conn.status, ext.STATUS_BEGIN) + self.assert_(curs.closed) + + self.assertEqual(self.conn.status, ext.STATUS_READY) + self.assert_(not self.conn.closed) + + curs = self.conn.cursor() + curs.execute("select * from test_with") + self.assertEqual(curs.fetchall(), [(4,)]) + + def test_with_error(self): + try: + with self.conn as conn: + with conn.cursor() as curs: + curs.execute("insert into test_with values (5)") + 1/0 + except ZeroDivisionError: + pass + + self.assertEqual(self.conn.status, ext.STATUS_READY) + self.assert_(not self.conn.closed) + self.assert_(curs.closed) + + curs = self.conn.cursor() + curs.execute("select * from test_with") + self.assertEqual(curs.fetchall(), []) + + def test_subclass(self): + closes = [] + class MyCurs(ext.cursor): + def close(self): + closes.append(None) + super(MyCurs, self).close() + + with self.conn.cursor(cursor_factory=MyCurs) as curs: + self.assert_(isinstance(curs, MyCurs)) + + self.assert_(curs.closed) + self.assert_(closes) + + +def test_suite(): + return unittest.TestLoader().loadTestsFromName(__name__) + +if __name__ == "__main__": + unittest.main() diff --git a/tests/testutils.py b/tests/testutils.py index 26551d4e..1e2e150f 100644 --- a/tests/testutils.py +++ b/tests/testutils.py @@ -26,6 +26,8 @@ import os import sys +from functools import wraps +from testconfig import dsn try: import unittest2 @@ -43,6 +45,7 @@ else: def skipIf(cond, msg): def skipIf_(f): + @wraps(f) def skipIf__(self): if cond: warnings.warn(msg) @@ -72,15 +75,62 @@ or unittest.TestCase.assert_ is not unittest.TestCase.assertTrue: unittest.TestCase.failUnlessEqual = unittest.TestCase.assertEqual -def decorate_all_tests(cls, decorator): - """Apply *decorator* to all the tests defined in the TestCase *cls*.""" +class ConnectingTestCase(unittest.TestCase): + """A test case providing connections for tests. + + A connection for the test is always available as `self.conn`. Others can be + created with `self.connect()`. All are closed on tearDown. + + Subclasses needing to customize setUp and tearDown should remember to call + the base class implementations. + """ + def setUp(self): + self._conns = [] + + def tearDown(self): + # close the connections used in the test + for conn in self._conns: + if not conn.closed: + conn.close() + + def connect(self, **kwargs): + try: + self._conns + except AttributeError, e: + raise AttributeError( + "%s (did you remember calling ConnectingTestCase.setUp()?)" + % e) + + import psycopg2 + conn = psycopg2.connect(dsn, **kwargs) + self._conns.append(conn) + return conn + + def _get_conn(self): + if not hasattr(self, '_the_conn'): + self._the_conn = self.connect() + + return self._the_conn + + def _set_conn(self, conn): + self._the_conn = conn + + conn = property(_get_conn, _set_conn) + + +def decorate_all_tests(cls, *decorators): + """ + Apply all the *decorators* to all the tests defined in the TestCase *cls*. + """ for n in dir(cls): if n.startswith('test'): - setattr(cls, n, decorator(getattr(cls, n))) + for d in decorators: + setattr(cls, n, d(getattr(cls, n))) def skip_if_no_uuid(f): """Decorator to skip a test if uuid is not supported by Py/PG.""" + @wraps(f) def skip_if_no_uuid_(self): try: import uuid @@ -104,6 +154,7 @@ def skip_if_no_uuid(f): def skip_if_tpc_disabled(f): """Skip a test if the server has tpc support disabled.""" + @wraps(f) def skip_if_tpc_disabled_(self): from psycopg2 import ProgrammingError cnn = self.connect() @@ -123,11 +174,11 @@ def skip_if_tpc_disabled(f): "set max_prepared_transactions to > 0 to run the test") return f(self) - skip_if_tpc_disabled_.__name__ = f.__name__ return skip_if_tpc_disabled_ def skip_if_no_namedtuple(f): + @wraps(f) def skip_if_no_namedtuple_(self): try: from collections import namedtuple @@ -136,12 +187,12 @@ def skip_if_no_namedtuple(f): else: return f(self) - skip_if_no_namedtuple_.__name__ = f.__name__ return skip_if_no_namedtuple_ def skip_if_no_iobase(f): """Skip a test if io.TextIOBase is not available.""" + @wraps(f) def skip_if_no_iobase_(self): try: from io import TextIOBase @@ -157,6 +208,7 @@ def skip_before_postgres(*ver): """Skip a test on PostgreSQL before a certain version.""" ver = ver + (0,) * (3 - len(ver)) def skip_before_postgres_(f): + @wraps(f) def skip_before_postgres__(self): if self.conn.server_version < int("%d%02d%02d" % ver): return self.skipTest("skipped because PostgreSQL %s" @@ -171,6 +223,7 @@ def skip_after_postgres(*ver): """Skip a test on PostgreSQL after (including) a certain version.""" ver = ver + (0,) * (3 - len(ver)) def skip_after_postgres_(f): + @wraps(f) def skip_after_postgres__(self): if self.conn.server_version >= int("%d%02d%02d" % ver): return self.skipTest("skipped because PostgreSQL %s" @@ -184,6 +237,7 @@ def skip_after_postgres(*ver): def skip_before_python(*ver): """Skip a test on Python before a certain version.""" def skip_before_python_(f): + @wraps(f) def skip_before_python__(self): if sys.version_info[:len(ver)] < ver: return self.skipTest("skipped because Python %s" @@ -197,6 +251,7 @@ def skip_before_python(*ver): def skip_from_python(*ver): """Skip a test on Python after (including) a certain version.""" def skip_from_python_(f): + @wraps(f) def skip_from_python__(self): if sys.version_info[:len(ver)] >= ver: return self.skipTest("skipped because Python %s" @@ -207,6 +262,36 @@ def skip_from_python(*ver): return skip_from_python__ return skip_from_python_ +def skip_if_no_superuser(f): + """Skip a test if the database user running the test is not a superuser""" + @wraps(f) + def skip_if_no_superuser_(self): + from psycopg2 import ProgrammingError + try: + return f(self) + except ProgrammingError, e: + import psycopg2.errorcodes + if e.pgcode == psycopg2.errorcodes.INSUFFICIENT_PRIVILEGE: + self.skipTest("skipped because not superuser") + else: + raise + + return skip_if_no_superuser_ + +def skip_if_green(reason): + def skip_if_green_(f): + @wraps(f) + def skip_if_green__(self): + from testconfig import green + if green: + return self.skipTest(reason) + else: + return f(self) + + return skip_if_green__ + return skip_if_green_ + +skip_copy_if_green = skip_if_green("copy in async mode currently not supported") def script_to_py3(script): """Convert a script to Python3 syntax if required.""" diff --git a/tox.ini b/tox.ini new file mode 100644 index 00000000..f27f3f15 --- /dev/null +++ b/tox.ini @@ -0,0 +1,10 @@ +# Tox (http://tox.testrun.org/) is a tool for running tests +# in multiple virtualenvs. This configuration file will run the +# test suite on all supported python versions. To use it, "pip install tox" +# and then run "tox" from this directory. + +[tox] +envlist = py26, py27 + +[testenv] +commands = make check