sqlspec 0.27.0__py3-none-any.whl → 0.28.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.

Files changed (64) hide show
  1. sqlspec/_typing.py +93 -0
  2. sqlspec/adapters/adbc/adk/store.py +21 -11
  3. sqlspec/adapters/adbc/data_dictionary.py +27 -5
  4. sqlspec/adapters/adbc/driver.py +83 -14
  5. sqlspec/adapters/aiosqlite/adk/store.py +27 -18
  6. sqlspec/adapters/asyncmy/adk/store.py +26 -16
  7. sqlspec/adapters/asyncpg/adk/store.py +26 -16
  8. sqlspec/adapters/asyncpg/data_dictionary.py +24 -17
  9. sqlspec/adapters/bigquery/adk/store.py +30 -21
  10. sqlspec/adapters/bigquery/config.py +11 -0
  11. sqlspec/adapters/bigquery/driver.py +138 -1
  12. sqlspec/adapters/duckdb/adk/store.py +21 -11
  13. sqlspec/adapters/duckdb/driver.py +87 -1
  14. sqlspec/adapters/oracledb/adk/store.py +89 -206
  15. sqlspec/adapters/oracledb/driver.py +183 -2
  16. sqlspec/adapters/oracledb/litestar/store.py +22 -24
  17. sqlspec/adapters/psqlpy/adk/store.py +28 -27
  18. sqlspec/adapters/psqlpy/data_dictionary.py +24 -17
  19. sqlspec/adapters/psqlpy/driver.py +7 -10
  20. sqlspec/adapters/psycopg/adk/store.py +51 -33
  21. sqlspec/adapters/psycopg/data_dictionary.py +48 -34
  22. sqlspec/adapters/sqlite/adk/store.py +29 -19
  23. sqlspec/config.py +100 -2
  24. sqlspec/core/filters.py +18 -10
  25. sqlspec/core/result.py +133 -2
  26. sqlspec/driver/_async.py +89 -0
  27. sqlspec/driver/_common.py +64 -29
  28. sqlspec/driver/_sync.py +95 -0
  29. sqlspec/extensions/adk/migrations/0001_create_adk_tables.py +2 -2
  30. sqlspec/extensions/adk/service.py +3 -3
  31. sqlspec/extensions/adk/store.py +8 -8
  32. sqlspec/extensions/aiosql/adapter.py +3 -15
  33. sqlspec/extensions/fastapi/__init__.py +21 -0
  34. sqlspec/extensions/fastapi/extension.py +331 -0
  35. sqlspec/extensions/fastapi/providers.py +543 -0
  36. sqlspec/extensions/flask/__init__.py +36 -0
  37. sqlspec/extensions/flask/_state.py +71 -0
  38. sqlspec/extensions/flask/_utils.py +40 -0
  39. sqlspec/extensions/flask/extension.py +389 -0
  40. sqlspec/extensions/litestar/config.py +3 -6
  41. sqlspec/extensions/litestar/plugin.py +26 -2
  42. sqlspec/extensions/starlette/__init__.py +10 -0
  43. sqlspec/extensions/starlette/_state.py +25 -0
  44. sqlspec/extensions/starlette/_utils.py +52 -0
  45. sqlspec/extensions/starlette/extension.py +254 -0
  46. sqlspec/extensions/starlette/middleware.py +154 -0
  47. sqlspec/protocols.py +40 -0
  48. sqlspec/storage/_utils.py +1 -14
  49. sqlspec/storage/backends/fsspec.py +3 -5
  50. sqlspec/storage/backends/local.py +1 -1
  51. sqlspec/storage/backends/obstore.py +10 -18
  52. sqlspec/typing.py +16 -0
  53. sqlspec/utils/__init__.py +25 -4
  54. sqlspec/utils/arrow_helpers.py +81 -0
  55. sqlspec/utils/module_loader.py +203 -3
  56. sqlspec/utils/portal.py +311 -0
  57. sqlspec/utils/serializers.py +110 -1
  58. sqlspec/utils/sync_tools.py +15 -5
  59. sqlspec/utils/type_guards.py +25 -0
  60. {sqlspec-0.27.0.dist-info → sqlspec-0.28.0.dist-info}/METADATA +2 -2
  61. {sqlspec-0.27.0.dist-info → sqlspec-0.28.0.dist-info}/RECORD +64 -50
  62. {sqlspec-0.27.0.dist-info → sqlspec-0.28.0.dist-info}/WHEEL +0 -0
  63. {sqlspec-0.27.0.dist-info → sqlspec-0.28.0.dist-info}/entry_points.txt +0 -0
  64. {sqlspec-0.27.0.dist-info → sqlspec-0.28.0.dist-info}/licenses/LICENSE +0 -0
sqlspec/driver/_common.py CHANGED
@@ -13,6 +13,7 @@ from sqlspec.core.cache import CachedStatement, get_cache, get_cache_config
13
13
  from sqlspec.core.splitter import split_sql_script
14
14
  from sqlspec.exceptions import ImproperConfigurationError, NotFoundError
15
15
  from sqlspec.utils.logging import get_logger
16
+ from sqlspec.utils.type_guards import is_statement_filter
16
17
 
17
18
  if TYPE_CHECKING:
18
19
  from collections.abc import Sequence
@@ -414,48 +415,82 @@ class CommonDriverAttributesMixin:
414
415
  """
415
416
  kwargs = kwargs or {}
416
417
 
418
+ filters: list[StatementFilter] = []
419
+ data_parameters: list[StatementParameters] = []
420
+
421
+ for param in parameters:
422
+ if is_statement_filter(param):
423
+ filters.append(param)
424
+ else:
425
+ data_parameters.append(param)
426
+
417
427
  if isinstance(statement, QueryBuilder):
418
428
  sql_statement = statement.to_statement(statement_config)
419
- if parameters or kwargs:
429
+ if data_parameters or kwargs:
420
430
  merged_parameters = (
421
- (*sql_statement.positional_parameters, *parameters)
422
- if parameters
431
+ (*sql_statement.positional_parameters, *tuple(data_parameters))
432
+ if data_parameters
423
433
  else sql_statement.positional_parameters
424
434
  )
425
- return SQL(sql_statement.sql, *merged_parameters, statement_config=statement_config, **kwargs)
435
+ sql_statement = SQL(sql_statement.sql, *merged_parameters, statement_config=statement_config, **kwargs)
436
+
437
+ for filter_obj in filters:
438
+ sql_statement = filter_obj.append_to_statement(sql_statement)
439
+
426
440
  return sql_statement
441
+
427
442
  if isinstance(statement, SQL):
428
- if parameters or kwargs:
443
+ sql_statement = statement
444
+
445
+ if data_parameters or kwargs:
429
446
  merged_parameters = (
430
- (*statement.positional_parameters, *parameters) if parameters else statement.positional_parameters
447
+ (*sql_statement.positional_parameters, *tuple(data_parameters))
448
+ if data_parameters
449
+ else sql_statement.positional_parameters
431
450
  )
432
- return SQL(statement.sql, *merged_parameters, statement_config=statement_config, **kwargs)
433
- needs_rebuild = False
434
-
435
- if statement_config.dialect and (
436
- not statement.statement_config.dialect or statement.statement_config.dialect != statement_config.dialect
437
- ):
438
- needs_rebuild = True
451
+ sql_statement = SQL(sql_statement.sql, *merged_parameters, statement_config=statement_config, **kwargs)
452
+ else:
453
+ needs_rebuild = False
454
+
455
+ if statement_config.dialect and (
456
+ not sql_statement.statement_config.dialect
457
+ or sql_statement.statement_config.dialect != statement_config.dialect
458
+ ):
459
+ needs_rebuild = True
460
+
461
+ if (
462
+ sql_statement.statement_config.parameter_config.default_execution_parameter_style
463
+ != statement_config.parameter_config.default_execution_parameter_style
464
+ ):
465
+ needs_rebuild = True
466
+
467
+ if needs_rebuild:
468
+ sql_text = sql_statement.raw_sql or sql_statement.sql
469
+
470
+ if sql_statement.is_many and sql_statement.parameters:
471
+ sql_statement = SQL(
472
+ sql_text, sql_statement.parameters, statement_config=statement_config, is_many=True
473
+ )
474
+ elif sql_statement.named_parameters:
475
+ sql_statement = SQL(
476
+ sql_text, statement_config=statement_config, **sql_statement.named_parameters
477
+ )
478
+ else:
479
+ sql_statement = SQL(
480
+ sql_text, *sql_statement.positional_parameters, statement_config=statement_config
481
+ )
482
+
483
+ for filter_obj in filters:
484
+ sql_statement = filter_obj.append_to_statement(sql_statement)
439
485
 
440
- if (
441
- statement.statement_config.parameter_config.default_execution_parameter_style
442
- != statement_config.parameter_config.default_execution_parameter_style
443
- ):
444
- needs_rebuild = True
486
+ return sql_statement
445
487
 
446
- if needs_rebuild:
447
- sql_text = statement.raw_sql or statement.sql
488
+ sql_statement = SQL(statement, *tuple(data_parameters), statement_config=statement_config, **kwargs)
448
489
 
449
- if statement.is_many and statement.parameters:
450
- new_sql = SQL(sql_text, statement.parameters, statement_config=statement_config, is_many=True)
451
- elif statement.named_parameters:
452
- new_sql = SQL(sql_text, statement_config=statement_config, **statement.named_parameters)
453
- else:
454
- new_sql = SQL(sql_text, *statement.positional_parameters, statement_config=statement_config)
490
+ for filter_obj in filters:
491
+ sql_statement = filter_obj.append_to_statement(sql_statement)
455
492
 
456
- return new_sql
457
- return statement
458
- return SQL(statement, *parameters, statement_config=statement_config, **kwargs)
493
+ return sql_statement
459
494
 
460
495
  def split_script_statements(
461
496
  self, script: str, statement_config: "StatementConfig", strip_trailing_semicolon: bool = False
sqlspec/driver/_sync.py CHANGED
@@ -4,6 +4,7 @@ from abc import abstractmethod
4
4
  from typing import TYPE_CHECKING, Any, Final, TypeVar, overload
5
5
 
6
6
  from sqlspec.core import SQL
7
+ from sqlspec.core.result import create_arrow_result
7
8
  from sqlspec.driver._common import (
8
9
  CommonDriverAttributesMixin,
9
10
  DataDictionaryMixin,
@@ -12,7 +13,10 @@ from sqlspec.driver._common import (
12
13
  handle_single_row_error,
13
14
  )
14
15
  from sqlspec.driver.mixins import SQLTranslatorMixin
16
+ from sqlspec.exceptions import ImproperConfigurationError
17
+ from sqlspec.utils.arrow_helpers import convert_dict_to_arrow
15
18
  from sqlspec.utils.logging import get_logger
19
+ from sqlspec.utils.module_loader import ensure_pyarrow
16
20
 
17
21
  if TYPE_CHECKING:
18
22
  from collections.abc import Sequence
@@ -341,6 +345,97 @@ class SyncDriverAdapterBase(CommonDriverAttributesMixin, SQLTranslatorMixin):
341
345
  result = self.execute(statement, *parameters, statement_config=statement_config, **kwargs)
342
346
  return result.get_data(schema_type=schema_type)
343
347
 
348
+ def select_to_arrow(
349
+ self,
350
+ statement: "Statement | QueryBuilder",
351
+ /,
352
+ *parameters: "StatementParameters | StatementFilter",
353
+ statement_config: "StatementConfig | None" = None,
354
+ return_format: str = "table",
355
+ native_only: bool = False,
356
+ batch_size: int | None = None,
357
+ arrow_schema: Any = None,
358
+ **kwargs: Any,
359
+ ) -> "Any":
360
+ """Execute query and return results as Apache Arrow format.
361
+
362
+ This base implementation uses the conversion path: execute() → dict → Arrow.
363
+ Adapters with native Arrow support (ADBC, DuckDB, BigQuery) override this
364
+ method to use zero-copy native paths for 5-10x performance improvement.
365
+
366
+ Args:
367
+ statement: SQL query string, Statement, or QueryBuilder
368
+ *parameters: Query parameters (same format as execute()/select())
369
+ statement_config: Optional statement configuration override
370
+ return_format: "table" for pyarrow.Table (default), "reader" for RecordBatchReader,
371
+ "batches" for iterator of RecordBatches
372
+ native_only: If True, raise error if native Arrow unavailable (default: False)
373
+ batch_size: Rows per batch for "batches" format (default: None = all rows)
374
+ arrow_schema: Optional pyarrow.Schema for type casting
375
+ **kwargs: Additional keyword arguments
376
+
377
+ Returns:
378
+ ArrowResult containing pyarrow.Table, RecordBatchReader, or RecordBatches
379
+
380
+ Raises:
381
+ MissingDependencyError: If pyarrow not installed
382
+ ImproperConfigurationError: If native_only=True and adapter doesn't support native Arrow
383
+ SQLExecutionError: If query execution fails
384
+
385
+ Examples:
386
+ >>> result = driver.select_to_arrow(
387
+ ... "SELECT * FROM users WHERE age > ?", 18
388
+ ... )
389
+ >>> df = result.to_pandas()
390
+ >>> print(df.head())
391
+
392
+ >>> # Force native Arrow path (raises error if unavailable)
393
+ >>> result = driver.select_to_arrow(
394
+ ... "SELECT * FROM users", native_only=True
395
+ ... )
396
+ """
397
+ # Check pyarrow is available
398
+ ensure_pyarrow()
399
+
400
+ # Check if native_only requested but not supported
401
+ if native_only:
402
+ msg = (
403
+ f"Adapter '{self.__class__.__name__}' does not support native Arrow results. "
404
+ f"Use native_only=False to allow conversion path, or switch to an adapter "
405
+ f"with native Arrow support (ADBC, DuckDB, BigQuery)."
406
+ )
407
+ raise ImproperConfigurationError(msg)
408
+
409
+ # Execute query using standard path
410
+ result = self.execute(statement, *parameters, statement_config=statement_config, **kwargs)
411
+
412
+ # Convert dict results to Arrow
413
+ arrow_data = convert_dict_to_arrow(
414
+ result.data,
415
+ return_format=return_format, # type: ignore[arg-type]
416
+ batch_size=batch_size,
417
+ )
418
+
419
+ # Apply schema casting if requested
420
+ if arrow_schema is not None:
421
+ import pyarrow as pa
422
+
423
+ if not isinstance(arrow_schema, pa.Schema):
424
+ msg = f"arrow_schema must be a pyarrow.Schema, got {type(arrow_schema).__name__}"
425
+ raise TypeError(msg)
426
+
427
+ arrow_data = arrow_data.cast(arrow_schema)
428
+
429
+ # Create ArrowResult
430
+ return create_arrow_result(
431
+ statement=result.statement,
432
+ data=arrow_data,
433
+ rows_affected=result.rows_affected,
434
+ last_inserted_id=result.last_inserted_id,
435
+ execution_time=result.execution_time,
436
+ metadata=result.metadata,
437
+ )
438
+
344
439
  def select_value(
345
440
  self,
346
441
  statement: "Statement | QueryBuilder",
@@ -114,8 +114,8 @@ async def up(context: "MigrationContext | None" = None) -> "list[str]":
114
114
  store_instance = store_class(config=context.config)
115
115
 
116
116
  return [
117
- store_instance._get_create_sessions_table_sql(), # pyright: ignore[reportPrivateUsage]
118
- store_instance._get_create_events_table_sql(), # pyright: ignore[reportPrivateUsage]
117
+ await store_instance._get_create_sessions_table_sql(), # pyright: ignore[reportPrivateUsage]
118
+ await store_instance._get_create_events_table_sql(), # pyright: ignore[reportPrivateUsage]
119
119
  ]
120
120
 
121
121
 
@@ -119,12 +119,12 @@ class SQLSpecSessionService(BaseSessionService):
119
119
 
120
120
  return record_to_session(record, events)
121
121
 
122
- async def list_sessions(self, *, app_name: str, user_id: str) -> "ListSessionsResponse":
123
- """List all sessions for an app and user.
122
+ async def list_sessions(self, *, app_name: str, user_id: str | None = None) -> "ListSessionsResponse":
123
+ """List all sessions for an app, optionally filtered by user.
124
124
 
125
125
  Args:
126
126
  app_name: Name of the application.
127
- user_id: ID of the user.
127
+ user_id: ID of the user. If None, all sessions for the app are listed.
128
128
 
129
129
  Returns:
130
130
  Response containing list of sessions (without events).
@@ -219,12 +219,12 @@ class BaseAsyncADKStore(ABC, Generic[ConfigT]):
219
219
  raise NotImplementedError
220
220
 
221
221
  @abstractmethod
222
- async def list_sessions(self, app_name: str, user_id: str) -> "list[SessionRecord]":
223
- """List all sessions for an app and user.
222
+ async def list_sessions(self, app_name: str, user_id: "str | None" = None) -> "list[SessionRecord]":
223
+ """List all sessions for an app, optionally filtered by user.
224
224
 
225
225
  Args:
226
226
  app_name: Name of the application.
227
- user_id: ID of the user.
227
+ user_id: ID of the user. If None, returns all sessions for the app.
228
228
 
229
229
  Returns:
230
230
  List of session records.
@@ -271,7 +271,7 @@ class BaseAsyncADKStore(ABC, Generic[ConfigT]):
271
271
  raise NotImplementedError
272
272
 
273
273
  @abstractmethod
274
- def _get_create_sessions_table_sql(self) -> str:
274
+ async def _get_create_sessions_table_sql(self) -> str:
275
275
  """Get the CREATE TABLE SQL for the sessions table.
276
276
 
277
277
  Returns:
@@ -280,7 +280,7 @@ class BaseAsyncADKStore(ABC, Generic[ConfigT]):
280
280
  raise NotImplementedError
281
281
 
282
282
  @abstractmethod
283
- def _get_create_events_table_sql(self) -> str:
283
+ async def _get_create_events_table_sql(self) -> str:
284
284
  """Get the CREATE TABLE SQL for the events table.
285
285
 
286
286
  Returns:
@@ -440,12 +440,12 @@ class BaseSyncADKStore(ABC, Generic[ConfigT]):
440
440
  raise NotImplementedError
441
441
 
442
442
  @abstractmethod
443
- def list_sessions(self, app_name: str, user_id: str) -> "list[SessionRecord]":
444
- """List all sessions for an app and user.
443
+ def list_sessions(self, app_name: str, user_id: "str | None" = None) -> "list[SessionRecord]":
444
+ """List all sessions for an app, optionally filtered by user.
445
445
 
446
446
  Args:
447
447
  app_name: Name of the application.
448
- user_id: ID of the user.
448
+ user_id: ID of the user. If None, returns all sessions for the app.
449
449
 
450
450
  Returns:
451
451
  List of session records.
@@ -13,14 +13,8 @@ from typing import Any, ClassVar, Generic, TypeVar
13
13
  from sqlspec.core.result import SQLResult
14
14
  from sqlspec.core.statement import SQL, StatementConfig
15
15
  from sqlspec.driver import AsyncDriverAdapterBase, SyncDriverAdapterBase
16
- from sqlspec.exceptions import MissingDependencyError
17
- from sqlspec.typing import (
18
- AIOSQL_INSTALLED,
19
- AiosqlAsyncProtocol,
20
- AiosqlParamType,
21
- AiosqlSQLOperationType,
22
- AiosqlSyncProtocol,
23
- )
16
+ from sqlspec.typing import AiosqlAsyncProtocol, AiosqlParamType, AiosqlSQLOperationType, AiosqlSyncProtocol
17
+ from sqlspec.utils.module_loader import ensure_aiosql
24
18
 
25
19
  logger = logging.getLogger("sqlspec.extensions.aiosql")
26
20
 
@@ -58,12 +52,6 @@ class CursorLike:
58
52
  return rows[0] if rows else None
59
53
 
60
54
 
61
- def _check_aiosql_available() -> None:
62
- if not AIOSQL_INSTALLED:
63
- msg = "aiosql"
64
- raise MissingDependencyError(msg, "aiosql")
65
-
66
-
67
55
  def _normalize_dialect(dialect: "str | Any | None") -> str:
68
56
  """Normalize dialect name for SQLGlot compatibility.
69
57
 
@@ -105,7 +93,7 @@ class _AiosqlAdapterBase(Generic[DriverT]):
105
93
  Args:
106
94
  driver: SQLSpec driver to use for execution.
107
95
  """
108
- _check_aiosql_available()
96
+ ensure_aiosql()
109
97
  self.driver: DriverT = driver
110
98
 
111
99
  def process_sql(self, query_name: str, op_type: "AiosqlSQLOperationType", sql: str) -> str:
@@ -0,0 +1,21 @@
1
+ """FastAPI extension for SQLSpec.
2
+
3
+ Extends Starlette integration with dependency injection helpers for FastAPI's
4
+ Depends() system, including filter dependency builders.
5
+ """
6
+
7
+ from sqlspec.config import StarletteConfig
8
+ from sqlspec.extensions.fastapi.extension import SQLSpecPlugin
9
+ from sqlspec.extensions.fastapi.providers import DependencyDefaults, FieldNameType, FilterConfig, provide_filters
10
+ from sqlspec.extensions.starlette.middleware import SQLSpecAutocommitMiddleware, SQLSpecManualMiddleware
11
+
12
+ __all__ = (
13
+ "DependencyDefaults",
14
+ "FieldNameType",
15
+ "FilterConfig",
16
+ "SQLSpecAutocommitMiddleware",
17
+ "SQLSpecManualMiddleware",
18
+ "SQLSpecPlugin",
19
+ "StarletteConfig",
20
+ "provide_filters",
21
+ )