Received: from malur.postgresql.org ([217.196.149.56]) by arkaria.postgresql.org with esmtps (TLS1.3) tls TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 (Exim 4.94.2) (envelope-from ) id 1sn2JJ-000YJK-DL for psycopg@arkaria.postgresql.org; Sat, 07 Sep 2024 20:45:34 +0000 Received: from localhost ([127.0.0.1] helo=malur.postgresql.org) by malur.postgresql.org with esmtp (Exim 4.94.2) (envelope-from ) id 1sn2JI-0040ps-Ll for psycopg@arkaria.postgresql.org; Sat, 07 Sep 2024 20:45:32 +0000 Received: from magus.postgresql.org ([2a02:c0:301:0:ffff::29]) by malur.postgresql.org with esmtps (TLS1.3) tls TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 (Exim 4.94.2) (envelope-from ) id 1sn2JI-0040pk-Dp for psycopg@lists.postgresql.org; Sat, 07 Sep 2024 20:45:32 +0000 Received: from mout.gmx.net ([212.227.17.20]) by magus.postgresql.org with esmtps (TLS1.3) tls TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 (Exim 4.94.2) (envelope-from ) id 1sn2JE-0002mI-PZ for psycopg@lists.postgresql.org; Sat, 07 Sep 2024 20:45:31 +0000 DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmx.net; s=s31663417; t=1725741926; x=1726346726; i=karsten.hilbert@gmx.net; bh=qQDDhiDNx2sBON56u08/468ME3wUZXKF9UQ/z7Awa+Y=; h=X-UI-Sender-Class:Date:From:To:Cc:Subject:Message-ID:References: MIME-Version:Content-Type:In-Reply-To:Content-Transfer-Encoding: cc:content-transfer-encoding:content-type:date:from:message-id: mime-version:reply-to:subject:to; b=gz5ATRNXaB5yKfWesB5GtkFazBkl1OQPAJ/QnR53EJb7n+w6lpnO/Paj2fo00SaM YPJc8jpH/u4IIVyCvGbZL+U6fKxaFyN+hqITaY3W9fPLSWWyRMdtDmUsjWy1zjZKW 5DNmafnOMx8V3uBQ/vFThmb3bClbkvKKIIPw2WWln7R2PLP88OErkmo2NcsxB+xY8 BL/in62mlLb4HI9r4TEClWDD5UbFjDKo6LnKesLXdB7iUHFXD6x77d3md+uMMVZmC mJvu4t7cCC50bKUrK22F2MBnCAevH5GeIROngyqx4Pq2lwxdO35XE9NaMTsFQ+rVN CWDgOMXyoh8/i37Rfw== X-UI-Sender-Class: 724b4f7f-cbec-4199-ad4e-598c01a50d3a Received: from hermes ([84.144.218.143]) by mail.gmx.net (mrgmx105 [212.227.17.168]) with ESMTPSA (Nemesis) id 1Mof57-1sG7CO30if-00eiR0; Sat, 07 Sep 2024 22:45:26 +0200 Received: from ncq by hermes with local (Exim 4.96) (envelope-from ) id 1sn2JA-00042h-1M; Sat, 07 Sep 2024 22:45:24 +0200 Date: Sat, 7 Sep 2024 22:45:24 +0200 From: Karsten Hilbert To: Adrian Klaver Cc: psycopg@lists.postgresql.org, python-list@python.org Subject: Re: psycopg2: proper positioning of .commit() within try: except: blocks Message-ID: References: <7cb50df2-9c76-477f-91c9-e149c7637104@aklaver.com> <5ee80b84-f04b-454d-ab39-45572e0751a1@aklaver.com> MIME-Version: 1.0 Content-Type: text/plain; charset=us-ascii Content-Disposition: inline In-Reply-To: <5ee80b84-f04b-454d-ab39-45572e0751a1@aklaver.com> Ma_X_il-Followup-to: d Re_X_turn-receipt-to: Karsten.Hilbert@gmx.net Di_X_sposition-Notification-To: Karsten.Hilbert@gmx.net X-Confi_X_rm-Reading-To: Karsten.Hilbert@gmx.net X-Pri_X_ority: 2 (High) Sender: X-Provags-ID: V03:K1:7wLcXaJdPT7iGY/Eu6aY639RZxOO7E85bUBmg0TBNbVYs2lw4+e 2N2I3JnPj5fKiq0MXL9lyi7HkZgLrCA7lHDPKTxT/VHgjuHyaUMx1GyvsJiYzPFayCjbSTB pdrMJq8nlu0KjrQBB8z9NNCmXX53jKhXmDoKWaz9rxTaQuAkhq1sBswg1jDX8QeuLIhkin2 dHruDEFpGNuzzrH9GQZyQ== X-Spam-Flag: NO UI-OutboundReport: notjunk:1;M01:P0:SBu5rJC1vkc=;sbEWshFAMIj+kkOEdz5X5UOO5XO MF9HhzE8/qyL3dVFRP1ovD19cGxgtpg5kj6ffqiACwWdBhmEAKEkm+zViNIV+ngK/QAPOs0LQ xBOUIu30ZnqdfDmBCPMDvg6Sp1YfR4DIKe3d7u3NdHDuEyLOc4D8jeqdCj2K3OQQuxJ2PU/QK LUBn7iZdL9bK8O9krhODxhlptQ1xYq0Uqu2DqECIHwPNtxkS38RhtpspKBebdYSykSa9dotQc 0KWsIE/o4xhSaJJ9DtRQqqqkDzO0VrzYVfaui6s7dOGf43WzwjUtesYd/D4JrLr0M+ODj6Z/g F4tF2Izn6Rdl9b5Jck06bAIiKFGfobLsS7/gu4QuUOIZNKR52t0R5fLlkfe23BoHH3922Ci2Q gVH9/yftQIZKVmDK6kavyrv3IyHlPAXS5Vnwf194M9klnAcXKQhWAI4jSWUKlMi0NC0lIBRG4 f1Z6DZUEpE10HQ4mMC/tPExeIO/t6JaFbpyh2l7EohtS1Do0B9d//nKaY3+wsFVLk4hmEmWAf oA5+9oMh/L3BmLFXVOtPYtC8Lp5hoPwzhzQ5BCOaz2ebOolQ3+kRC+em1NN6PMAdURQyL73OT HBNjsgyMmE/mmcfZD+h+neOFd6UdmmaH5uZ8uTJVeHsRMkBYkZ9mZBWMsMEwQGi1swGrb3DgL jz0W2rJl/LQ6IMvOvF/0oee6D2Aml4RyOP3en/kjOCrkCn7LXA3aTfdn3ni0b1y2LVKhPye+m roqjymoy/3axEgiHzCVSpWlw/EDTjY3cno2YsZC1Iesn7yjTq3SSgudygWa2LN7kyRSjLfT4p r4JNvP+Zc0nXrslfXogChyEg== Content-Transfer-Encoding: quoted-printable List-Id: List-Help: List-Subscribe: List-Post: List-Owner: List-Archive: Archived-At: Precedence: bulk Am Sat, Sep 07, 2024 at 01:03:34PM -0700 schrieb Adrian Klaver: > In the case you show you are doing commit() before the close() so any er= rors in the > transactions will show up then. My first thought would be to wrap the co= mmit() in a > try/except and deal with error there. Right, and this was suggested elsewhere ;) And, yeah, the actual code is much more involved :-D #------------------------------------------------------------------------ def __safely_close_cursor_and_rollback_close_conn(close_cursor=3DNone, rol= lback_tx=3DNone, close_conn=3DNone): if close_cursor: try: close_cursor() except PG_ERROR_EXCEPTION as pg_exc: _log.exception('cannot close cursor') gmConnectionPool.log_pg_exception_details(pg_exc) if rollback_tx: try: # need to rollback so ABORT state isn't retained in pooled connections rollback_tx() except PG_ERROR_EXCEPTION as pg_exc: _log.exception('cannot rollback transaction') gmConnectionPool.log_pg_exception_details(pg_exc) if close_conn: try: close_conn() except PG_ERROR_EXCEPTION as pg_exc: _log.exception('cannot close connection') gmConnectionPool.log_pg_exception_details(pg_exc) #------------------------------------------------------------------------ def run_rw_queries ( link_obj:_TLnkObj=3DNone, queries:_TQueries=3DNone, end_tx:bool=3DFalse, return_data:bool=3DNone, get_col_idx:bool=3DFalse, verbose:bool=3DFalse ) -> tuple[list[dbapi.extras.DictRow], dict[str, int] | None]: """Convenience function for running read-write queries. Typically (part of) a transaction. Args: link_obj: None, cursor, connection queries: * a list of dicts [{'cmd': , 'args': or ) * to be executed as a single transaction * the last query may usefully return rows, such as: SELECT currval('some_sequence'); or INSERT/UPDATE ... RETURNING some_value; end_tx: * controls whether the transaction is finalized (eg. COMMITted/ROLLed BACK) or not, this allows the call to run_rw_queries() to be part of a framing transaction * if link_obj is a *connection* then "end_tx" will default to False unless it is explicitly set to True which is taken to mean "yes, you do have full control over the transaction" in which case the transaction is properly finalized * if link_obj is a *cursor* we CANNOT finalize the transaction because we would need the connection for that * if link_obj is *None* "end_tx" will, of course, always be True, because we always have full control over the connection, not ending the transaction would be pointless return_data: * if true, the returned data will include the rows the last query selected * if false, it returns None instead get_col_idx: * True: the returned tuple will include a dictionary mapping field names to column positions * False: the returned tuple includes None instead of a field mapping dic= tionary Returns: * (None, None) if last query did not return rows * ("fetchall() result", ) if last query returned any rows and "re= turn_data" was True * for *index* see "get_col_idx" """ assert queries is not None, ' must not be None' if link_obj is None: conn =3D get_connection(readonly =3D False) curs =3D conn.cursor() conn_close =3D conn.close tx_commit =3D conn.commit tx_rollback =3D conn.rollback curs_close =3D curs.close notices_accessor =3D conn else: conn_close =3D lambda *x: None tx_commit =3D lambda *x: None tx_rollback =3D lambda *x: None curs_close =3D lambda *x: None if isinstance(link_obj, dbapi._psycopg.cursor): curs =3D link_obj notices_accessor =3D curs.connection elif isinstance(link_obj, dbapi._psycopg.connection): if end_tx: tx_commit =3D link_obj.commit tx_rollback =3D link_obj.rollback curs =3D link_obj.cursor() curs_close =3D curs.close notices_accessor =3D link_obj else: raise ValueError('link_obj must be cursor, connection or None but not [= %s]' % link_obj) for query in queries: try: args =3D query['args'] except KeyError: args =3D None try: curs.execute(query['cmd'], args) if verbose: gmConnectionPool.log_cursor_state(curs) for notice in notices_accessor.notices: _log.debug(notice.replace('\n', '/').replace('\n', '/')) del notices_accessor.notices[:] # DB related exceptions except dbapi.Error as pg_exc: _log.error('query failed in RW connection') gmConnectionPool.log_pg_exception_details(pg_exc) for notice in notices_accessor.notices: _log.debug(notice.replace('\n', '/').replace('\n', '/')) del notices_accessor.notices[:] __safely_close_cursor_and_rollback_close_conn ( curs_close, tx_rollback, conn_close ) # privilege problem ? if pg_exc.pgcode =3D=3D PG_error_codes.INSUFFICIENT_PRIVILEGE: details =3D 'Query: [%s]' % curs.query.decode(errors =3D 'replace').st= rip().strip('\n').strip().strip('\n') if curs.statusmessage !=3D '': details =3D 'Status: %s\n%s' % ( curs.statusmessage.strip().strip('\n').strip().strip('\n'), details ) if pg_exc.pgerror is None: msg =3D '[%s]' % pg_exc.pgcode else: msg =3D '[%s]: %s' % (pg_exc.pgcode, pg_exc.pgerror) raise gmExceptions.AccessDenied ( msg, source =3D 'PostgreSQL', code =3D pg_exc.pgcode, details =3D details ) # other DB problem gmLog2.log_stack_trace() raise # other exception except Exception: _log.exception('error running query in RW connection') gmConnectionPool.log_cursor_state(curs) for notice in notices_accessor.notices: _log.debug(notice.replace('\n', '/').replace('\n', '/')) del notices_accessor.notices[:] gmLog2.log_stack_trace() __safely_close_cursor_and_rollback_close_conn ( curs_close, tx_rollback, conn_close ) raise data =3D None col_idx =3D None if return_data: try: data =3D curs.fetchall() except Exception: _log.exception('error fetching data from RW query') gmLog2.log_stack_trace() __safely_close_cursor_and_rollback_close_conn ( curs_close, tx_rollback, conn_close ) raise if get_col_idx: col_idx =3D get_col_indices(curs) curs_close() tx_commit() conn_close() return (data, col_idx) #------------------------------------------------------------------------ Best, Karsten =2D- GPG 40BE 5B0E C98E 1713 AFA6 5BC0 3BEA AC80 7D4F C89B