Skip to content

Commit e26375f

Browse files
authored
feat(driver): add fetch* method aliases compatibility (#275)
Add fetch* method aliases to both sync and async driver base classes, providing an alternative naming convention for users familiar with asyncpg's API. Both select* (primary) and fetch* (alias) conventions are fully supported with complete type safety.
1 parent be469fa commit e26375f

File tree

4 files changed

+1059
-0
lines changed

4 files changed

+1059
-0
lines changed

docs/usage/drivers_and_querying.rst

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -388,6 +388,52 @@ Execute a SELECT query returning a single scalar value.
388388
:dedent: 4
389389
:caption: `select_value`
390390

391+
asyncpg-Compatible Method Aliases (fetch*)
392+
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
393+
394+
For users transitioning from asyncpg or preferring its naming convention, SQLSpec provides ``fetch*`` aliases for all ``select*`` methods. Both naming styles are fully supported and produce identical results.
395+
396+
.. list-table:: Method Aliases
397+
:header-rows: 1
398+
:widths: 40 60
399+
400+
* - Primary Method
401+
- asyncpg-Compatible Alias
402+
* - ``select()``
403+
- ``fetch()``
404+
* - ``select_one()``
405+
- ``fetch_one()``
406+
* - ``select_one_or_none()``
407+
- ``fetch_one_or_none()``
408+
* - ``select_value()``
409+
- ``fetch_value()``
410+
* - ``select_value_or_none()``
411+
- ``fetch_value_or_none()``
412+
* - ``select_to_arrow()``
413+
- ``fetch_to_arrow()``
414+
* - ``select_with_total()``
415+
- ``fetch_with_total()``
416+
417+
**Example using fetch() instead of select():**
418+
419+
.. code-block:: python
420+
421+
# Both styles work identically
422+
async with spec.provide_session(config) as session:
423+
# Primary naming (recommended in docs)
424+
users = await session.select("SELECT * FROM users WHERE age > ?", 18)
425+
426+
# asyncpg-compatible naming (identical behavior)
427+
users = await session.fetch("SELECT * FROM users WHERE age > ?", 18)
428+
429+
# fetch_one() is equivalent to select_one()
430+
user = await session.fetch_one("SELECT * FROM users WHERE id = ?", 1)
431+
432+
# fetch_value() is equivalent to select_value()
433+
count = await session.fetch_value("SELECT COUNT(*) FROM users")
434+
435+
**Note:** Both naming conventions are fully supported and will not be deprecated. Choose the style that feels most natural for your codebase. The SQLSpec documentation primarily uses ``select*`` methods, but ``fetch*`` aliases are equally valid.
436+
391437
Working with Results
392438
--------------------
393439

sqlspec/driver/_async.py

Lines changed: 254 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -398,6 +398,51 @@ async def select_one(
398398
except ValueError as error:
399399
handle_single_row_error(error)
400400

401+
@overload
402+
async def fetch_one(
403+
self,
404+
statement: "Statement | QueryBuilder",
405+
/,
406+
*parameters: "StatementParameters | StatementFilter",
407+
schema_type: "type[SchemaT]",
408+
statement_config: "StatementConfig | None" = None,
409+
**kwargs: Any,
410+
) -> "SchemaT": ...
411+
412+
@overload
413+
async def fetch_one(
414+
self,
415+
statement: "Statement | QueryBuilder",
416+
/,
417+
*parameters: "StatementParameters | StatementFilter",
418+
schema_type: None = None,
419+
statement_config: "StatementConfig | None" = None,
420+
**kwargs: Any,
421+
) -> "dict[str, Any]": ...
422+
423+
async def fetch_one(
424+
self,
425+
statement: "Statement | QueryBuilder",
426+
/,
427+
*parameters: "StatementParameters | StatementFilter",
428+
schema_type: "type[SchemaT] | None" = None,
429+
statement_config: "StatementConfig | None" = None,
430+
**kwargs: Any,
431+
) -> "SchemaT | dict[str, Any]":
432+
"""Execute a select statement and return exactly one row.
433+
434+
This is an alias for :meth:`select_one` provided for users familiar
435+
with asyncpg's fetch_one() naming convention.
436+
437+
Raises an exception if no rows or more than one row is returned.
438+
439+
See Also:
440+
select_one(): Primary method with identical behavior
441+
"""
442+
return await self.select_one(
443+
statement, *parameters, schema_type=schema_type, statement_config=statement_config, **kwargs
444+
)
445+
401446
@overload
402447
async def select_one_or_none(
403448
self,
@@ -437,6 +482,52 @@ async def select_one_or_none(
437482
result = await self.execute(statement, *parameters, statement_config=statement_config, **kwargs)
438483
return result.one_or_none(schema_type=schema_type)
439484

485+
@overload
486+
async def fetch_one_or_none(
487+
self,
488+
statement: "Statement | QueryBuilder",
489+
/,
490+
*parameters: "StatementParameters | StatementFilter",
491+
schema_type: "type[SchemaT]",
492+
statement_config: "StatementConfig | None" = None,
493+
**kwargs: Any,
494+
) -> "SchemaT | None": ...
495+
496+
@overload
497+
async def fetch_one_or_none(
498+
self,
499+
statement: "Statement | QueryBuilder",
500+
/,
501+
*parameters: "StatementParameters | StatementFilter",
502+
schema_type: None = None,
503+
statement_config: "StatementConfig | None" = None,
504+
**kwargs: Any,
505+
) -> "dict[str, Any] | None": ...
506+
507+
async def fetch_one_or_none(
508+
self,
509+
statement: "Statement | QueryBuilder",
510+
/,
511+
*parameters: "StatementParameters | StatementFilter",
512+
schema_type: "type[SchemaT] | None" = None,
513+
statement_config: "StatementConfig | None" = None,
514+
**kwargs: Any,
515+
) -> "SchemaT | dict[str, Any] | None":
516+
"""Execute a select statement and return at most one row.
517+
518+
This is an alias for :meth:`select_one_or_none` provided for users familiar
519+
with asyncpg's fetch_one_or_none() naming convention.
520+
521+
Returns None if no rows are found.
522+
Raises an exception if more than one row is returned.
523+
524+
See Also:
525+
select_one_or_none(): Primary method with identical behavior
526+
"""
527+
return await self.select_one_or_none(
528+
statement, *parameters, schema_type=schema_type, statement_config=statement_config, **kwargs
529+
)
530+
440531
@overload
441532
async def select(
442533
self,
@@ -472,6 +563,49 @@ async def select(
472563
result = await self.execute(statement, *parameters, statement_config=statement_config, **kwargs)
473564
return result.get_data(schema_type=schema_type)
474565

566+
@overload
567+
async def fetch(
568+
self,
569+
statement: "Statement | QueryBuilder",
570+
/,
571+
*parameters: "StatementParameters | StatementFilter",
572+
schema_type: "type[SchemaT]",
573+
statement_config: "StatementConfig | None" = None,
574+
**kwargs: Any,
575+
) -> "list[SchemaT]": ...
576+
577+
@overload
578+
async def fetch(
579+
self,
580+
statement: "Statement | QueryBuilder",
581+
/,
582+
*parameters: "StatementParameters | StatementFilter",
583+
schema_type: None = None,
584+
statement_config: "StatementConfig | None" = None,
585+
**kwargs: Any,
586+
) -> "list[dict[str, Any]]": ...
587+
588+
async def fetch(
589+
self,
590+
statement: "Statement | QueryBuilder",
591+
/,
592+
*parameters: "StatementParameters | StatementFilter",
593+
schema_type: "type[SchemaT] | None" = None,
594+
statement_config: "StatementConfig | None" = None,
595+
**kwargs: Any,
596+
) -> "list[SchemaT] | list[dict[str, Any]]":
597+
"""Execute a select statement and return all rows.
598+
599+
This is an alias for :meth:`select` provided for users familiar
600+
with asyncpg's fetch() naming convention.
601+
602+
See Also:
603+
select(): Primary method with identical behavior
604+
"""
605+
return await self.select(
606+
statement, *parameters, schema_type=schema_type, statement_config=statement_config, **kwargs
607+
)
608+
475609
async def select_to_arrow(
476610
self,
477611
statement: "Statement | QueryBuilder",
@@ -549,6 +683,37 @@ async def select_to_arrow(
549683
metadata=result.metadata,
550684
)
551685

686+
async def fetch_to_arrow(
687+
self,
688+
statement: "Statement | QueryBuilder",
689+
/,
690+
*parameters: "StatementParameters | StatementFilter",
691+
statement_config: "StatementConfig | None" = None,
692+
return_format: "ArrowReturnFormat" = "table",
693+
native_only: bool = False,
694+
batch_size: int | None = None,
695+
arrow_schema: Any = None,
696+
**kwargs: Any,
697+
) -> "ArrowResult":
698+
"""Execute query and return results as Apache Arrow format (async).
699+
700+
This is an alias for :meth:`select_to_arrow` provided for users familiar
701+
with asyncpg's fetch() naming convention.
702+
703+
See Also:
704+
select_to_arrow(): Primary method with identical behavior and full documentation
705+
"""
706+
return await self.select_to_arrow(
707+
statement,
708+
*parameters,
709+
statement_config=statement_config,
710+
return_format=return_format,
711+
native_only=native_only,
712+
batch_size=batch_size,
713+
arrow_schema=arrow_schema,
714+
**kwargs,
715+
)
716+
552717
async def select_value(
553718
self,
554719
statement: "Statement | QueryBuilder",
@@ -568,6 +733,27 @@ async def select_value(
568733
except ValueError as error:
569734
handle_single_row_error(error)
570735

736+
async def fetch_value(
737+
self,
738+
statement: "Statement | QueryBuilder",
739+
/,
740+
*parameters: "StatementParameters | StatementFilter",
741+
statement_config: "StatementConfig | None" = None,
742+
**kwargs: Any,
743+
) -> Any:
744+
"""Execute a select statement and return a single scalar value.
745+
746+
This is an alias for :meth:`select_value` provided for users familiar
747+
with asyncpg's fetch_value() naming convention.
748+
749+
Expects exactly one row with one column.
750+
Raises an exception if no rows or more than one row/column is returned.
751+
752+
See Also:
753+
select_value(): Primary method with identical behavior
754+
"""
755+
return await self.select_value(statement, *parameters, statement_config=statement_config, **kwargs)
756+
571757
async def select_value_or_none(
572758
self,
573759
statement: "Statement | QueryBuilder",
@@ -585,6 +771,28 @@ async def select_value_or_none(
585771
result = await self.execute(statement, *parameters, statement_config=statement_config, **kwargs)
586772
return result.scalar_or_none()
587773

774+
async def fetch_value_or_none(
775+
self,
776+
statement: "Statement | QueryBuilder",
777+
/,
778+
*parameters: "StatementParameters | StatementFilter",
779+
statement_config: "StatementConfig | None" = None,
780+
**kwargs: Any,
781+
) -> Any:
782+
"""Execute a select statement and return a single scalar value or None.
783+
784+
This is an alias for :meth:`select_value_or_none` provided for users familiar
785+
with asyncpg's fetch_value_or_none() naming convention.
786+
787+
Returns None if no rows are found.
788+
Expects at most one row with one column.
789+
Raises an exception if more than one row is returned.
790+
791+
See Also:
792+
select_value_or_none(): Primary method with identical behavior
793+
"""
794+
return await self.select_value_or_none(statement, *parameters, statement_config=statement_config, **kwargs)
795+
588796
@overload
589797
async def select_with_total(
590798
self,
@@ -641,6 +849,52 @@ async def select_with_total(
641849

642850
return (select_result.get_data(schema_type=schema_type), count_result.scalar())
643851

852+
@overload
853+
async def fetch_with_total(
854+
self,
855+
statement: "Statement | QueryBuilder",
856+
/,
857+
*parameters: "StatementParameters | StatementFilter",
858+
schema_type: "type[SchemaT]",
859+
statement_config: "StatementConfig | None" = None,
860+
**kwargs: Any,
861+
) -> "tuple[list[SchemaT], int]": ...
862+
863+
@overload
864+
async def fetch_with_total(
865+
self,
866+
statement: "Statement | QueryBuilder",
867+
/,
868+
*parameters: "StatementParameters | StatementFilter",
869+
schema_type: None = None,
870+
statement_config: "StatementConfig | None" = None,
871+
**kwargs: Any,
872+
) -> "tuple[list[dict[str, Any]], int]": ...
873+
874+
async def fetch_with_total(
875+
self,
876+
statement: "Statement | QueryBuilder",
877+
/,
878+
*parameters: "StatementParameters | StatementFilter",
879+
schema_type: "type[SchemaT] | None" = None,
880+
statement_config: "StatementConfig | None" = None,
881+
**kwargs: Any,
882+
) -> "tuple[list[SchemaT] | list[dict[str, Any]], int]":
883+
"""Execute a select statement and return both the data and total count.
884+
885+
This is an alias for :meth:`select_with_total` provided for users familiar
886+
with asyncpg's fetch() naming convention.
887+
888+
This method is designed for pagination scenarios where you need both
889+
the current page of data and the total number of rows that match the query.
890+
891+
See Also:
892+
select_with_total(): Primary method with identical behavior and full documentation
893+
"""
894+
return await self.select_with_total(
895+
statement, *parameters, schema_type=schema_type, statement_config=statement_config, **kwargs
896+
)
897+
644898
async def _execute_stack_operation(self, operation: "StackOperation") -> "SQLResult | ArrowResult | None":
645899
kwargs = dict(operation.keyword_arguments) if operation.keyword_arguments else {}
646900

0 commit comments

Comments
 (0)