sqlspec 0.12.1__py3-none-any.whl → 0.13.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of sqlspec might be problematic. Click here for more details.
- sqlspec/_sql.py +21 -180
- sqlspec/adapters/adbc/config.py +10 -12
- sqlspec/adapters/adbc/driver.py +120 -118
- sqlspec/adapters/aiosqlite/config.py +3 -3
- sqlspec/adapters/aiosqlite/driver.py +116 -141
- sqlspec/adapters/asyncmy/config.py +3 -4
- sqlspec/adapters/asyncmy/driver.py +123 -135
- sqlspec/adapters/asyncpg/config.py +3 -7
- sqlspec/adapters/asyncpg/driver.py +98 -140
- sqlspec/adapters/bigquery/config.py +4 -5
- sqlspec/adapters/bigquery/driver.py +231 -181
- sqlspec/adapters/duckdb/config.py +3 -6
- sqlspec/adapters/duckdb/driver.py +132 -124
- sqlspec/adapters/oracledb/config.py +6 -5
- sqlspec/adapters/oracledb/driver.py +242 -259
- sqlspec/adapters/psqlpy/config.py +3 -7
- sqlspec/adapters/psqlpy/driver.py +118 -93
- sqlspec/adapters/psycopg/config.py +34 -30
- sqlspec/adapters/psycopg/driver.py +342 -214
- sqlspec/adapters/sqlite/config.py +3 -3
- sqlspec/adapters/sqlite/driver.py +150 -104
- sqlspec/config.py +0 -4
- sqlspec/driver/_async.py +89 -98
- sqlspec/driver/_common.py +52 -17
- sqlspec/driver/_sync.py +81 -105
- sqlspec/driver/connection.py +207 -0
- sqlspec/driver/mixins/_csv_writer.py +91 -0
- sqlspec/driver/mixins/_pipeline.py +38 -49
- sqlspec/driver/mixins/_result_utils.py +27 -9
- sqlspec/driver/mixins/_storage.py +149 -216
- sqlspec/driver/mixins/_type_coercion.py +3 -4
- sqlspec/driver/parameters.py +138 -0
- sqlspec/exceptions.py +10 -2
- sqlspec/extensions/aiosql/adapter.py +0 -10
- sqlspec/extensions/litestar/handlers.py +0 -1
- sqlspec/extensions/litestar/plugin.py +0 -3
- sqlspec/extensions/litestar/providers.py +0 -14
- sqlspec/loader.py +31 -118
- sqlspec/protocols.py +542 -0
- sqlspec/service/__init__.py +3 -2
- sqlspec/service/_util.py +147 -0
- sqlspec/service/base.py +1116 -9
- sqlspec/statement/builder/__init__.py +42 -32
- sqlspec/statement/builder/_ddl_utils.py +0 -10
- sqlspec/statement/builder/_parsing_utils.py +10 -4
- sqlspec/statement/builder/base.py +70 -23
- sqlspec/statement/builder/column.py +283 -0
- sqlspec/statement/builder/ddl.py +102 -65
- sqlspec/statement/builder/delete.py +23 -7
- sqlspec/statement/builder/insert.py +29 -15
- sqlspec/statement/builder/merge.py +4 -4
- sqlspec/statement/builder/mixins/_aggregate_functions.py +113 -14
- sqlspec/statement/builder/mixins/_common_table_expr.py +0 -1
- sqlspec/statement/builder/mixins/_delete_from.py +1 -1
- sqlspec/statement/builder/mixins/_from.py +10 -8
- sqlspec/statement/builder/mixins/_group_by.py +0 -1
- sqlspec/statement/builder/mixins/_insert_from_select.py +0 -1
- sqlspec/statement/builder/mixins/_insert_values.py +0 -2
- sqlspec/statement/builder/mixins/_join.py +20 -13
- sqlspec/statement/builder/mixins/_limit_offset.py +3 -3
- sqlspec/statement/builder/mixins/_merge_clauses.py +3 -4
- sqlspec/statement/builder/mixins/_order_by.py +2 -2
- sqlspec/statement/builder/mixins/_pivot.py +4 -7
- sqlspec/statement/builder/mixins/_select_columns.py +6 -5
- sqlspec/statement/builder/mixins/_unpivot.py +6 -9
- sqlspec/statement/builder/mixins/_update_from.py +2 -1
- sqlspec/statement/builder/mixins/_update_set.py +11 -8
- sqlspec/statement/builder/mixins/_where.py +61 -34
- sqlspec/statement/builder/select.py +32 -17
- sqlspec/statement/builder/update.py +25 -11
- sqlspec/statement/filters.py +39 -14
- sqlspec/statement/parameter_manager.py +220 -0
- sqlspec/statement/parameters.py +210 -79
- sqlspec/statement/pipelines/__init__.py +166 -23
- sqlspec/statement/pipelines/analyzers/_analyzer.py +22 -25
- sqlspec/statement/pipelines/context.py +35 -39
- sqlspec/statement/pipelines/transformers/__init__.py +2 -3
- sqlspec/statement/pipelines/transformers/_expression_simplifier.py +19 -187
- sqlspec/statement/pipelines/transformers/_literal_parameterizer.py +667 -43
- sqlspec/statement/pipelines/transformers/_remove_comments_and_hints.py +76 -0
- sqlspec/statement/pipelines/validators/_dml_safety.py +33 -18
- sqlspec/statement/pipelines/validators/_parameter_style.py +87 -14
- sqlspec/statement/pipelines/validators/_performance.py +38 -23
- sqlspec/statement/pipelines/validators/_security.py +39 -62
- sqlspec/statement/result.py +37 -129
- sqlspec/statement/splitter.py +0 -12
- sqlspec/statement/sql.py +885 -379
- sqlspec/statement/sql_compiler.py +140 -0
- sqlspec/storage/__init__.py +10 -2
- sqlspec/storage/backends/fsspec.py +82 -35
- sqlspec/storage/backends/obstore.py +66 -49
- sqlspec/storage/capabilities.py +101 -0
- sqlspec/storage/registry.py +56 -83
- sqlspec/typing.py +6 -434
- sqlspec/utils/cached_property.py +25 -0
- sqlspec/utils/correlation.py +0 -2
- sqlspec/utils/logging.py +0 -6
- sqlspec/utils/sync_tools.py +0 -4
- sqlspec/utils/text.py +0 -5
- sqlspec/utils/type_guards.py +892 -0
- {sqlspec-0.12.1.dist-info → sqlspec-0.13.0.dist-info}/METADATA +1 -1
- sqlspec-0.13.0.dist-info/RECORD +150 -0
- sqlspec/statement/builder/protocols.py +0 -20
- sqlspec/statement/pipelines/base.py +0 -315
- sqlspec/statement/pipelines/result_types.py +0 -41
- sqlspec/statement/pipelines/transformers/_remove_comments.py +0 -66
- sqlspec/statement/pipelines/transformers/_remove_hints.py +0 -81
- sqlspec/statement/pipelines/validators/base.py +0 -67
- sqlspec/storage/protocol.py +0 -170
- sqlspec-0.12.1.dist-info/RECORD +0 -145
- {sqlspec-0.12.1.dist-info → sqlspec-0.13.0.dist-info}/WHEEL +0 -0
- {sqlspec-0.12.1.dist-info → sqlspec-0.13.0.dist-info}/licenses/LICENSE +0 -0
- {sqlspec-0.12.1.dist-info → sqlspec-0.13.0.dist-info}/licenses/NOTICE +0 -0
sqlspec/statement/filters.py
CHANGED
|
@@ -17,6 +17,7 @@ if TYPE_CHECKING:
|
|
|
17
17
|
__all__ = (
|
|
18
18
|
"AnyCollectionFilter",
|
|
19
19
|
"BeforeAfterFilter",
|
|
20
|
+
"FilterTypeT",
|
|
20
21
|
"FilterTypes",
|
|
21
22
|
"InAnyFilter",
|
|
22
23
|
"InCollectionFilter",
|
|
@@ -112,9 +113,7 @@ class BeforeAfterFilter(StatementFilter):
|
|
|
112
113
|
final_condition = conditions[0]
|
|
113
114
|
for cond in conditions[1:]:
|
|
114
115
|
final_condition = exp.And(this=final_condition, expression=cond)
|
|
115
|
-
# Use the SQL object's where method which handles all cases
|
|
116
116
|
result = statement.where(final_condition)
|
|
117
|
-
# Add the filter's parameters to the result
|
|
118
117
|
_, named_params = self.extract_parameters()
|
|
119
118
|
for name, value in named_params.items():
|
|
120
119
|
result = result.add_named_parameter(name, value)
|
|
@@ -171,7 +170,6 @@ class OnBeforeAfterFilter(StatementFilter):
|
|
|
171
170
|
for cond in conditions[1:]:
|
|
172
171
|
final_condition = exp.And(this=final_condition, expression=cond)
|
|
173
172
|
result = statement.where(final_condition)
|
|
174
|
-
# Add the filter's parameters to the result
|
|
175
173
|
_, named_params = self.extract_parameters()
|
|
176
174
|
for name, value in named_params.items():
|
|
177
175
|
result = result.add_named_parameter(name, value)
|
|
@@ -229,7 +227,6 @@ class InCollectionFilter(InAnyFilter[T]):
|
|
|
229
227
|
]
|
|
230
228
|
|
|
231
229
|
result = statement.where(exp.In(this=exp.column(self.field_name), expressions=placeholder_expressions))
|
|
232
|
-
# Add the filter's parameters to the result
|
|
233
230
|
_, named_params = self.extract_parameters()
|
|
234
231
|
for name, value in named_params.items():
|
|
235
232
|
result = result.add_named_parameter(name, value)
|
|
@@ -273,7 +270,6 @@ class NotInCollectionFilter(InAnyFilter[T]):
|
|
|
273
270
|
result = statement.where(
|
|
274
271
|
exp.Not(this=exp.In(this=exp.column(self.field_name), expressions=placeholder_expressions))
|
|
275
272
|
)
|
|
276
|
-
# Add the filter's parameters to the result
|
|
277
273
|
_, named_params = self.extract_parameters()
|
|
278
274
|
for name, value in named_params.items():
|
|
279
275
|
result = result.add_named_parameter(name, value)
|
|
@@ -323,7 +319,6 @@ class AnyCollectionFilter(InAnyFilter[T]):
|
|
|
323
319
|
array_expr = exp.Array(expressions=placeholder_expressions)
|
|
324
320
|
# Generates SQL like: self.field_name = ANY(ARRAY[?, ?, ...])
|
|
325
321
|
result = statement.where(exp.EQ(this=exp.column(self.field_name), expression=exp.Any(this=array_expr)))
|
|
326
|
-
# Add the filter's parameters to the result
|
|
327
322
|
_, named_params = self.extract_parameters()
|
|
328
323
|
for name, value in named_params.items():
|
|
329
324
|
result = result.add_named_parameter(name, value)
|
|
@@ -372,7 +367,6 @@ class NotAnyCollectionFilter(InAnyFilter[T]):
|
|
|
372
367
|
# Generates SQL like: NOT (self.field_name = ANY(ARRAY[?, ?, ...]))
|
|
373
368
|
condition = exp.EQ(this=exp.column(self.field_name), expression=exp.Any(this=array_expr))
|
|
374
369
|
result = statement.where(exp.Not(this=condition))
|
|
375
|
-
# Add the filter's parameters to the result
|
|
376
370
|
_, named_params = self.extract_parameters()
|
|
377
371
|
for name, value in named_params.items():
|
|
378
372
|
result = result.add_named_parameter(name, value)
|
|
@@ -396,13 +390,48 @@ class LimitOffsetFilter(PaginationFilter):
|
|
|
396
390
|
offset: int
|
|
397
391
|
"""Value for ``OFFSET`` clause of query."""
|
|
398
392
|
|
|
393
|
+
def __post_init__(self) -> None:
|
|
394
|
+
"""Initialize parameter names."""
|
|
395
|
+
# Generate unique parameter names to avoid conflicts
|
|
396
|
+
import uuid
|
|
397
|
+
|
|
398
|
+
unique_suffix = str(uuid.uuid4()).replace("-", "")[:8]
|
|
399
|
+
self._limit_param_name = f"limit_{unique_suffix}"
|
|
400
|
+
self._offset_param_name = f"offset_{unique_suffix}"
|
|
401
|
+
|
|
399
402
|
def extract_parameters(self) -> tuple[list[Any], dict[str, Any]]:
|
|
400
403
|
"""Extract filter parameters."""
|
|
401
|
-
|
|
402
|
-
return [], {"limit": self.limit, "offset": self.offset}
|
|
404
|
+
return [], {self._limit_param_name: self.limit, self._offset_param_name: self.offset}
|
|
403
405
|
|
|
404
406
|
def append_to_statement(self, statement: "SQL") -> "SQL":
|
|
405
|
-
|
|
407
|
+
# Create limit and offset expressions using our pre-generated parameter names
|
|
408
|
+
from sqlglot import exp
|
|
409
|
+
|
|
410
|
+
limit_placeholder = exp.Placeholder(this=self._limit_param_name)
|
|
411
|
+
offset_placeholder = exp.Placeholder(this=self._offset_param_name)
|
|
412
|
+
|
|
413
|
+
# Apply LIMIT and OFFSET to the statement
|
|
414
|
+
result = statement
|
|
415
|
+
|
|
416
|
+
# Check if the statement supports LIMIT directly
|
|
417
|
+
if isinstance(result._statement, exp.Select):
|
|
418
|
+
new_statement = result._statement.limit(limit_placeholder)
|
|
419
|
+
else:
|
|
420
|
+
# Wrap in a SELECT if the statement doesn't support LIMIT directly
|
|
421
|
+
new_statement = exp.Select().from_(result._statement).limit(limit_placeholder)
|
|
422
|
+
|
|
423
|
+
# Add OFFSET
|
|
424
|
+
if isinstance(new_statement, exp.Select):
|
|
425
|
+
new_statement = new_statement.offset(offset_placeholder)
|
|
426
|
+
|
|
427
|
+
result = result.copy(statement=new_statement)
|
|
428
|
+
|
|
429
|
+
# Add the parameters to the result
|
|
430
|
+
_, named_params = self.extract_parameters()
|
|
431
|
+
for name, value in named_params.items():
|
|
432
|
+
result = result.add_named_parameter(name, value)
|
|
433
|
+
|
|
434
|
+
return result
|
|
406
435
|
|
|
407
436
|
|
|
408
437
|
@dataclass
|
|
@@ -450,7 +479,6 @@ class SearchFilter(StatementFilter):
|
|
|
450
479
|
if isinstance(self.field_name, str):
|
|
451
480
|
self._param_name = f"{self.field_name}_search"
|
|
452
481
|
else:
|
|
453
|
-
# For multiple fields, use a generic search parameter name
|
|
454
482
|
self._param_name = "search_value"
|
|
455
483
|
|
|
456
484
|
def extract_parameters(self) -> tuple[list[Any], dict[str, Any]]:
|
|
@@ -484,7 +512,6 @@ class SearchFilter(StatementFilter):
|
|
|
484
512
|
final_condition = exp.Or(this=final_condition, expression=cond)
|
|
485
513
|
result = statement.where(final_condition)
|
|
486
514
|
|
|
487
|
-
# Add the filter's parameters to the result
|
|
488
515
|
_, named_params = self.extract_parameters()
|
|
489
516
|
for name, value in named_params.items():
|
|
490
517
|
result = result.add_named_parameter(name, value)
|
|
@@ -502,7 +529,6 @@ class NotInSearchFilter(SearchFilter):
|
|
|
502
529
|
if isinstance(self.field_name, str):
|
|
503
530
|
self._param_name = f"{self.field_name}_not_search"
|
|
504
531
|
else:
|
|
505
|
-
# For multiple fields, use a generic search parameter name
|
|
506
532
|
self._param_name = "not_search_value"
|
|
507
533
|
|
|
508
534
|
def extract_parameters(self) -> tuple[list[Any], dict[str, Any]]:
|
|
@@ -536,7 +562,6 @@ class NotInSearchFilter(SearchFilter):
|
|
|
536
562
|
final_condition = exp.And(this=final_condition, expression=cond)
|
|
537
563
|
result = statement.where(final_condition)
|
|
538
564
|
|
|
539
|
-
# Add the filter's parameters to the result
|
|
540
565
|
_, named_params = self.extract_parameters()
|
|
541
566
|
for name, value in named_params.items():
|
|
542
567
|
result = result.add_named_parameter(name, value)
|
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
"""Parameter management for SQL objects."""
|
|
2
|
+
|
|
3
|
+
from typing import Any, Optional
|
|
4
|
+
|
|
5
|
+
from sqlspec.statement.filters import StatementFilter
|
|
6
|
+
from sqlspec.statement.parameters import ParameterConverter, ParameterStyle
|
|
7
|
+
|
|
8
|
+
__all__ = ("ParameterManager",)
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class ParameterManager:
|
|
12
|
+
"""Manages parameter processing and conversion for SQL objects."""
|
|
13
|
+
|
|
14
|
+
def __init__(
|
|
15
|
+
self,
|
|
16
|
+
parameters: "Optional[tuple[Any, ...]]" = None,
|
|
17
|
+
kwargs: "Optional[dict[str, Any]]" = None,
|
|
18
|
+
converter: "Optional[ParameterConverter]" = None,
|
|
19
|
+
) -> None:
|
|
20
|
+
self.converter = converter or ParameterConverter()
|
|
21
|
+
self.named_params: dict[str, Any] = {}
|
|
22
|
+
self.filters: list[StatementFilter] = []
|
|
23
|
+
self._positional_parameters = parameters or ()
|
|
24
|
+
self._named_parameters = kwargs or {}
|
|
25
|
+
if parameters:
|
|
26
|
+
for i, param in enumerate(parameters):
|
|
27
|
+
self.named_params[f"pos_param_{i}"] = param
|
|
28
|
+
if kwargs:
|
|
29
|
+
self.process_parameters(**kwargs)
|
|
30
|
+
|
|
31
|
+
def process_parameters(self, *parameters: Any, **kwargs: Any) -> None:
|
|
32
|
+
"""Process positional parameters and kwargs into named parameters."""
|
|
33
|
+
for i, param in enumerate(parameters):
|
|
34
|
+
if isinstance(param, StatementFilter):
|
|
35
|
+
self.filters.append(param)
|
|
36
|
+
pos_params, named_params = param.extract_parameters()
|
|
37
|
+
for j, p_param in enumerate(pos_params):
|
|
38
|
+
self.named_params[f"pos_param_{i}_{j}"] = p_param
|
|
39
|
+
self.named_params.update(named_params)
|
|
40
|
+
elif isinstance(param, (list, tuple)):
|
|
41
|
+
for j, p_param in enumerate(param):
|
|
42
|
+
self.named_params[f"pos_param_{i}_{j}"] = p_param
|
|
43
|
+
elif isinstance(param, dict):
|
|
44
|
+
self.named_params.update(param)
|
|
45
|
+
else:
|
|
46
|
+
self.named_params[f"pos_param_{i}"] = param
|
|
47
|
+
if "parameters" in kwargs:
|
|
48
|
+
param_value = kwargs.pop("parameters")
|
|
49
|
+
if isinstance(param_value, (list, tuple)):
|
|
50
|
+
for i, p_param in enumerate(param_value):
|
|
51
|
+
self.named_params[f"kw_pos_param_{i}"] = p_param
|
|
52
|
+
elif isinstance(param_value, dict):
|
|
53
|
+
self.named_params.update(param_value)
|
|
54
|
+
else:
|
|
55
|
+
self.named_params["kw_single_param"] = param_value
|
|
56
|
+
|
|
57
|
+
for key, value in kwargs.items():
|
|
58
|
+
if not key.startswith("_"):
|
|
59
|
+
self.named_params[key] = value
|
|
60
|
+
|
|
61
|
+
def get_compiled_parameters(self, param_info: list[Any], target_style: ParameterStyle) -> Any:
|
|
62
|
+
"""Compile internal named parameters into the target style."""
|
|
63
|
+
if target_style == ParameterStyle.POSITIONAL_COLON:
|
|
64
|
+
return self._convert_to_positional_colon_format(self.named_params, param_info)
|
|
65
|
+
if target_style in {ParameterStyle.QMARK, ParameterStyle.NUMERIC, ParameterStyle.POSITIONAL_PYFORMAT}:
|
|
66
|
+
return self._convert_to_positional_format(self.named_params, param_info)
|
|
67
|
+
if target_style == ParameterStyle.NAMED_COLON:
|
|
68
|
+
return self._convert_to_named_colon_format(self.named_params, param_info)
|
|
69
|
+
if target_style == ParameterStyle.NAMED_PYFORMAT:
|
|
70
|
+
return self._convert_to_named_pyformat_format(self.named_params, param_info)
|
|
71
|
+
return self.named_params
|
|
72
|
+
|
|
73
|
+
def copy_from(self, other: "ParameterManager") -> None:
|
|
74
|
+
"""Copy parameters and filters from another parameter manager."""
|
|
75
|
+
self.named_params.update(other.named_params)
|
|
76
|
+
self.filters.extend(other.filters)
|
|
77
|
+
|
|
78
|
+
def add_named_parameter(self, name: str, value: Any) -> None:
|
|
79
|
+
"""Add a named parameter."""
|
|
80
|
+
self.named_params[name] = value
|
|
81
|
+
self._named_parameters[name] = value
|
|
82
|
+
|
|
83
|
+
def get_unique_parameter_name(
|
|
84
|
+
self, base_name: str, namespace: Optional[str] = None, preserve_original: bool = False
|
|
85
|
+
) -> str:
|
|
86
|
+
"""Generate a unique parameter name."""
|
|
87
|
+
all_param_names = set(self.named_params.keys())
|
|
88
|
+
candidate = f"{namespace}_{base_name}" if namespace else base_name
|
|
89
|
+
|
|
90
|
+
if preserve_original and candidate not in all_param_names:
|
|
91
|
+
return candidate
|
|
92
|
+
|
|
93
|
+
if candidate not in all_param_names:
|
|
94
|
+
return candidate
|
|
95
|
+
|
|
96
|
+
counter = 1
|
|
97
|
+
while True:
|
|
98
|
+
new_candidate = f"{candidate}_{counter}"
|
|
99
|
+
if new_candidate not in all_param_names:
|
|
100
|
+
return new_candidate
|
|
101
|
+
counter += 1
|
|
102
|
+
|
|
103
|
+
def _convert_to_positional_format(self, params: dict[str, Any], param_info: list[Any]) -> list[Any]:
|
|
104
|
+
"""Convert to positional format (list).
|
|
105
|
+
|
|
106
|
+
This is used for parameter styles like QMARK (?), NUMERIC ($1), and POSITIONAL_PYFORMAT (%s).
|
|
107
|
+
"""
|
|
108
|
+
if not param_info:
|
|
109
|
+
return list(params.values())
|
|
110
|
+
|
|
111
|
+
result = []
|
|
112
|
+
for i, info in enumerate(param_info):
|
|
113
|
+
if info.name and info.name in params:
|
|
114
|
+
result.append(params[info.name])
|
|
115
|
+
elif f"pos_param_{i}" in params:
|
|
116
|
+
result.append(params[f"pos_param_{i}"])
|
|
117
|
+
elif f"kw_pos_param_{i}" in params:
|
|
118
|
+
result.append(params[f"kw_pos_param_{i}"])
|
|
119
|
+
elif f"arg_{i}" in params:
|
|
120
|
+
result.append(params[f"arg_{i}"])
|
|
121
|
+
else:
|
|
122
|
+
result.append(None)
|
|
123
|
+
return result
|
|
124
|
+
|
|
125
|
+
def _convert_to_positional_colon_format(self, params: dict[str, Any], param_info: list[Any]) -> dict[str, Any]:
|
|
126
|
+
"""Convert to positional colon format (Oracle :1, :2 style).
|
|
127
|
+
|
|
128
|
+
Oracle's positional parameters are 1-indexed and are accessed by string keys.
|
|
129
|
+
Returns a dict with string keys "1", "2", etc.
|
|
130
|
+
"""
|
|
131
|
+
digit_keys = {k: v for k, v in params.items() if k.isdigit()}
|
|
132
|
+
if (
|
|
133
|
+
digit_keys
|
|
134
|
+
and param_info
|
|
135
|
+
and all(hasattr(info, "style") and info.style == ParameterStyle.POSITIONAL_COLON for info in param_info)
|
|
136
|
+
):
|
|
137
|
+
required_nums = {info.name for info in param_info if hasattr(info, "name")}
|
|
138
|
+
if required_nums.issubset(digit_keys.keys()):
|
|
139
|
+
return digit_keys
|
|
140
|
+
|
|
141
|
+
# This handles cases like :0, :1, :3 (with gaps) where we should preserve the actual numbers
|
|
142
|
+
if param_info and all(
|
|
143
|
+
hasattr(info, "style")
|
|
144
|
+
and info.style == ParameterStyle.POSITIONAL_COLON
|
|
145
|
+
and hasattr(info, "name")
|
|
146
|
+
and info.name.isdigit()
|
|
147
|
+
for info in param_info
|
|
148
|
+
):
|
|
149
|
+
result = {}
|
|
150
|
+
positional_values = self._convert_to_positional_format(params, param_info)
|
|
151
|
+
for i, value in enumerate(positional_values):
|
|
152
|
+
if value is not None:
|
|
153
|
+
numeric_key = str(i)
|
|
154
|
+
if any(info.name == numeric_key for info in param_info):
|
|
155
|
+
result[numeric_key] = value
|
|
156
|
+
else:
|
|
157
|
+
result[str(i + 1)] = value
|
|
158
|
+
|
|
159
|
+
return result
|
|
160
|
+
|
|
161
|
+
positional_list = self._convert_to_positional_format(params, param_info)
|
|
162
|
+
return {str(i + 1): value for i, value in enumerate(positional_list)}
|
|
163
|
+
|
|
164
|
+
def _convert_to_named_colon_format(self, params: dict[str, Any], param_info: list[Any]) -> dict[str, Any]:
|
|
165
|
+
"""Convert to named colon format (:name style).
|
|
166
|
+
|
|
167
|
+
This format expects a dictionary with parameter names as keys.
|
|
168
|
+
We need to ensure all placeholders have corresponding values.
|
|
169
|
+
"""
|
|
170
|
+
result = {}
|
|
171
|
+
for info in param_info:
|
|
172
|
+
if info.name:
|
|
173
|
+
if info.name in params:
|
|
174
|
+
result[info.name] = params[info.name]
|
|
175
|
+
else:
|
|
176
|
+
for key, value in params.items():
|
|
177
|
+
if key.endswith(f"_{info.ordinal}") or key == f"arg_{info.ordinal}":
|
|
178
|
+
result[info.name] = value
|
|
179
|
+
break
|
|
180
|
+
else:
|
|
181
|
+
gen_name = f"arg_{info.ordinal}"
|
|
182
|
+
if f"pos_param_{info.ordinal}" in params:
|
|
183
|
+
result[gen_name] = params[f"pos_param_{info.ordinal}"]
|
|
184
|
+
elif f"kw_pos_param_{info.ordinal}" in params:
|
|
185
|
+
result[gen_name] = params[f"kw_pos_param_{info.ordinal}"]
|
|
186
|
+
elif gen_name in params:
|
|
187
|
+
result[gen_name] = params[gen_name]
|
|
188
|
+
for key, value in params.items():
|
|
189
|
+
if not key.startswith(("pos_param_", "kw_pos_param_", "arg_")) and key not in result:
|
|
190
|
+
result[key] = value
|
|
191
|
+
|
|
192
|
+
return result
|
|
193
|
+
|
|
194
|
+
def _convert_to_named_pyformat_format(self, params: dict[str, Any], param_info: list[Any]) -> dict[str, Any]:
|
|
195
|
+
"""Convert to named pyformat format (%(name)s style).
|
|
196
|
+
|
|
197
|
+
This is similar to named colon format but uses Python string formatting syntax.
|
|
198
|
+
"""
|
|
199
|
+
return self._convert_to_named_colon_format(params, param_info)
|
|
200
|
+
|
|
201
|
+
@property
|
|
202
|
+
def positional_parameters(self) -> tuple[Any, ...]:
|
|
203
|
+
"""Get the original positional parameters."""
|
|
204
|
+
return self._positional_parameters
|
|
205
|
+
|
|
206
|
+
@property
|
|
207
|
+
def named_parameters(self) -> dict[str, Any]:
|
|
208
|
+
"""Get the combined named parameters."""
|
|
209
|
+
return self.named_params
|
|
210
|
+
|
|
211
|
+
def get_parameter_info(self) -> tuple[tuple[Any, ...], dict[str, Any]]:
|
|
212
|
+
"""Get parameter information in the legacy format.
|
|
213
|
+
|
|
214
|
+
This method provides backward compatibility for code expecting
|
|
215
|
+
the old parameter_info format.
|
|
216
|
+
|
|
217
|
+
Returns:
|
|
218
|
+
Tuple of (positional_parameters, named_parameters)
|
|
219
|
+
"""
|
|
220
|
+
return (self._positional_parameters, self._named_parameters)
|