From: Daniele Varrazzo Date: Tue, 27 Oct 2020 17:30:50 +0000 (+0100) Subject: Subclasses of dumpable objects are dumpable too X-Git-Tag: 3.0.dev0~446 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=46eb6f91755ef5a8f0f0a91ceab213e3d7468381;p=thirdparty%2Fpsycopg.git Subclasses of dumpable objects are dumpable too --- diff --git a/psycopg3/psycopg3/transform.py b/psycopg3/psycopg3/transform.py index 83593e866..345b7d8ea 100644 --- a/psycopg3/psycopg3/transform.py +++ b/psycopg3/psycopg3/transform.py @@ -144,24 +144,28 @@ class Transformer: rc.append(self.get_loader(oid, fmt).load) def get_dumper(self, obj: Any, format: Format) -> "Dumper": - key = (type(obj), format) + # Fast path: return a Dumper class already instantiated from the same type + cls = type(obj) try: - return self._dumpers_cache[key] + return self._dumpers_cache[cls, format] except KeyError: pass - for amap in self._dumpers_maps: - if key in amap: - dumper_cls = amap[key] - break - else: - raise e.ProgrammingError( - f"cannot adapt type {type(obj).__name__}" - f" to format {Format(format).name}" - ) - - self._dumpers_cache[key] = dumper = dumper_cls(key[0], self) - return dumper + # We haven't seen this type in this query yet. Look for an adapter + # in contexts from the most specific to the most generic. + # Also look for superclasses: if you can adapt a type you should be + # able to adapt its subtypes, otherwise Liskov is sad. + for dmap in self._dumpers_maps: + for scls in cls.__mro__: + key = (scls, format) + if key in dmap: + self._dumpers_cache[key] = dumper = dmap[key](scls, self) + return dumper + + raise e.ProgrammingError( + f"cannot adapt type {type(obj).__name__}" + f" to format {Format(format).name}" + ) def load_row(self, row: int) -> Optional[Tuple[Any, ...]]: res = self.pgresult diff --git a/psycopg3_c/psycopg3_c/transform.pyx b/psycopg3_c/psycopg3_c/transform.pyx index ca5a711da..e79a32dba 100644 --- a/psycopg3_c/psycopg3_c/transform.pyx +++ b/psycopg3_c/psycopg3_c/transform.pyx @@ -178,24 +178,28 @@ cdef class Transformer: return row_loader def get_dumper(self, obj: Any, format: Format) -> "Dumper": - key = (type(obj), format) + # Fast path: return a Dumper class already instantiated from the same type + cls = type(obj) try: - return self._dumpers_cache[key] + return self._dumpers_cache[cls, format] except KeyError: pass - for amap in self._dumpers_maps: - if key in amap: - dumper_cls = amap[key] - break - else: - raise e.ProgrammingError( - f"cannot adapt type {type(obj).__name__}" - f" to format {Format(format).name}" - ) - - self._dumpers_cache[key] = dumper = dumper_cls(key[0], self) - return dumper + # We haven't seen this type in this query yet. Look for an adapter + # in contexts from the most specific to the most generic. + # Also look for superclasses: if you can adapt a type you should be + # able to adapt its subtypes, otherwise Liskov is sad. + for dmap in self._dumpers_maps: + for scls in cls.__mro__: + key = (scls, format) + if key in dmap: + self._dumpers_cache[key] = dumper = dmap[key](scls, self) + return dumper + + raise e.ProgrammingError( + f"cannot adapt type {type(obj).__name__}" + f" to format {Format(format).name}" + ) def load_row(self, row: int) -> Optional[Tuple[Any, ...]]: if self._pgresult is None: diff --git a/tests/test_adapt.py b/tests/test_adapt.py index 9bb074553..b566c1859 100644 --- a/tests/test_adapt.py +++ b/tests/test_adapt.py @@ -45,6 +45,16 @@ def test_dump_cursor_ctx(conn): assert cur.fetchone() == ("hellot", "worldb") +@pytest.mark.parametrize("fmt_out", [Format.TEXT, Format.BINARY]) +def test_dump_subclass(conn, fmt_out): + class MyString(str): + pass + + cur = conn.cursor() + cur.execute("select %s, %b", [MyString("hello"), MyString("world")]) + assert cur.fetchone() == ("hello", "world") + + @pytest.mark.parametrize( "data, format, type, result", [