from __future__ import annotations
-from typing import TYPE_CHECKING, Any
+from typing import Any
+from . import abc
from . import errors as e
from . import sql
from ._enums import PyFormat
from ._compat import Interpolation, Template
-
-if TYPE_CHECKING:
- from .abc import Transformer
+from ._transformer import Transformer
# Formats supported by template strings
FMT_AUTO = PyFormat.AUTO.value
class TemplateProcessor:
- def __init__(self, template: Template, *, tx: Transformer, server_params: bool):
+ def __init__(self, template: Template, *, tx: abc.Transformer, server_params: bool):
self.template = template
self._tx = tx
self._server_params = server_params
raise e.ProgrammingError(
f"{type(item.value).__qualname__} not supported in string templates"
)
+
+
+def as_string(t: Template, context: abc.AdaptContext | None = None) -> str:
+ tx = Transformer(context)
+ tp = TemplateProcessor(t, tx=tx, server_params=False)
+ tp.process()
+ return tp.query.decode(tx.encoding)
+
+
+def as_bytes(t: Template, context: abc.AdaptContext | None = None) -> bytes:
+ tx = Transformer(context)
+ tp = TemplateProcessor(t, tx=tx, server_params=False)
+ tp.process()
+ return tp.query
# Literals
NULL = SQL("NULL")
DEFAULT = SQL("DEFAULT")
+
+
+def as_string(obj: Composable | Template, context: AdaptContext | None = None) -> str:
+ if isinstance(obj, Composable):
+ return obj.as_string(context=context)
+ elif isinstance(obj, Template):
+ from ._tstrings import as_string
+
+ return as_string(obj, context)
+ else:
+ raise TypeError(f"{type(obj).__name__} objects not supported by as_string")
+
+
+def as_bytes(obj: Composable | Template, context: AdaptContext | None = None) -> bytes:
+ if isinstance(obj, Composable):
+ return obj.as_bytes(context=context)
+ elif isinstance(obj, Template):
+ from ._tstrings import as_bytes
+
+ return as_bytes(obj, context)
+ else:
+ raise TypeError(f"{type(obj).__name__} objects not supported by as_bytes")
from .utils import eur
from .fix_crdb import crdb_encoding, crdb_scs_off
+from .test_adapt import make_dumper
@pytest.mark.parametrize(
assert ph.as_bytes() == f"%(foo){format.value}".encode()
+def test_as_string():
+ query = sql.as_string(sql.SQL("select {}").format("foo"))
+ assert query == no_e("select 'foo'")
+
+
+def test_as_string_context(conn):
+ conn.adapters.register_dumper(str, make_dumper("1"))
+ query = sql.as_string(sql.SQL("select {}").format("foo"), context=conn)
+ assert query == no_e("select 'foo1'")
+
+
+def test_as_string_error():
+ with pytest.raises(TypeError):
+ sql.as_string("query") # type: ignore[arg-type]
+
+
+def test_as_bytes():
+ query = sql.as_bytes(sql.SQL("select {}").format("foo"))
+ assert query == no_e(b"select 'foo'")
+
+
+def test_as_bytes_context(conn):
+ conn.adapters.register_dumper(str, make_dumper("1"))
+ query = sql.as_bytes(sql.SQL("select {}").format("foo"), context=conn)
+ assert query == no_e(b"select 'foo1'")
+
+
+def test_as_bytes_error():
+ with pytest.raises(TypeError):
+ sql.as_bytes("query") # type: ignore[arg-type]
+
+
class TestValues:
def test_null(self, conn):
assert isinstance(sql.NULL, sql.SQL)
from psycopg.pq import Format
from .acompat import alist
+from .test_sql import no_e
+from .test_adapt import make_dumper
vstr = "hello"
vint = 16
assert cur.description[1].name == vstr
assert b"$2" in cur._query.query
assert b"$3" not in cur._query.query
+
+
+def test_as_string():
+ query = sql.as_string(t"select {vstr}")
+ assert query == no_e("select 'hello'")
+
+
+async def test_as_string_context(aconn):
+ aconn.adapters.register_dumper(str, make_dumper("1"))
+ query = sql.as_string(t"select {vstr}", context=aconn)
+ assert query == no_e("select 'hello1'")
+
+
+def test_as_bytes():
+ query = sql.as_bytes(t"select {vstr}")
+ assert query == no_e(b"select 'hello'")
+
+
+async def test_as_bytes_context(aconn):
+ aconn.adapters.register_dumper(str, make_dumper("1"))
+ query = sql.as_bytes(t"select {vstr}", context=aconn)
+ assert query == no_e(b"select 'hello1'")