Enhancement: Update psycopg2 to 3.

This commit is contained in:
SergeantPanda 2026-05-28 19:27:52 -05:00
parent d6f837238f
commit 80b72d8cfa
7 changed files with 26 additions and 40 deletions

View file

@ -1,6 +1,6 @@
import sys
import psycopg2
from psycopg2 import sql
import psycopg
from psycopg import sql
from django.core.management.base import BaseCommand
from django.conf import settings
from django.db import connection
@ -38,8 +38,7 @@ class Command(BaseCommand):
maintenance_db = 'postgres'
try:
self.stdout.write("Connecting to maintenance database...")
conn = psycopg2.connect(dbname=maintenance_db, user=user, password=password, host=host, port=port, **ssl_kwargs)
conn.autocommit = True
conn = psycopg.connect(dbname=maintenance_db, user=user, password=password, host=host, port=port, autocommit=True, **ssl_kwargs)
cur = conn.cursor()
self.stdout.write(f"Dropping database '{db_name}'...")
cur.execute(sql.SQL("DROP DATABASE IF EXISTS {}").format(sql.Identifier(db_name)))

View file

@ -153,7 +153,7 @@ class EpgIgnoreListsTest(TestCase):
class DropDBCommandTlsTest(TestCase):
"""Verify dropdb management command passes TLS parameters to psycopg2."""
"""Verify dropdb management command passes TLS parameters to psycopg."""
databases = []
_DB_WITH_TLS = {
@ -184,7 +184,7 @@ class DropDBCommandTlsTest(TestCase):
}
}
@patch('core.management.commands.dropdb.psycopg2.connect')
@patch('core.management.commands.dropdb.psycopg.connect')
@patch('core.management.commands.dropdb.connection')
@patch('builtins.input', return_value='yes')
def test_dropdb_passes_ssl_kwargs_when_tls_enabled(self, _inp, _conn, mock_connect):
@ -199,13 +199,14 @@ class DropDBCommandTlsTest(TestCase):
mock_connect.assert_called_once_with(
dbname='postgres', user='testuser', password='testpass',
host='localhost', port=5432,
autocommit=True,
sslmode='verify-full',
sslrootcert='/certs/ca.crt',
sslcert='/certs/client.crt',
sslkey='/certs/client.key',
)
@patch('core.management.commands.dropdb.psycopg2.connect')
@patch('core.management.commands.dropdb.psycopg.connect')
@patch('core.management.commands.dropdb.connection')
@patch('builtins.input', return_value='yes')
def test_dropdb_no_ssl_kwargs_when_tls_disabled(self, _inp, _conn, mock_connect):
@ -220,4 +221,5 @@ class DropDBCommandTlsTest(TestCase):
mock_connect.assert_called_once_with(
dbname='postgres', user='testuser', password='testpass',
host='localhost', port=5432,
autocommit=True,
)

View file

@ -1,24 +1,20 @@
"""
Loaded via uWSGI's `import = dispatcharr.gevent_patch` directive.
Two things happen here:
gevent stdlib monkey-patching - replaces blocking socket/threading/os
primitives with gevent-cooperative versions. `gevent-early-monkey-patch = true`
in the ini should already have done this; calling it again is a safe no-op if
it did, and a necessary fallback if it didn't (e.g. older uWSGI build).
1. gevent stdlib monkey-patching - replaces blocking socket/threading/os
primitives with gevent-cooperative versions. `gevent-early-monkey-patch = true`
in the ini should already have done this; calling it again is a safe no-op if
it did, and a necessary fallback if it didn't (e.g. older uWSGI build).
2. psycogreen - installs a wait callback on psycopg2 so libpq yields to the
gevent hub during I/O instead of blocking the OS thread.
Without (1), `async_to_sync(channel_layer.group_send)` in send_websocket_update
Without this, `async_to_sync(channel_layer.group_send)` in send_websocket_update
calls epoll_wait() directly, which blocks the OS thread and freezes all greenlets
on the worker until the call returns. With (1), select.epoll is replaced by
monkey-patching, which breaks asyncio event loop creation in threadpool threads.
on the worker until the call returns. With monkey-patching, select.epoll is
replaced, which breaks asyncio event loop creation in threadpool threads.
send_websocket_update therefore uses a synchronous Redis path in gevent workers
instead of asyncio - see _gevent_ws_send() in core/utils.py.
Without (2), psycopg2 network calls pin the worker during slow/stalled queries.
psycopg3 uses Python's socket layer for I/O, so monkey.patch_all() provides
gevent compatibility without any additional driver patching.
Celery and Daphne run in separate daemon processes and do not load this module.
@ -40,15 +36,4 @@ if not monkey.is_module_patched("socket"):
else:
print("[gevent_patch] gevent stdlib monkey-patching already active.", flush=True)
try:
from psycogreen.gevent import patch_psycopg
patch_psycopg()
print("[gevent_patch] psycogreen: psycopg2 patched for gevent.", flush=True)
except ImportError:
print(
"[gevent_patch] WARNING: psycogreen not installed - "
"psycopg2 will block the gevent hub during DB I/O. "
"Run: uv pip install psycogreen",
file=sys.stderr,
flush=True,
)

View file

@ -42,8 +42,8 @@ gevent = 100
# Patch the stdlib (socket, threading, time, ...) before any app code
# loads so blocking calls yield to the gevent hub. Without this, a single
# blocking psycopg2 / requests / DNS call freezes every greenlet on the
# worker. The companion module greens psycopg2 specifically.
# blocking requests / DNS call freezes every greenlet on the
# worker. psycopg3 uses Python's socket layer, so no additional patching is needed.
gevent-early-monkey-patch = true
import = dispatcharr.gevent_patch

View file

@ -45,8 +45,8 @@ gevent = 100
# Patch the stdlib (socket, threading, time, ...) before any app code
# loads so blocking calls yield to the gevent hub. Without this, a single
# blocking psycopg2 / requests / DNS call freezes every greenlet on the
# worker. The companion module greens psycopg2 specifically.
# blocking requests / DNS call freezes every greenlet on the
# worker. psycopg3 uses Python's socket layer, so no additional patching is needed.
gevent-early-monkey-patch = true
import = dispatcharr.gevent_patch

View file

@ -48,8 +48,8 @@ gevent = 400 # Each unused greenlet costs ~2-4KB of memory
# Patch the stdlib (socket, threading, time, ...) before any app code
# loads so blocking calls yield to the gevent hub. Without this, a single
# blocking psycopg2 / requests / DNS call freezes every greenlet on the
# worker. The companion module greens psycopg2 specifically.
# blocking requests / DNS call freezes every greenlet on the
# worker. psycopg3 uses Python's socket layer, so no additional patching is needed.
gevent-early-monkey-patch = true
import = dispatcharr.gevent_patch

View file

@ -44,8 +44,8 @@ gevent = 400 # Each unused greenlet costs ~2-4KB of memory
# Patch the stdlib (socket, threading, time, ...) before any app code
# loads so blocking calls yield to the gevent hub. Without this, a single
# blocking psycopg2 / requests / DNS call freezes every greenlet on the
# worker. The companion module greens psycopg2 specifically.
# blocking requests / DNS call freezes every greenlet on the
# worker. psycopg3 uses Python's socket layer, so no additional patching is needed.
gevent-early-monkey-patch = true
import = dispatcharr.gevent_patch