1- """Parses a SQL statement and replaces the placeholders with the corresponding parameters"""
2-
31import collections
42
53from ._sql_sanitizer import SQLSanitizer , escape_verbatim_colon
1715
1816
1917def statement_factory (dialect ):
18+ """Creates a sanitizer for ``dialect`` and injects it into ``Statement``, exposing a simpler
19+ interface for ``Statement``.
20+
21+ :param dialect: a SQLAlchemy dialect
22+ :type dialect: :class:`sqlalchemy.engine.Dialect`
23+ """
24+
2025 sql_sanitizer = SQLSanitizer (dialect )
2126
2227 def statement (sql , * args , ** kwargs ):
@@ -26,9 +31,23 @@ def statement(sql, *args, **kwargs):
2631
2732
2833class Statement :
29- """Parses a SQL statement and replaces the placeholders with the corresponding parameters"""
34+ """Parses a SQL statement and substitutes any parameter markers with their corresponding
35+ placeholders.
36+ """
3037
3138 def __init__ (self , sql_sanitizer , sql , * args , ** kwargs ):
39+ """
40+ :param sql_sanitizer: The SQL sanitizer used to sanitize the parameters
41+ :type sql_sanitizer: :class:`_sql_sanitizer.SQLSanitizer`
42+
43+ :param sql: The SQL statement
44+ :type sql: str
45+
46+ :param *args: Zero or more positional parameters to be substituted for the parameter markers
47+
48+ :param *kwargs: Zero or more keyword arguments to be substituted for the parameter markers
49+ """
50+
3251 if len (args ) > 0 and len (kwargs ) > 0 :
3352 raise RuntimeError ("cannot pass both positional and named parameters" )
3453
@@ -54,9 +73,18 @@ def _get_escaped_kwargs(self, kwargs):
5473 return {k : self ._sql_sanitizer .escape (v ) for k , v in kwargs .items ()}
5574
5675 def _tokenize (self ):
76+ """
77+ :returns: A flattened list of SQLParse tokens that represent the SQL statement
78+ """
79+
5780 return list (self ._statement .flatten ())
5881
5982 def _get_operation_keyword (self ):
83+ """
84+ :returns: The operation keyword of the SQL statement (e.g., ``SELECT``, ``DELETE``, etc)
85+ :rtype: str
86+ """
87+
6088 for token in self ._statement :
6189 if is_operation_token (token .ttype ):
6290 token_value = token .value .upper ()
@@ -69,6 +97,11 @@ def _get_operation_keyword(self):
6997 return operation_keyword
7098
7199 def _get_paramstyle (self ):
100+ """
101+ :returns: The paramstyle used in the SQL statement (if any)
102+ :rtype: :class:_statement_util.Paramstyle``
103+ """
104+
72105 paramstyle = None
73106 for token in self ._tokens :
74107 if is_placeholder (token .ttype ):
@@ -80,6 +113,11 @@ def _get_paramstyle(self):
80113 return paramstyle
81114
82115 def _default_paramstyle (self ):
116+ """
117+ :returns: If positional args were passed, returns ``Paramstyle.QMARK``; if keyword arguments
118+ were passed, returns ``Paramstyle.NAMED``; otherwise, returns ``None``
119+ """
120+
83121 paramstyle = None
84122 if self ._args :
85123 paramstyle = Paramstyle .QMARK
@@ -89,6 +127,12 @@ def _default_paramstyle(self):
89127 return paramstyle
90128
91129 def _get_placeholders (self ):
130+ """
131+ :returns: A dict that maps the index of each parameter marker in the tokens list to the name
132+ of that parameter marker (if applicable) or ``None``
133+ :rtype: dict
134+ """
135+
92136 placeholders = collections .OrderedDict ()
93137 for index , token in enumerate (self ._tokens ):
94138 if is_placeholder (token .ttype ):
@@ -109,11 +153,18 @@ def _substitute_markers_with_escaped_params(self):
109153 self ._substitute_named_or_pyformat_markers ()
110154
111155 def _substitute_format_or_qmark_markers (self ):
156+ """Substitutes format or qmark parameter markers with their corresponding parameters.
157+ """
158+
112159 self ._assert_valid_arg_count ()
113160 for arg_index , token_index in enumerate (self ._placeholders .keys ()):
114161 self ._tokens [token_index ] = self ._args [arg_index ]
115162
116163 def _assert_valid_arg_count (self ):
164+ """Raises a ``RuntimeError`` if the number of arguments does not match the number of
165+ placeholders.
166+ """
167+
117168 if len (self ._placeholders ) != len (self ._args ):
118169 placeholders = get_human_readable_list (self ._placeholders .values ())
119170 args = get_human_readable_list (self ._args )
@@ -123,6 +174,10 @@ def _assert_valid_arg_count(self):
123174 raise RuntimeError (f"more placeholders ({ placeholders } ) than values ({ args } )" )
124175
125176 def _substitue_numeric_markers (self ):
177+ """Substitutes numeric parameter markers with their corresponding parameters. Raises a
178+ ``RuntimeError`` if any parameters are missing or unused.
179+ """
180+
126181 unused_arg_indices = set (range (len (self ._args )))
127182 for token_index , num in self ._placeholders .items ():
128183 if num >= len (self ._args ):
@@ -138,6 +193,10 @@ def _substitue_numeric_markers(self):
138193 f"unused value{ '' if len (unused_args ) == 1 else 's' } ({ unused_args } )" )
139194
140195 def _substitute_named_or_pyformat_markers (self ):
196+ """Substitutes named or pyformat parameter markers with their corresponding parameters.
197+ Raises a ``RuntimeError`` if any parameters are missing or unused.
198+ """
199+
141200 unused_params = set (self ._kwargs .keys ())
142201 for token_index , param_name in self ._placeholders .items ():
143202 if param_name not in self ._kwargs :
@@ -152,6 +211,10 @@ def _substitute_named_or_pyformat_markers(self):
152211 f"unused value{ '' if len (unused_params ) == 1 else 's' } ({ joined_unused_params } )" )
153212
154213 def _escape_verbatim_colons (self ):
214+ """Escapes verbatim colons from string literal and identifier tokens so they aren't treated
215+ as parameter markers.
216+ """
217+
155218 for token in self ._tokens :
156219 if is_string_literal (token .ttype ) or is_identifier (token .ttype ):
157220 token .value = escape_verbatim_colon (token .value )
@@ -175,4 +238,7 @@ def is_update(self):
175238 return self ._operation_keyword == "UPDATE"
176239
177240 def __str__ (self ):
241+ """Joins the statement tokens into a string.
242+ """
243+
178244 return "" .join ([str (token ) for token in self ._tokens ])
0 commit comments