Edgewall Software
Modify

Opened 5 weeks ago

Closed 5 weeks ago

#13803 closed defect (fixed)

_libpq_pathname in postgres_backend.py should be a str instance

Reported by: Jun Omae Owned by: Jun Omae
Priority: normal Milestone: 1.6.1
Component: database backend Version: 1.6
Severity: normal Keywords: postgresql
Cc: Branch:
Release Notes:
  • Fixed libpq version detection crashing with Python 3.13.0.
  • Use psycopg2.extensions.libpq_version() to retrieve the libpq version if psycopg2≥2.7.
API Changes:
Internal Changes:

Description

Raised from https://github.com/edgewall/trac/pull/24.

ctypes does not handle bytes, but strings. Currently, opening the library just fails with cause ctypes immediately calls libname.endwith("…") on it, which fails if libname isn't a string.

  • trac/db/postgres_backend.py

    diff --git a/trac/db/postgres_backend.py b/trac/db/postgres_backend.py
    index 162a714937..4dad6c3e14 100644
    a b  
    6262                        '''.encode('utf-8'),
    6363                        _f.read(), re.VERBOSE)
    6464                    if _match:
    65                         _libpq_pathname = _match.group(1)
     65                        _libpq_pathname = _match.group(1).decode('utf-8')
    6666                else:
    6767                    if re.search(r'\0libpq\.dll\0'.encode('utf-8'), _f.read(),
    6868                                 re.IGNORECASE):

Attachments (0)

Change History (4)

comment:1 by Jun Omae, 5 weeks ago

Hmm, the issue is not occurred in my environment for PostgreSQL.

Python 3.11.10 (main, Sep  7 2024, 18:35:42) [GCC 9.4.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> import ctypes
>>> from trac.db.postgres_backend import _libpq_pathname
>>> _libpq_pathname
b'libpq.so.5'
>>> lib = ctypes.CDLL(_libpq_pathname)
>>> lib.PQlibVersion()
120020

According to https://github.com/psycopg/psycopg2/blob/2.9.9/NEWS#L398-L399, psycopg2.__libpq_version__ and psycopg2.extensions.libpq_version() have been added to retrieve the version of libpq in psycopg2 2.7. I'm trying to check the libpq_version() method first.

>>> import psycopg2
>>> psycopg2
<module 'psycopg2' from '/home/jun66j5/venv/py311/lib/python3.11/site-packages/psycopg2/__init__.py'>
>>> psycopg2.libpq_version
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: module 'psycopg2' has no attribute 'libpq_version'. Did you mean: '__libpq_version__'?
>>> psycopg2.__libpq_version__
120017
>>> import psycopg2.extensions
>>> psycopg2.extensions.libpq_version()
120020

comment:2 by BtbN, 5 weeks ago

This is only an issue with Python 3.13 and up, since the versions before did not have this check: https://github.com/python/cpython/commit/408e127159e54d87bb3464fd8bd60219dc527fac

But yeah, the whole version check is a bit dubious for sure. Why does it regexp a libpq.so.5 string out of the library that's bundled with psycopg2-binary, to then try and dlopen() that, which in turn just fails again with a File Not Found error.

Shouldn't it instead just pass psycopg._psycopg.__file__ to CDLL directly? A quick try-out of that seems to work fine at least.

in reply to:  2 comment:3 by Jun Omae, 5 weeks ago

Replying to BtbN:

This is only an issue with Python 3.13 and up, since the versions before did not have this check: https://github.com/python/cpython/commit/408e127159e54d87bb3464fd8bd60219dc527fac

Python 3.13.0 (main, Oct  8 2024, 08:51:27) [GCC 9.4.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> from trac.db.postgres_backend import _libpq_pathname
>>> _libpq_pathname
b'libpq.so.5'
>>> import ctypes
>>> lib = ctypes.CDLL(_libpq_pathname)
Traceback (most recent call last):
  File "<python-input-4>", line 1, in <module>
    lib = ctypes.CDLL(_libpq_pathname)
  File "/usr/lib/python3.13/ctypes/__init__.py", line 353, in __init__
    if name.endswith(".fwork"):
       ~~~~~~~~~~~~~^^^^^^^^^^
TypeError: endswith first arg must be bytes or a tuple of bytes, not str
>>>

I think the issue when passing a bytes instance to ctypes.CDLL is an issue of Python 3.13.0.

But yeah, the whole version check is a bit dubious for sure. Why does it regexp a libpq.so.5 string out of the library that's bundled with psycopg2-binary, to then try and dlopen() that, which in turn just fails again with a File Not Found error.

Shouldn't it instead just pass psycopg._psycopg.__file__ to CDLL directly? A quick try-out of that seems to work fine at least.

psycopg._psycopg.__file__ is _psycopg.so, not libpq. The code is added in [15257] (#12622), in order to detect _psycopg.so do dynamic linked or static linked with libpq module.

comment:4 by Jun Omae, 5 weeks ago

Release Notes: modified (diff)
Resolution: fixed
Status: assignedclosed

Committed BtbN's patch in [17861]. Thanks, BtbN!

Also, used psycopg2.extensions.libpq_version() in [17862] and merged two changes to trunk in [17863].

Modify Ticket

Change Properties
Set your email in Preferences
Action
as closed The owner will remain Jun Omae.
The resolution will be deleted. Next status will be 'reopened'.
to The owner will be changed from Jun Omae to the specified user.

Add Comment


E-mail address and name can be saved in the Preferences .
 
Note: See TracTickets for help on using tickets.