"""Parameter style conversion utilities."""
from collections.abc import Callable, Mapping, Sequence
from typing import Any, Final
from mypy_extensions import mypyc_attr
from sqlspec.core.parameters._types import (
_NAMED_STYLES,
_POSITIONAL_STYLES,
ConvertedParameters,
NamedParameterOutput,
ParameterInfo,
ParameterMapping,
ParameterPayload,
ParameterSequence,
ParameterStyle,
PositionalParameterOutput,
)
from sqlspec.core.parameters._validator import ParameterValidator
from sqlspec.exceptions import SQLSpecError
__all__ = ("ParameterConverter",)
_ORDERED_PARAM_INFO_MIN_SIZE = 2
_OCCURRENCE_KEYED_STYLES: Final[frozenset[ParameterStyle]] = frozenset({
ParameterStyle.QMARK,
ParameterStyle.POSITIONAL_PYFORMAT,
})
_EXPANDING_POSITIONAL_STYLES: Final[frozenset[ParameterStyle]] = frozenset({
ParameterStyle.QMARK,
ParameterStyle.POSITIONAL_PYFORMAT,
ParameterStyle.POSITIONAL_COLON,
})
def _placeholder_qmark(_: Any) -> str:
return "?"
def _placeholder_numeric(index: Any) -> str:
return f"${int(index) + 1}"
def _placeholder_named_colon(name: Any) -> str:
return f":{name}"
def _placeholder_positional_colon(index: Any) -> str:
return f":{int(index) + 1}"
def _placeholder_named_at(name: Any) -> str:
return f"@{name}"
def _placeholder_named_dollar(name: Any) -> str:
return f"${name}"
def _placeholder_named_pyformat(name: Any) -> str:
return f"%({name})s"
def _placeholder_positional_pyformat(_: Any) -> str:
return "%s"
def _ordered_parameter_info(param_info: "list[ParameterInfo]") -> "list[ParameterInfo]":
if len(param_info) < _ORDERED_PARAM_INFO_MIN_SIZE:
return param_info
previous_position = param_info[0].position
for param in param_info[1:]:
if param.position < previous_position:
return sorted(param_info, key=lambda item: item.position)
previous_position = param.position
return param_info
def _single_parameter_style(param_info: "list[ParameterInfo]") -> "ParameterStyle | None":
if not param_info:
return None
style = param_info[0].style
for param in param_info[1:]:
if param.style != style:
return None
return style
def _is_positional_style(style: "ParameterStyle") -> bool:
return style in _POSITIONAL_STYLES
def _parameter_lookup_key(param: "ParameterInfo") -> str:
if param.style in _OCCURRENCE_KEYED_STYLES:
return f"{param.placeholder_text}_{param.ordinal}"
return param.placeholder_text
def _named_parameter_name(param: "ParameterInfo") -> str:
param_name = param.name or f"param_{param.ordinal}"
if param_name.isdigit():
return f"param_{param.ordinal}"
return param_name
@mypyc_attr(allow_interpreted_subclasses=False)
class ParameterConverter:
"""Parameter style conversion helper."""
__slots__ = ("_placeholder_generators", "validator")
def __init__(self, validator: "ParameterValidator | None" = None) -> None:
self.validator = validator or ParameterValidator()
self._placeholder_generators: dict[ParameterStyle, Callable[[Any], str]] = {
ParameterStyle.QMARK: _placeholder_qmark,
ParameterStyle.NUMERIC: _placeholder_numeric,
ParameterStyle.NAMED_COLON: _placeholder_named_colon,
ParameterStyle.POSITIONAL_COLON: _placeholder_positional_colon,
ParameterStyle.NAMED_AT: _placeholder_named_at,
ParameterStyle.NAMED_DOLLAR: _placeholder_named_dollar,
ParameterStyle.NAMED_PYFORMAT: _placeholder_named_pyformat,
ParameterStyle.POSITIONAL_PYFORMAT: _placeholder_positional_pyformat,
}