import json
import logging
-from types import CodeType # noqa[F401]
+from types import BuiltinFunctionType, CodeType # noqa[F401]
from typing import Any, Callable
from warnings import warn
from threading import Lock
JsonDumpsFunction: TypeAlias = Callable[[Any], "str | bytes"]
JsonLoadsFunction: TypeAlias = Callable[["str | bytes"], Any]
-_AdapterKey: TypeAlias = "tuple[type, CodeType]"
+_AdapterKey: TypeAlias = "tuple[type, CodeType | BuiltinFunctionType | type]"
logger = logging.getLogger("psycopg")
the same if a lambda if defined in a function, so we can use it as a more
stable hash key.
"""
+ # A builtin is stable and has no cache so it's a good candidate as hash key.
+ if isinstance(f, (BuiltinFunctionType, type)):
+ return (t, f)
+
# Check if there's an unexpected Python implementation that doesn't define
- # these dunder attributes. If thta's the case, raise a warning, which will
+ # these dunder attributes. If that's the case, raise a warning, which will
# crash our test suite and/or hopefully will be detected by the user.
try:
f.__code__
assert caplog.records
+@pytest.mark.parametrize("binary", [True, False])
+@pytest.mark.parametrize("pgtype", ["json", "jsonb"])
+@pytest.mark.parametrize("dumps", [str, len])
+def test_dumper_warning_builtin(dsn, binary, pgtype, dumps, caplog, recwarn):
+ caplog.set_level(logging.WARNING, logger="psycopg")
+ recwarn.clear()
+
+ # Note: private implementation, it might change
+ from psycopg.types.json import _dumpers_cache
+
+ # A function with no closure is cached on the code, so lambdas are not
+ # different items.
+
+ with psycopg.connect(dsn) as conn1:
+ set_json_dumps(dumps, conn1)
+ assert not recwarn
+ assert (size1 := len(_dumpers_cache))
+
+ with psycopg.connect(dsn) as conn2:
+ set_json_dumps(dumps, conn2)
+ size2 = len(_dumpers_cache)
+
+ assert size1 == size2
+ assert not caplog.records
+ assert not recwarn
+
+
@pytest.mark.parametrize("binary", [True, False])
@pytest.mark.parametrize("pgtype", ["json", "jsonb"])
def test_load_leak_with_local_functions(dsn, binary, pgtype, caplog):
assert caplog.records
+@pytest.mark.parametrize("binary", [True, False])
+@pytest.mark.parametrize("pgtype", ["json", "jsonb"])
+@pytest.mark.parametrize("loads", [str, len])
+def test_loader_warning_builtin(dsn, binary, pgtype, loads, caplog, recwarn):
+ caplog.set_level(logging.WARNING, logger="psycopg")
+ recwarn.clear()
+
+ # Note: private implementation, it might change
+ from psycopg.types.json import _loaders_cache
+
+ # A function with no closure is cached on the code, so lambdas are not
+ # different items.
+
+ with psycopg.connect(dsn) as conn1:
+ set_json_loads(loads, conn1)
+ assert not recwarn
+ assert (size1 := len(_loaders_cache))
+
+ with psycopg.connect(dsn) as conn2:
+ set_json_loads(loads, conn2)
+ size2 = len(_loaders_cache)
+
+ assert size1 == size2
+ assert not caplog.records
+ assert not recwarn
+
+
def my_dumps(obj):
obj = deepcopy(obj)
obj["baz"] = "qux"