mirror of
https://github.com/psycopg/psycopg2.git
synced 2024-11-25 10:23:43 +03:00
Adding sql module documentation
This commit is contained in:
parent
41b9bfe401
commit
4a55b8018a
|
@ -44,6 +44,7 @@ Psycopg 2 is both Unicode and Python 3 friendly.
|
||||||
advanced
|
advanced
|
||||||
extensions
|
extensions
|
||||||
extras
|
extras
|
||||||
|
sql
|
||||||
tz
|
tz
|
||||||
pool
|
pool
|
||||||
errorcodes
|
errorcodes
|
||||||
|
|
171
lib/sql.py
171
lib/sql.py
|
@ -31,8 +31,29 @@ from psycopg2 import extensions as ext
|
||||||
|
|
||||||
|
|
||||||
class Composable(object):
|
class Composable(object):
|
||||||
"""Base class for objects that can be used to compose an SQL string."""
|
"""
|
||||||
|
Abstract base class for objects that can be used to compose an SQL string.
|
||||||
|
|
||||||
|
Composables can be passed directly to `~cursor.execute()` and
|
||||||
|
`~cursor.executemany()`.
|
||||||
|
|
||||||
|
Composables can be joined using the ``+`` operator: the result will be
|
||||||
|
a `Composed` instance containing the objects joined. The operator ``*`` is
|
||||||
|
also supported with an integer argument: the result is a `!Composed`
|
||||||
|
instance containing the left argument repeated as many times as requested.
|
||||||
|
|
||||||
|
.. automethod:: as_string
|
||||||
|
"""
|
||||||
def as_string(self, conn_or_curs):
|
def as_string(self, conn_or_curs):
|
||||||
|
"""
|
||||||
|
Return the string value of the object.
|
||||||
|
|
||||||
|
The object is evaluated in the context of the *conn_or_curs* argument.
|
||||||
|
|
||||||
|
The function is automatically invoked by `~cursor.execute()` and
|
||||||
|
`~cursor.executemany()` if a `!Composable` is passed instead of the
|
||||||
|
query string.
|
||||||
|
"""
|
||||||
raise NotImplementedError
|
raise NotImplementedError
|
||||||
|
|
||||||
def __add__(self, other):
|
def __add__(self, other):
|
||||||
|
@ -43,8 +64,26 @@ class Composable(object):
|
||||||
else:
|
else:
|
||||||
return NotImplemented
|
return NotImplemented
|
||||||
|
|
||||||
|
def __mul__(self, n):
|
||||||
|
return Composed([self] * n)
|
||||||
|
|
||||||
|
|
||||||
class Composed(Composable):
|
class Composed(Composable):
|
||||||
|
"""
|
||||||
|
A `Composable` object obtained concatenating a sequence of `Composable`.
|
||||||
|
|
||||||
|
The object is usually created using `compose()` and the `Composable`
|
||||||
|
operators. However it is possible to create a `!Composed` directly
|
||||||
|
specifying a sequence of `Composable` as arguments.
|
||||||
|
|
||||||
|
Example::
|
||||||
|
|
||||||
|
>>> sql.Composed([sql.SQL("insert into "), sql.Identifier("table")]) \\
|
||||||
|
... .as_string(conn)
|
||||||
|
'insert into "table"'
|
||||||
|
|
||||||
|
.. automethod:: join
|
||||||
|
"""
|
||||||
def __init__(self, seq):
|
def __init__(self, seq):
|
||||||
self._seq = []
|
self._seq = []
|
||||||
for i in seq:
|
for i in seq:
|
||||||
|
@ -70,10 +109,20 @@ class Composed(Composable):
|
||||||
else:
|
else:
|
||||||
return NotImplemented
|
return NotImplemented
|
||||||
|
|
||||||
def __mul__(self, n):
|
|
||||||
return Composed(self._seq * n)
|
|
||||||
|
|
||||||
def join(self, joiner):
|
def join(self, joiner):
|
||||||
|
"""
|
||||||
|
Return a new `!Composed` interposing the *joiner* with the `!Composed` items.
|
||||||
|
|
||||||
|
The *joiner* must be a `SQL` or a string which will be interpreted as
|
||||||
|
an `SQL`.
|
||||||
|
|
||||||
|
Example::
|
||||||
|
|
||||||
|
>>> fields = sql.Identifier('foo') + sql.Identifier('bar') # a Composed
|
||||||
|
>>> fields.join(', ').as_string(conn)
|
||||||
|
'"foo", "bar"'
|
||||||
|
|
||||||
|
"""
|
||||||
if isinstance(joiner, basestring):
|
if isinstance(joiner, basestring):
|
||||||
joiner = SQL(joiner)
|
joiner = SQL(joiner)
|
||||||
elif not isinstance(joiner, SQL):
|
elif not isinstance(joiner, SQL):
|
||||||
|
@ -93,10 +142,15 @@ class Composed(Composable):
|
||||||
|
|
||||||
|
|
||||||
class SQL(Composable):
|
class SQL(Composable):
|
||||||
def __init__(self, wrapped):
|
"""
|
||||||
if not isinstance(wrapped, basestring):
|
A `Composable` representing a snippet of SQL string to be included verbatim.
|
||||||
|
|
||||||
|
.. automethod:: join
|
||||||
|
"""
|
||||||
|
def __init__(self, string):
|
||||||
|
if not isinstance(string, basestring):
|
||||||
raise TypeError("SQL values must be strings")
|
raise TypeError("SQL values must be strings")
|
||||||
self._wrapped = wrapped
|
self._wrapped = string
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
return "sql.SQL(%r)" % (self._wrapped,)
|
return "sql.SQL(%r)" % (self._wrapped,)
|
||||||
|
@ -104,10 +158,21 @@ class SQL(Composable):
|
||||||
def as_string(self, conn_or_curs):
|
def as_string(self, conn_or_curs):
|
||||||
return self._wrapped
|
return self._wrapped
|
||||||
|
|
||||||
def __mul__(self, n):
|
|
||||||
return Composed([self] * n)
|
|
||||||
|
|
||||||
def join(self, seq):
|
def join(self, seq):
|
||||||
|
"""
|
||||||
|
Join a sequence of `Composable` or a `Composed` and return a `!Composed`.
|
||||||
|
|
||||||
|
Use the object value to separate the *seq* elements.
|
||||||
|
|
||||||
|
Example::
|
||||||
|
|
||||||
|
>>> snip - sql.SQL(', ').join(map(sql.Identifier, ['foo', 'bar', 'baz']))
|
||||||
|
>>> snip.as_string(conn)
|
||||||
|
'"foo", "bar", "baz"'
|
||||||
|
"""
|
||||||
|
if isinstance(seq, Composed):
|
||||||
|
seq = seq._seq
|
||||||
|
|
||||||
rv = []
|
rv = []
|
||||||
it = iter(seq)
|
it = iter(seq)
|
||||||
try:
|
try:
|
||||||
|
@ -123,15 +188,21 @@ class SQL(Composable):
|
||||||
|
|
||||||
|
|
||||||
class Identifier(Composable):
|
class Identifier(Composable):
|
||||||
def __init__(self, wrapped):
|
"""
|
||||||
if not isinstance(wrapped, basestring):
|
A `Composable` representing an SQL identifer.
|
||||||
|
|
||||||
|
Identifiers usually represent names of database objects, such as tables
|
||||||
|
or fields. They follow `different rules`__ than SQL string literals for
|
||||||
|
escaping (e.g. they use double quotes).
|
||||||
|
|
||||||
|
.. __: https://www.postgresql.org/docs/current/static/sql-syntax-lexical.html# \
|
||||||
|
SQL-SYNTAX-IDENTIFIERS
|
||||||
|
"""
|
||||||
|
def __init__(self, string):
|
||||||
|
if not isinstance(string, basestring):
|
||||||
raise TypeError("SQL identifiers must be strings")
|
raise TypeError("SQL identifiers must be strings")
|
||||||
|
|
||||||
self._wrapped = wrapped
|
self._wrapped = string
|
||||||
|
|
||||||
@property
|
|
||||||
def wrapped(self):
|
|
||||||
return self._wrapped
|
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
return "sql.Identifier(%r)" % (self._wrapped,)
|
return "sql.Identifier(%r)" % (self._wrapped,)
|
||||||
|
@ -141,6 +212,11 @@ class Identifier(Composable):
|
||||||
|
|
||||||
|
|
||||||
class Literal(Composable):
|
class Literal(Composable):
|
||||||
|
"""
|
||||||
|
Represent an SQL value to be included in a query.
|
||||||
|
|
||||||
|
The object follows the normal :ref:`adaptation rules <python-types-adaptation>`
|
||||||
|
"""
|
||||||
def __init__(self, wrapped):
|
def __init__(self, wrapped):
|
||||||
self._wrapped = wrapped
|
self._wrapped = wrapped
|
||||||
|
|
||||||
|
@ -166,11 +242,31 @@ class Literal(Composable):
|
||||||
|
|
||||||
return rv
|
return rv
|
||||||
|
|
||||||
def __mul__(self, n):
|
|
||||||
return Composed([self] * n)
|
|
||||||
|
|
||||||
|
|
||||||
class Placeholder(Composable):
|
class Placeholder(Composable):
|
||||||
|
"""A `Composable` representing a placeholder for query parameters.
|
||||||
|
|
||||||
|
If the name is specified, generate a named placeholder (e.g. ``%(name)s``),
|
||||||
|
otherwise generate a positional placeholder (e.g. ``%s``).
|
||||||
|
|
||||||
|
The object is useful to generate SQL queries with a variable number of
|
||||||
|
arguments.
|
||||||
|
|
||||||
|
Examples::
|
||||||
|
|
||||||
|
>>> sql.compose("insert into table (%s) values (%s)", [
|
||||||
|
... sql.SQL(', ').join(map(sql.Identifier, names)),
|
||||||
|
... sql.SQL(', ').join(sql.Placeholder() * 3)
|
||||||
|
... ]).as_string(conn)
|
||||||
|
'insert into table ("foo", "bar", "baz") values (%s, %s, %s)'
|
||||||
|
|
||||||
|
>>> sql.compose("insert into table (%s) values (%s)", [
|
||||||
|
... sql.SQL(', ').join(map(sql.Identifier, names)),
|
||||||
|
... sql.SQL(', ').join(map(sql.Placeholder, names))
|
||||||
|
... ]).as_string(conn)
|
||||||
|
'insert into table ("foo", "bar", "baz") values (%(foo)s, %(bar)s, %(baz)s)'
|
||||||
|
"""
|
||||||
|
|
||||||
def __init__(self, name=None):
|
def __init__(self, name=None):
|
||||||
if isinstance(name, basestring):
|
if isinstance(name, basestring):
|
||||||
if ')' in name:
|
if ')' in name:
|
||||||
|
@ -185,9 +281,6 @@ class Placeholder(Composable):
|
||||||
return "sql.Placeholder(%r)" % (
|
return "sql.Placeholder(%r)" % (
|
||||||
self._name if self._name is not None else '',)
|
self._name if self._name is not None else '',)
|
||||||
|
|
||||||
def __mul__(self, n):
|
|
||||||
return Composed([self] * n)
|
|
||||||
|
|
||||||
def as_string(self, conn_or_curs):
|
def as_string(self, conn_or_curs):
|
||||||
if self._name is not None:
|
if self._name is not None:
|
||||||
return "%%(%s)s" % self._name
|
return "%%(%s)s" % self._name
|
||||||
|
@ -204,7 +297,37 @@ re_compose = re.compile("""
|
||||||
""", re.VERBOSE)
|
""", re.VERBOSE)
|
||||||
|
|
||||||
|
|
||||||
def compose(sql, args=()):
|
def compose(sql, args=None):
|
||||||
|
"""
|
||||||
|
Merge an SQL string with some variable parts.
|
||||||
|
|
||||||
|
The *sql* string can contain placeholders such as `%s` or `%(name)s`.
|
||||||
|
If the string must contain a literal ``%`` symbol use ``%%``. Note that,
|
||||||
|
unlike `~cursor.execute()`, the replacement ``%%`` |=>| ``%`` is *always*
|
||||||
|
performed, even if there is no argument.
|
||||||
|
|
||||||
|
.. |=>| unicode:: 0x21D2 .. double right arrow
|
||||||
|
|
||||||
|
*args* must be a sequence or mapping (according to the placeholder style)
|
||||||
|
of `Composable` instances.
|
||||||
|
|
||||||
|
The value returned is a `Composed` instance obtained replacing the
|
||||||
|
arguments to the query placeholders.
|
||||||
|
|
||||||
|
Example::
|
||||||
|
|
||||||
|
>>> query = sql.compose(
|
||||||
|
... "select %s from %s", [
|
||||||
|
... sql.SQL(', ').join(
|
||||||
|
... [sql.Identifier('foo'), sql.Identifier('bar')]),
|
||||||
|
... sql.Identifier('table')])
|
||||||
|
>>> query.as_string(conn)
|
||||||
|
select "foo", "bar" from "table"'
|
||||||
|
|
||||||
|
"""
|
||||||
|
if args is None:
|
||||||
|
args = ()
|
||||||
|
|
||||||
phs = list(re_compose.finditer(sql))
|
phs = list(re_compose.finditer(sql))
|
||||||
|
|
||||||
# check placeholders consistent
|
# check placeholders consistent
|
||||||
|
|
|
@ -208,6 +208,11 @@ class SQLTests(ConnectingTestCase):
|
||||||
self.assert_(isinstance(obj, sql.Composed))
|
self.assert_(isinstance(obj, sql.Composed))
|
||||||
self.assertEqual(obj.as_string(self.conn), '"foo", bar, 42')
|
self.assertEqual(obj.as_string(self.conn), '"foo", bar, 42')
|
||||||
|
|
||||||
|
obj = sql.SQL(", ").join(
|
||||||
|
sql.Composed([sql.Identifier('foo'), sql.SQL('bar'), sql.Literal(42)]))
|
||||||
|
self.assert_(isinstance(obj, sql.Composed))
|
||||||
|
self.assertEqual(obj.as_string(self.conn), '"foo", bar, 42')
|
||||||
|
|
||||||
|
|
||||||
class ComposedTest(ConnectingTestCase):
|
class ComposedTest(ConnectingTestCase):
|
||||||
def test_class(self):
|
def test_class(self):
|
||||||
|
|
Loading…
Reference in New Issue
Block a user