PyAthena 3.29.0__tar.gz → 3.29.2__tar.gz

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.
Files changed (85) hide show
  1. {pyathena-3.29.0 → pyathena-3.29.2}/PKG-INFO +5 -3
  2. {pyathena-3.29.0 → pyathena-3.29.2}/README.md +2 -2
  3. {pyathena-3.29.0 → pyathena-3.29.2}/pyathena/__init__.py +2 -2
  4. {pyathena-3.29.0 → pyathena-3.29.2}/pyathena/_version.py +2 -2
  5. {pyathena-3.29.0 → pyathena-3.29.2}/pyathena/aio/arrow/cursor.py +1 -1
  6. {pyathena-3.29.0 → pyathena-3.29.2}/pyathena/aio/pandas/cursor.py +1 -1
  7. {pyathena-3.29.0 → pyathena-3.29.2}/pyathena/aio/polars/cursor.py +1 -1
  8. {pyathena-3.29.0 → pyathena-3.29.2}/pyathena/aio/s3fs/cursor.py +1 -1
  9. {pyathena-3.29.0 → pyathena-3.29.2}/pyathena/aio/spark/cursor.py +1 -1
  10. pyathena-3.29.2/pyathena/aio/sqlalchemy/arrow.py +55 -0
  11. pyathena-3.29.2/pyathena/aio/sqlalchemy/base.py +221 -0
  12. pyathena-3.29.2/pyathena/aio/sqlalchemy/pandas.py +61 -0
  13. pyathena-3.29.2/pyathena/aio/sqlalchemy/polars.py +55 -0
  14. pyathena-3.29.2/pyathena/aio/sqlalchemy/rest.py +37 -0
  15. pyathena-3.29.2/pyathena/aio/sqlalchemy/s3fs.py +44 -0
  16. pyathena-3.29.2/pyathena/sqlalchemy/__init__.py +1 -0
  17. {pyathena-3.29.0 → pyathena-3.29.2}/pyathena/sqlalchemy/compiler.py +65 -69
  18. {pyathena-3.29.0 → pyathena-3.29.2}/pyproject.toml +19 -4
  19. {pyathena-3.29.0 → pyathena-3.29.2}/.gitignore +0 -0
  20. {pyathena-3.29.0 → pyathena-3.29.2}/LICENSE +0 -0
  21. {pyathena-3.29.0 → pyathena-3.29.2}/pyathena/aio/__init__.py +0 -0
  22. {pyathena-3.29.0 → pyathena-3.29.2}/pyathena/aio/arrow/__init__.py +0 -0
  23. {pyathena-3.29.0 → pyathena-3.29.2}/pyathena/aio/common.py +0 -0
  24. {pyathena-3.29.0 → pyathena-3.29.2}/pyathena/aio/connection.py +0 -0
  25. {pyathena-3.29.0 → pyathena-3.29.2}/pyathena/aio/cursor.py +0 -0
  26. {pyathena-3.29.0 → pyathena-3.29.2}/pyathena/aio/pandas/__init__.py +0 -0
  27. {pyathena-3.29.0 → pyathena-3.29.2}/pyathena/aio/polars/__init__.py +0 -0
  28. {pyathena-3.29.0 → pyathena-3.29.2}/pyathena/aio/result_set.py +0 -0
  29. {pyathena-3.29.0 → pyathena-3.29.2}/pyathena/aio/s3fs/__init__.py +0 -0
  30. {pyathena-3.29.0 → pyathena-3.29.2}/pyathena/aio/spark/__init__.py +0 -0
  31. {pyathena-3.29.0/pyathena/arrow → pyathena-3.29.2/pyathena/aio/sqlalchemy}/__init__.py +0 -0
  32. {pyathena-3.29.0 → pyathena-3.29.2}/pyathena/aio/util.py +0 -0
  33. {pyathena-3.29.0/pyathena/filesystem → pyathena-3.29.2/pyathena/arrow}/__init__.py +0 -0
  34. {pyathena-3.29.0 → pyathena-3.29.2}/pyathena/arrow/async_cursor.py +0 -0
  35. {pyathena-3.29.0 → pyathena-3.29.2}/pyathena/arrow/converter.py +0 -0
  36. {pyathena-3.29.0 → pyathena-3.29.2}/pyathena/arrow/cursor.py +0 -0
  37. {pyathena-3.29.0 → pyathena-3.29.2}/pyathena/arrow/result_set.py +0 -0
  38. {pyathena-3.29.0 → pyathena-3.29.2}/pyathena/arrow/util.py +0 -0
  39. {pyathena-3.29.0 → pyathena-3.29.2}/pyathena/async_cursor.py +0 -0
  40. {pyathena-3.29.0 → pyathena-3.29.2}/pyathena/common.py +0 -0
  41. {pyathena-3.29.0 → pyathena-3.29.2}/pyathena/connection.py +0 -0
  42. {pyathena-3.29.0 → pyathena-3.29.2}/pyathena/converter.py +0 -0
  43. {pyathena-3.29.0 → pyathena-3.29.2}/pyathena/cursor.py +0 -0
  44. {pyathena-3.29.0 → pyathena-3.29.2}/pyathena/error.py +0 -0
  45. {pyathena-3.29.0/pyathena/s3fs → pyathena-3.29.2/pyathena/filesystem}/__init__.py +0 -0
  46. {pyathena-3.29.0 → pyathena-3.29.2}/pyathena/filesystem/s3.py +0 -0
  47. {pyathena-3.29.0 → pyathena-3.29.2}/pyathena/filesystem/s3_object.py +0 -0
  48. {pyathena-3.29.0 → pyathena-3.29.2}/pyathena/formatter.py +0 -0
  49. {pyathena-3.29.0 → pyathena-3.29.2}/pyathena/model.py +0 -0
  50. {pyathena-3.29.0 → pyathena-3.29.2}/pyathena/pandas/__init__.py +0 -0
  51. {pyathena-3.29.0 → pyathena-3.29.2}/pyathena/pandas/async_cursor.py +0 -0
  52. {pyathena-3.29.0 → pyathena-3.29.2}/pyathena/pandas/converter.py +0 -0
  53. {pyathena-3.29.0 → pyathena-3.29.2}/pyathena/pandas/cursor.py +0 -0
  54. {pyathena-3.29.0 → pyathena-3.29.2}/pyathena/pandas/result_set.py +0 -0
  55. {pyathena-3.29.0 → pyathena-3.29.2}/pyathena/pandas/util.py +0 -0
  56. {pyathena-3.29.0 → pyathena-3.29.2}/pyathena/polars/__init__.py +0 -0
  57. {pyathena-3.29.0 → pyathena-3.29.2}/pyathena/polars/async_cursor.py +0 -0
  58. {pyathena-3.29.0 → pyathena-3.29.2}/pyathena/polars/converter.py +0 -0
  59. {pyathena-3.29.0 → pyathena-3.29.2}/pyathena/polars/cursor.py +0 -0
  60. {pyathena-3.29.0 → pyathena-3.29.2}/pyathena/polars/result_set.py +0 -0
  61. {pyathena-3.29.0 → pyathena-3.29.2}/pyathena/polars/util.py +0 -0
  62. {pyathena-3.29.0 → pyathena-3.29.2}/pyathena/py.typed +0 -0
  63. {pyathena-3.29.0 → pyathena-3.29.2}/pyathena/result_set.py +0 -0
  64. {pyathena-3.29.0/pyathena/spark → pyathena-3.29.2/pyathena/s3fs}/__init__.py +0 -0
  65. {pyathena-3.29.0 → pyathena-3.29.2}/pyathena/s3fs/async_cursor.py +0 -0
  66. {pyathena-3.29.0 → pyathena-3.29.2}/pyathena/s3fs/converter.py +0 -0
  67. {pyathena-3.29.0 → pyathena-3.29.2}/pyathena/s3fs/cursor.py +0 -0
  68. {pyathena-3.29.0 → pyathena-3.29.2}/pyathena/s3fs/reader.py +0 -0
  69. {pyathena-3.29.0 → pyathena-3.29.2}/pyathena/s3fs/result_set.py +0 -0
  70. {pyathena-3.29.0/pyathena/sqlalchemy → pyathena-3.29.2/pyathena/spark}/__init__.py +0 -0
  71. {pyathena-3.29.0 → pyathena-3.29.2}/pyathena/spark/async_cursor.py +0 -0
  72. {pyathena-3.29.0 → pyathena-3.29.2}/pyathena/spark/common.py +0 -0
  73. {pyathena-3.29.0 → pyathena-3.29.2}/pyathena/spark/cursor.py +0 -0
  74. {pyathena-3.29.0 → pyathena-3.29.2}/pyathena/sqlalchemy/arrow.py +0 -0
  75. {pyathena-3.29.0 → pyathena-3.29.2}/pyathena/sqlalchemy/base.py +0 -0
  76. {pyathena-3.29.0 → pyathena-3.29.2}/pyathena/sqlalchemy/constants.py +0 -0
  77. {pyathena-3.29.0 → pyathena-3.29.2}/pyathena/sqlalchemy/pandas.py +0 -0
  78. {pyathena-3.29.0 → pyathena-3.29.2}/pyathena/sqlalchemy/polars.py +0 -0
  79. {pyathena-3.29.0 → pyathena-3.29.2}/pyathena/sqlalchemy/preparer.py +0 -0
  80. {pyathena-3.29.0 → pyathena-3.29.2}/pyathena/sqlalchemy/requirements.py +0 -0
  81. {pyathena-3.29.0 → pyathena-3.29.2}/pyathena/sqlalchemy/rest.py +0 -0
  82. {pyathena-3.29.0 → pyathena-3.29.2}/pyathena/sqlalchemy/s3fs.py +0 -0
  83. {pyathena-3.29.0 → pyathena-3.29.2}/pyathena/sqlalchemy/types.py +0 -0
  84. {pyathena-3.29.0 → pyathena-3.29.2}/pyathena/sqlalchemy/util.py +0 -0
  85. {pyathena-3.29.0 → pyathena-3.29.2}/pyathena/util.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: PyAthena
3
- Version: 3.29.0
3
+ Version: 3.29.2
4
4
  Summary: Python DB API 2.0 (PEP 249) client for Amazon Athena
5
5
  Project-URL: homepage, https://github.com/pyathena-dev/PyAthena/
6
6
  Project-URL: repository, https://github.com/pyathena-dev/PyAthena/
@@ -43,6 +43,8 @@ Requires-Dist: botocore>=1.29.4
43
43
  Requires-Dist: fsspec
44
44
  Requires-Dist: python-dateutil
45
45
  Requires-Dist: tenacity>=4.1.0
46
+ Provides-Extra: aiosqlalchemy
47
+ Requires-Dist: sqlalchemy[asyncio]>=2.0.0; extra == 'aiosqlalchemy'
46
48
  Provides-Extra: arrow
47
49
  Requires-Dist: pyarrow>=10.0.0; (python_version < '3.14') and extra == 'arrow'
48
50
  Requires-Dist: pyarrow>=22.0.0; (python_version >= '3.14') and extra == 'arrow'
@@ -113,10 +115,10 @@ Native asyncio is also supported:
113
115
 
114
116
  ```python
115
117
  import asyncio
116
- from pyathena import aconnect
118
+ from pyathena import aio_connect
117
119
 
118
120
  async def main():
119
- async with await aconnect(s3_staging_dir="s3://YOUR_S3_BUCKET/path/to/",
121
+ async with await aio_connect(s3_staging_dir="s3://YOUR_S3_BUCKET/path/to/",
120
122
  region_name="us-west-2") as conn:
121
123
  cursor = conn.cursor()
122
124
  await cursor.execute("SELECT 1")
@@ -56,10 +56,10 @@ Native asyncio is also supported:
56
56
 
57
57
  ```python
58
58
  import asyncio
59
- from pyathena import aconnect
59
+ from pyathena import aio_connect
60
60
 
61
61
  async def main():
62
- async with await aconnect(s3_staging_dir="s3://YOUR_S3_BUCKET/path/to/",
62
+ async with await aio_connect(s3_staging_dir="s3://YOUR_S3_BUCKET/path/to/",
63
63
  region_name="us-west-2") as conn:
64
64
  cursor = conn.cursor()
65
65
  await cursor.execute("SELECT 1")
@@ -131,7 +131,7 @@ def connect(*args, **kwargs) -> "Connection[Any]":
131
131
  return Connection(*args, **kwargs)
132
132
 
133
133
 
134
- async def aconnect(*args, **kwargs) -> "AioConnection":
134
+ async def aio_connect(*args, **kwargs) -> "AioConnection":
135
135
  """Create a new async database connection to Amazon Athena.
136
136
 
137
137
  This is the async counterpart of :func:`connect`. It returns an
@@ -147,7 +147,7 @@ async def aconnect(*args, **kwargs) -> "AioConnection":
147
147
 
148
148
  Example:
149
149
  >>> import pyathena
150
- >>> conn = await pyathena.aconnect(
150
+ >>> conn = await pyathena.aio_connect(
151
151
  ... s3_staging_dir='s3://my-bucket/staging/',
152
152
  ... region_name='us-east-1',
153
153
  ... )
@@ -28,7 +28,7 @@ version_tuple: VERSION_TUPLE
28
28
  commit_id: COMMIT_ID
29
29
  __commit_id__: COMMIT_ID
30
30
 
31
- __version__ = version = '3.29.0'
32
- __version_tuple__ = version_tuple = (3, 29, 0)
31
+ __version__ = version = '3.29.2'
32
+ __version_tuple__ = version_tuple = (3, 29, 2)
33
33
 
34
34
  __commit_id__ = commit_id = None
@@ -29,7 +29,7 @@ class AioArrowCursor(WithAsyncFetch):
29
29
  operations, keeping the event loop free.
30
30
 
31
31
  Example:
32
- >>> async with await pyathena.aconnect(...) as conn:
32
+ >>> async with await pyathena.aio_connect(...) as conn:
33
33
  ... cursor = conn.cursor(AioArrowCursor)
34
34
  ... await cursor.execute("SELECT * FROM my_table")
35
35
  ... table = cursor.as_arrow()
@@ -40,7 +40,7 @@ class AioPandasCursor(WithAsyncFetch):
40
40
  when ``chunksize`` is set, as fetch calls trigger lazy S3 reads.
41
41
 
42
42
  Example:
43
- >>> async with await pyathena.aconnect(...) as conn:
43
+ >>> async with await pyathena.aio_connect(...) as conn:
44
44
  ... cursor = conn.cursor(AioPandasCursor)
45
45
  ... await cursor.execute("SELECT * FROM my_table")
46
46
  ... df = cursor.as_pandas()
@@ -31,7 +31,7 @@ class AioPolarsCursor(WithAsyncFetch):
31
31
  when ``chunksize`` is set, as fetch calls trigger lazy S3 reads.
32
32
 
33
33
  Example:
34
- >>> async with await pyathena.aconnect(...) as conn:
34
+ >>> async with await pyathena.aio_connect(...) as conn:
35
35
  ... cursor = conn.cursor(AioPolarsCursor)
36
36
  ... await cursor.execute("SELECT * FROM my_table")
37
37
  ... df = cursor.as_polars()
@@ -23,7 +23,7 @@ class AioS3FSCursor(WithAsyncFetch):
23
23
  reader, making fetch calls blocking I/O.
24
24
 
25
25
  Example:
26
- >>> async with await pyathena.aconnect(...) as conn:
26
+ >>> async with await pyathena.aio_connect(...) as conn:
27
27
  ... cursor = conn.cursor(AioS3FSCursor)
28
28
  ... await cursor.execute("SELECT * FROM my_table")
29
29
  ... row = await cursor.fetchone()
@@ -33,7 +33,7 @@ class AioSparkCursor(SparkBaseCursor, WithCalculationExecution):
33
33
 
34
34
  Example:
35
35
  >>> import asyncio
36
- >>> async with await pyathena.aconnect(
36
+ >>> async with await pyathena.aio_connect(
37
37
  ... work_group="spark-workgroup",
38
38
  ... cursor_class=AioSparkCursor,
39
39
  ... ) as conn:
@@ -0,0 +1,55 @@
1
+ # -*- coding: utf-8 -*-
2
+ from typing import TYPE_CHECKING
3
+
4
+ from pyathena.aio.sqlalchemy.base import AthenaAioDialect
5
+ from pyathena.util import strtobool
6
+
7
+ if TYPE_CHECKING:
8
+ from types import ModuleType
9
+
10
+
11
+ class AthenaAioArrowDialect(AthenaAioDialect):
12
+ """Async SQLAlchemy dialect for Amazon Athena with Apache Arrow result format.
13
+
14
+ This dialect uses ``AioArrowCursor`` for native asyncio query execution
15
+ with Apache Arrow Table results.
16
+
17
+ Connection URL Format:
18
+ ``awsathena+aioarrow://{access_key}:{secret_key}@athena.{region}.amazonaws.com/{schema}``
19
+
20
+ Query Parameters:
21
+ In addition to the base dialect parameters:
22
+ - unload: If "true", use UNLOAD for Parquet output
23
+
24
+ Example:
25
+ >>> from sqlalchemy.ext.asyncio import create_async_engine
26
+ >>> engine = create_async_engine(
27
+ ... "awsathena+aioarrow://:@athena.us-west-2.amazonaws.com/default"
28
+ ... "?s3_staging_dir=s3://my-bucket/athena-results/"
29
+ ... "&unload=true"
30
+ ... )
31
+
32
+ See Also:
33
+ :class:`~pyathena.aio.arrow.cursor.AioArrowCursor`: The underlying async cursor.
34
+ :class:`~pyathena.aio.sqlalchemy.base.AthenaAioDialect`: Base async dialect.
35
+ """
36
+
37
+ driver = "aioarrow"
38
+ supports_statement_cache = True
39
+
40
+ def create_connect_args(self, url):
41
+ from pyathena.aio.arrow.cursor import AioArrowCursor
42
+
43
+ opts = super()._create_connect_args(url)
44
+ opts.update({"cursor_class": AioArrowCursor})
45
+ cursor_kwargs = {}
46
+ if "unload" in opts:
47
+ cursor_kwargs.update({"unload": bool(strtobool(opts.pop("unload")))})
48
+ if cursor_kwargs:
49
+ opts.update({"cursor_kwargs": cursor_kwargs})
50
+ self._connect_options = opts
51
+ return [[], opts]
52
+
53
+ @classmethod
54
+ def import_dbapi(cls) -> "ModuleType":
55
+ return super().import_dbapi()
@@ -0,0 +1,221 @@
1
+ # -*- coding: utf-8 -*-
2
+ from __future__ import annotations
3
+
4
+ from collections import deque
5
+ from typing import TYPE_CHECKING, Any, Dict, List, MutableMapping, Optional, Tuple, Union, cast
6
+
7
+ from sqlalchemy import pool
8
+ from sqlalchemy.engine import AdaptedConnection
9
+ from sqlalchemy.util.concurrency import await_only
10
+
11
+ import pyathena
12
+ from pyathena.aio.connection import AioConnection
13
+ from pyathena.error import (
14
+ DatabaseError,
15
+ DataError,
16
+ Error,
17
+ IntegrityError,
18
+ InterfaceError,
19
+ InternalError,
20
+ NotSupportedError,
21
+ OperationalError,
22
+ ProgrammingError,
23
+ )
24
+ from pyathena.sqlalchemy.base import AthenaDialect
25
+
26
+ if TYPE_CHECKING:
27
+ from types import ModuleType
28
+
29
+ from sqlalchemy import URL
30
+
31
+
32
+ class AsyncAdapt_pyathena_cursor: # noqa: N801 - follows SQLAlchemy's internal async adapter naming convention (e.g. AsyncAdapt_asyncpg_dbapi)
33
+ """Wraps any async PyAthena cursor with a sync DBAPI interface.
34
+
35
+ SQLAlchemy's async engine uses greenlet-based ``await_only()`` to call
36
+ async methods from synchronous code running inside the greenlet context.
37
+ This adapter wraps an ``AioCursor`` (or variant) so that the dialect can
38
+ use a normal synchronous DBAPI interface while the underlying I/O is async.
39
+ """
40
+
41
+ server_side = False
42
+ __slots__ = ("_cursor", "_rows")
43
+
44
+ def __init__(self, cursor: Any) -> None:
45
+ self._cursor = cursor
46
+ self._rows: deque[Any] = deque()
47
+
48
+ @property
49
+ def description(self) -> Any:
50
+ return self._cursor.description
51
+
52
+ @property
53
+ def rowcount(self) -> int:
54
+ return self._cursor.rowcount # type: ignore[no-any-return]
55
+
56
+ def close(self) -> None:
57
+ self._cursor.close()
58
+ self._rows.clear()
59
+
60
+ def execute(self, operation: str, parameters: Any = None, **kwargs: Any) -> Any:
61
+ result = await_only(self._cursor.execute(operation, parameters, **kwargs))
62
+ if self._cursor.description:
63
+ self._rows = deque(await_only(self._cursor.fetchall()))
64
+ else:
65
+ self._rows.clear()
66
+ return result
67
+
68
+ def executemany(
69
+ self,
70
+ operation: str,
71
+ seq_of_parameters: List[Optional[Union[Dict[str, Any], List[str]]]],
72
+ **kwargs: Any,
73
+ ) -> None:
74
+ for parameters in seq_of_parameters:
75
+ await_only(self._cursor.execute(operation, parameters, **kwargs))
76
+ self._rows.clear()
77
+
78
+ def fetchone(self) -> Any:
79
+ if self._rows:
80
+ return self._rows.popleft()
81
+ return None
82
+
83
+ def fetchmany(self, size: Optional[int] = None) -> Any:
84
+ if size is None:
85
+ size = self._cursor.arraysize if hasattr(self._cursor, "arraysize") else 1
86
+ return [self._rows.popleft() for _ in range(min(size, len(self._rows)))]
87
+
88
+ def fetchall(self) -> Any:
89
+ items = list(self._rows)
90
+ self._rows.clear()
91
+ return items
92
+
93
+ def setinputsizes(self, sizes: Any) -> None:
94
+ self._cursor.setinputsizes(sizes)
95
+
96
+ async def _async_soft_close(self) -> None:
97
+ return
98
+
99
+ # PyAthena-specific methods used by AthenaDialect reflection
100
+ def list_databases(self, *args: Any, **kwargs: Any) -> Any:
101
+ return await_only(self._cursor.list_databases(*args, **kwargs))
102
+
103
+ def get_table_metadata(self, *args: Any, **kwargs: Any) -> Any:
104
+ return await_only(self._cursor.get_table_metadata(*args, **kwargs))
105
+
106
+ def list_table_metadata(self, *args: Any, **kwargs: Any) -> Any:
107
+ return await_only(self._cursor.list_table_metadata(*args, **kwargs))
108
+
109
+ def __enter__(self) -> "AsyncAdapt_pyathena_cursor":
110
+ return self
111
+
112
+ def __exit__(self, exc_type: Any, exc_val: Any, exc_tb: Any) -> None:
113
+ self.close()
114
+
115
+
116
+ class AsyncAdapt_pyathena_connection(AdaptedConnection): # noqa: N801 - follows SQLAlchemy's internal async adapter naming convention (e.g. AsyncAdapt_asyncpg_dbapi)
117
+ """Wraps ``AioConnection`` with a sync DBAPI interface.
118
+
119
+ This adapted connection delegates ``cursor()`` to the underlying
120
+ ``AioConnection`` and wraps each returned async cursor with
121
+ ``AsyncAdapt_pyathena_cursor``.
122
+ """
123
+
124
+ __slots__ = ("dbapi", "_connection")
125
+
126
+ def __init__(self, dbapi: "AsyncAdapt_pyathena_dbapi", connection: AioConnection) -> None:
127
+ self.dbapi = dbapi
128
+ self._connection = connection # type: ignore[assignment]
129
+
130
+ @property
131
+ def driver_connection(self) -> AioConnection:
132
+ return self._connection # type: ignore[return-value]
133
+
134
+ @property
135
+ def catalog_name(self) -> Optional[str]:
136
+ return self._connection.catalog_name # type: ignore[no-any-return]
137
+
138
+ @property
139
+ def schema_name(self) -> Optional[str]:
140
+ return self._connection.schema_name # type: ignore[no-any-return]
141
+
142
+ def cursor(self) -> AsyncAdapt_pyathena_cursor:
143
+ raw_cursor = self._connection.cursor()
144
+ return AsyncAdapt_pyathena_cursor(raw_cursor)
145
+
146
+ def close(self) -> None:
147
+ self._connection.close()
148
+
149
+ def commit(self) -> None:
150
+ self._connection.commit() # type: ignore[unused-coroutine]
151
+
152
+ def rollback(self) -> None:
153
+ pass
154
+
155
+
156
+ class AsyncAdapt_pyathena_dbapi: # noqa: N801 - follows SQLAlchemy's internal async adapter naming convention (e.g. AsyncAdapt_asyncpg_dbapi)
157
+ """Fake DBAPI module for the async SQLAlchemy engine.
158
+
159
+ SQLAlchemy expects ``import_dbapi()`` to return a module-like object
160
+ with ``connect()``, ``paramstyle``, and the standard DBAPI exception
161
+ hierarchy. This class fulfils that contract while routing connections
162
+ through ``AioConnection``.
163
+ """
164
+
165
+ paramstyle = "pyformat"
166
+
167
+ # DBAPI exception hierarchy
168
+ Error = Error
169
+ Warning = pyathena.Warning
170
+ InterfaceError = InterfaceError
171
+ DatabaseError = DatabaseError
172
+ InternalError = InternalError
173
+ OperationalError = OperationalError
174
+ ProgrammingError = ProgrammingError
175
+ IntegrityError = IntegrityError
176
+ DataError = DataError
177
+ NotSupportedError = NotSupportedError
178
+
179
+ def connect(self, **kwargs: Any) -> AsyncAdapt_pyathena_connection:
180
+ connection = await_only(AioConnection.create(**kwargs))
181
+ return AsyncAdapt_pyathena_connection(self, connection)
182
+
183
+
184
+ class AthenaAioDialect(AthenaDialect):
185
+ """Base async SQLAlchemy dialect for Amazon Athena.
186
+
187
+ Extends the synchronous ``AthenaDialect`` with async capability
188
+ by setting ``is_async = True`` and providing an adapted DBAPI module
189
+ that wraps ``AioConnection`` and async cursors via greenlet-based
190
+ ``await_only()``.
191
+
192
+ Subclasses (e.g. ``AthenaAioRestDialect``, ``AthenaAioPandasDialect``)
193
+ register concrete ``awsathena+aio*`` drivers.
194
+
195
+ See Also:
196
+ :class:`~pyathena.sqlalchemy.base.AthenaDialect`: Synchronous base dialect.
197
+ :class:`~pyathena.aio.connection.AioConnection`: Native async connection.
198
+ """
199
+
200
+ is_async = True
201
+ supports_statement_cache = True
202
+
203
+ @classmethod
204
+ def get_pool_class(cls, url: "URL") -> type:
205
+ return pool.AsyncAdaptedQueuePool
206
+
207
+ @classmethod
208
+ def import_dbapi(cls) -> "ModuleType":
209
+ return AsyncAdapt_pyathena_dbapi() # type: ignore[return-value]
210
+
211
+ @classmethod
212
+ def dbapi(cls) -> "ModuleType": # type: ignore[override]
213
+ return AsyncAdapt_pyathena_dbapi() # type: ignore[return-value]
214
+
215
+ def create_connect_args(self, url: "URL") -> Tuple[Tuple[str], MutableMapping[str, Any]]:
216
+ opts = self._create_connect_args(url)
217
+ self._connect_options = opts
218
+ return cast(Tuple[str], ()), opts
219
+
220
+ def get_driver_connection(self, connection: Any) -> Any:
221
+ return connection
@@ -0,0 +1,61 @@
1
+ # -*- coding: utf-8 -*-
2
+ from typing import TYPE_CHECKING
3
+
4
+ from pyathena.aio.sqlalchemy.base import AthenaAioDialect
5
+ from pyathena.util import strtobool
6
+
7
+ if TYPE_CHECKING:
8
+ from types import ModuleType
9
+
10
+
11
+ class AthenaAioPandasDialect(AthenaAioDialect):
12
+ """Async SQLAlchemy dialect for Amazon Athena with pandas DataFrame result format.
13
+
14
+ This dialect uses ``AioPandasCursor`` for native asyncio query execution
15
+ with pandas DataFrame results.
16
+
17
+ Connection URL Format:
18
+ ``awsathena+aiopandas://{access_key}:{secret_key}@athena.{region}.amazonaws.com/{schema}``
19
+
20
+ Query Parameters:
21
+ In addition to the base dialect parameters:
22
+ - unload: If "true", use UNLOAD for Parquet output
23
+ - engine: CSV parsing engine ("c", "python", or "pyarrow")
24
+ - chunksize: Number of rows per chunk for memory-efficient processing
25
+
26
+ Example:
27
+ >>> from sqlalchemy.ext.asyncio import create_async_engine
28
+ >>> engine = create_async_engine(
29
+ ... "awsathena+aiopandas://:@athena.us-west-2.amazonaws.com/default"
30
+ ... "?s3_staging_dir=s3://my-bucket/athena-results/"
31
+ ... "&unload=true&chunksize=10000"
32
+ ... )
33
+
34
+ See Also:
35
+ :class:`~pyathena.aio.pandas.cursor.AioPandasCursor`: The underlying async cursor.
36
+ :class:`~pyathena.aio.sqlalchemy.base.AthenaAioDialect`: Base async dialect.
37
+ """
38
+
39
+ driver = "aiopandas"
40
+ supports_statement_cache = True
41
+
42
+ def create_connect_args(self, url):
43
+ from pyathena.aio.pandas.cursor import AioPandasCursor
44
+
45
+ opts = super()._create_connect_args(url)
46
+ opts.update({"cursor_class": AioPandasCursor})
47
+ cursor_kwargs = {}
48
+ if "unload" in opts:
49
+ cursor_kwargs.update({"unload": bool(strtobool(opts.pop("unload")))})
50
+ if "engine" in opts:
51
+ cursor_kwargs.update({"engine": opts.pop("engine")})
52
+ if "chunksize" in opts:
53
+ cursor_kwargs.update({"chunksize": int(opts.pop("chunksize"))}) # type: ignore
54
+ if cursor_kwargs:
55
+ opts.update({"cursor_kwargs": cursor_kwargs})
56
+ self._connect_options = opts
57
+ return [[], opts]
58
+
59
+ @classmethod
60
+ def import_dbapi(cls) -> "ModuleType":
61
+ return super().import_dbapi()
@@ -0,0 +1,55 @@
1
+ # -*- coding: utf-8 -*-
2
+ from typing import TYPE_CHECKING
3
+
4
+ from pyathena.aio.sqlalchemy.base import AthenaAioDialect
5
+ from pyathena.util import strtobool
6
+
7
+ if TYPE_CHECKING:
8
+ from types import ModuleType
9
+
10
+
11
+ class AthenaAioPolarsDialect(AthenaAioDialect):
12
+ """Async SQLAlchemy dialect for Amazon Athena with Polars DataFrame result format.
13
+
14
+ This dialect uses ``AioPolarsCursor`` for native asyncio query execution
15
+ with Polars DataFrame results.
16
+
17
+ Connection URL Format:
18
+ ``awsathena+aiopolars://{access_key}:{secret_key}@athena.{region}.amazonaws.com/{schema}``
19
+
20
+ Query Parameters:
21
+ In addition to the base dialect parameters:
22
+ - unload: If "true", use UNLOAD for Parquet output
23
+
24
+ Example:
25
+ >>> from sqlalchemy.ext.asyncio import create_async_engine
26
+ >>> engine = create_async_engine(
27
+ ... "awsathena+aiopolars://:@athena.us-west-2.amazonaws.com/default"
28
+ ... "?s3_staging_dir=s3://my-bucket/athena-results/"
29
+ ... "&unload=true"
30
+ ... )
31
+
32
+ See Also:
33
+ :class:`~pyathena.aio.polars.cursor.AioPolarsCursor`: The underlying async cursor.
34
+ :class:`~pyathena.aio.sqlalchemy.base.AthenaAioDialect`: Base async dialect.
35
+ """
36
+
37
+ driver = "aiopolars"
38
+ supports_statement_cache = True
39
+
40
+ def create_connect_args(self, url):
41
+ from pyathena.aio.polars.cursor import AioPolarsCursor
42
+
43
+ opts = super()._create_connect_args(url)
44
+ opts.update({"cursor_class": AioPolarsCursor})
45
+ cursor_kwargs = {}
46
+ if "unload" in opts:
47
+ cursor_kwargs.update({"unload": bool(strtobool(opts.pop("unload")))})
48
+ if cursor_kwargs:
49
+ opts.update({"cursor_kwargs": cursor_kwargs})
50
+ self._connect_options = opts
51
+ return [[], opts]
52
+
53
+ @classmethod
54
+ def import_dbapi(cls) -> "ModuleType":
55
+ return super().import_dbapi()
@@ -0,0 +1,37 @@
1
+ # -*- coding: utf-8 -*-
2
+ from typing import TYPE_CHECKING
3
+
4
+ from pyathena.aio.sqlalchemy.base import AthenaAioDialect
5
+
6
+ if TYPE_CHECKING:
7
+ from types import ModuleType
8
+
9
+
10
+ class AthenaAioRestDialect(AthenaAioDialect):
11
+ """Async SQLAlchemy dialect for Amazon Athena using the standard REST API cursor.
12
+
13
+ This dialect uses ``AioCursor`` for native asyncio query execution.
14
+ Results are returned as Python tuples with type conversion handled by
15
+ the default converter.
16
+
17
+ Connection URL Format:
18
+ ``awsathena+aiorest://{access_key}:{secret_key}@athena.{region}.amazonaws.com/{schema}``
19
+
20
+ Example:
21
+ >>> from sqlalchemy.ext.asyncio import create_async_engine
22
+ >>> engine = create_async_engine(
23
+ ... "awsathena+aiorest://:@athena.us-west-2.amazonaws.com/default"
24
+ ... "?s3_staging_dir=s3://my-bucket/athena-results/"
25
+ ... )
26
+
27
+ See Also:
28
+ :class:`~pyathena.aio.cursor.AioCursor`: The underlying async cursor.
29
+ :class:`~pyathena.aio.sqlalchemy.base.AthenaAioDialect`: Base async dialect.
30
+ """
31
+
32
+ driver = "aiorest"
33
+ supports_statement_cache = True
34
+
35
+ @classmethod
36
+ def import_dbapi(cls) -> "ModuleType":
37
+ return super().import_dbapi()
@@ -0,0 +1,44 @@
1
+ # -*- coding: utf-8 -*-
2
+ from typing import TYPE_CHECKING
3
+
4
+ from pyathena.aio.sqlalchemy.base import AthenaAioDialect
5
+
6
+ if TYPE_CHECKING:
7
+ from types import ModuleType
8
+
9
+
10
+ class AthenaAioS3FSDialect(AthenaAioDialect):
11
+ """Async SQLAlchemy dialect for PyAthena with S3FS cursor.
12
+
13
+ This dialect uses ``AioS3FSCursor`` for native asyncio query execution
14
+ with S3 filesystem-based CSV result reading.
15
+
16
+ Connection URL Format:
17
+ ``awsathena+aios3fs://{access_key}:{secret_key}@athena.{region}.amazonaws.com/{schema}``
18
+
19
+ Example:
20
+ >>> from sqlalchemy.ext.asyncio import create_async_engine
21
+ >>> engine = create_async_engine(
22
+ ... "awsathena+aios3fs://:@athena.us-east-1.amazonaws.com/database"
23
+ ... "?s3_staging_dir=s3://bucket/path"
24
+ ... )
25
+
26
+ See Also:
27
+ :class:`~pyathena.aio.s3fs.cursor.AioS3FSCursor`: The underlying async cursor.
28
+ :class:`~pyathena.aio.sqlalchemy.base.AthenaAioDialect`: Base async dialect.
29
+ """
30
+
31
+ driver = "aios3fs"
32
+ supports_statement_cache = True
33
+
34
+ def create_connect_args(self, url):
35
+ from pyathena.aio.s3fs.cursor import AioS3FSCursor
36
+
37
+ opts = super()._create_connect_args(url)
38
+ opts.update({"cursor_class": AioS3FSCursor})
39
+ self._connect_options = opts
40
+ return [[], opts]
41
+
42
+ @classmethod
43
+ def import_dbapi(cls) -> "ModuleType":
44
+ return super().import_dbapi()
@@ -0,0 +1 @@
1
+ # -*- coding: utf-8 -*-
@@ -1,7 +1,7 @@
1
1
  # -*- coding: utf-8 -*-
2
2
  from __future__ import annotations
3
3
 
4
- from typing import TYPE_CHECKING, Any, Dict, List, Optional, Tuple, Type, Union, cast
4
+ from typing import TYPE_CHECKING, Any, Dict, List, Mapping, Optional, Tuple, Union, cast
5
5
 
6
6
  from sqlalchemy import exc, types, util
7
7
  from sqlalchemy.sql.compiler import (
@@ -31,12 +31,12 @@ if TYPE_CHECKING:
31
31
  UniqueConstraint,
32
32
  )
33
33
  from sqlalchemy.sql.ddl import CreateTable
34
- from sqlalchemy.sql.elements import FunctionElement
34
+ from sqlalchemy.sql.functions import Function
35
35
  from sqlalchemy.sql.selectable import GenerativeSelect
36
36
 
37
37
  from pyathena.sqlalchemy.base import AthenaDialect
38
38
 
39
- _DialectArgDict = Dict[str, Any]
39
+ _DialectArgDict = Mapping[str, Any]
40
40
  CreateColumn = Any
41
41
 
42
42
 
@@ -61,10 +61,10 @@ class AthenaTypeCompiler(GenericTypeCompiler):
61
61
  https://docs.aws.amazon.com/athena/latest/ug/data-types.html
62
62
  """
63
63
 
64
- def visit_FLOAT(self, type_: Type[Any], **kw) -> str: # noqa: N802
65
- return self.visit_REAL(type_, **kw)
64
+ def visit_FLOAT(self, type_: types.Float[Any], **kw: Any) -> str: # noqa: N802
65
+ return self.visit_REAL(type_, **kw) # type: ignore[arg-type]
66
66
 
67
- def visit_REAL(self, type_: Type[Any], **kw) -> str: # noqa: N802
67
+ def visit_REAL(self, type_: types.REAL[Any], **kw: Any) -> str: # noqa: N802
68
68
  return "FLOAT"
69
69
 
70
70
  def visit_DOUBLE(self, type_, **kw) -> str: # noqa: N802
@@ -73,78 +73,78 @@ class AthenaTypeCompiler(GenericTypeCompiler):
73
73
  def visit_DOUBLE_PRECISION(self, type_, **kw) -> str: # noqa: N802
74
74
  return "DOUBLE"
75
75
 
76
- def visit_NUMERIC(self, type_: Type[Any], **kw) -> str: # noqa: N802
77
- return self.visit_DECIMAL(type_, **kw)
76
+ def visit_NUMERIC(self, type_: types.Numeric[Any], **kw: Any) -> str: # noqa: N802
77
+ return self.visit_DECIMAL(type_, **kw) # type: ignore[arg-type]
78
78
 
79
- def visit_DECIMAL(self, type_: Type[Any], **kw) -> str: # noqa: N802
79
+ def visit_DECIMAL(self, type_: types.DECIMAL[Any], **kw: Any) -> str: # noqa: N802
80
80
  if type_.precision is None:
81
81
  return "DECIMAL"
82
82
  if type_.scale is None:
83
83
  return f"DECIMAL({type_.precision})"
84
84
  return f"DECIMAL({type_.precision}, {type_.scale})"
85
85
 
86
- def visit_TINYINT(self, type_: Type[Any], **kw) -> str: # noqa: N802
86
+ def visit_TINYINT(self, type_: types.Integer, **kw: Any) -> str: # noqa: N802
87
87
  return "TINYINT"
88
88
 
89
- def visit_INTEGER(self, type_: Type[Any], **kw) -> str: # noqa: N802
89
+ def visit_INTEGER(self, type_: types.Integer, **kw: Any) -> str: # noqa: N802
90
90
  return "INTEGER"
91
91
 
92
- def visit_SMALLINT(self, type_: Type[Any], **kw) -> str: # noqa: N802
92
+ def visit_SMALLINT(self, type_: types.SmallInteger, **kw: Any) -> str: # noqa: N802
93
93
  return "SMALLINT"
94
94
 
95
- def visit_BIGINT(self, type_: Type[Any], **kw) -> str: # noqa: N802
95
+ def visit_BIGINT(self, type_: types.BigInteger, **kw: Any) -> str: # noqa: N802
96
96
  return "BIGINT"
97
97
 
98
- def visit_TIMESTAMP(self, type_: Type[Any], **kw) -> str: # noqa: N802
98
+ def visit_TIMESTAMP(self, type_: types.TIMESTAMP, **kw: Any) -> str: # noqa: N802
99
99
  return "TIMESTAMP"
100
100
 
101
- def visit_DATETIME(self, type_: Type[Any], **kw) -> str: # noqa: N802
102
- return self.visit_TIMESTAMP(type_, **kw)
101
+ def visit_DATETIME(self, type_: types.DateTime, **kw: Any) -> str: # noqa: N802
102
+ return self.visit_TIMESTAMP(type_, **kw) # type: ignore[arg-type]
103
103
 
104
- def visit_DATE(self, type_: Type[Any], **kw) -> str: # noqa: N802
104
+ def visit_DATE(self, type_: types.Date, **kw: Any) -> str: # noqa: N802
105
105
  return "DATE"
106
106
 
107
- def visit_TIME(self, type_: Type[Any], **kw) -> str: # noqa: N802
107
+ def visit_TIME(self, type_: types.Time, **kw: Any) -> str: # noqa: N802
108
108
  raise exc.CompileError(f"Data type `{type_}` is not supported")
109
109
 
110
- def visit_CLOB(self, type_: Type[Any], **kw) -> str: # noqa: N802
111
- return self.visit_BINARY(type_, **kw)
110
+ def visit_CLOB(self, type_: types.CLOB, **kw: Any) -> str: # noqa: N802
111
+ return self.visit_BINARY(type_, **kw) # type: ignore[arg-type]
112
112
 
113
- def visit_NCLOB(self, type_: Type[Any], **kw) -> str: # noqa: N802
114
- return self.visit_BINARY(type_, **kw)
113
+ def visit_NCLOB(self, type_: types.Text, **kw: Any) -> str: # noqa: N802
114
+ return self.visit_BINARY(type_, **kw) # type: ignore[arg-type]
115
115
 
116
- def visit_CHAR(self, type_: Type[Any], **kw) -> str: # noqa: N802
116
+ def visit_CHAR(self, type_: types.CHAR, **kw: Any) -> str: # noqa: N802
117
117
  if type_.length:
118
- return cast(str, self._render_string_type(type_, "CHAR"))
118
+ return self._render_string_type("CHAR", type_.length, type_.collation)
119
119
  return "STRING"
120
120
 
121
- def visit_NCHAR(self, type_: Type[Any], **kw) -> str: # noqa: N802
122
- return self.visit_CHAR(type_, **kw)
121
+ def visit_NCHAR(self, type_: types.NCHAR, **kw: Any) -> str: # noqa: N802
122
+ return self.visit_CHAR(type_, **kw) # type: ignore[arg-type]
123
123
 
124
- def visit_VARCHAR(self, type_: Type[Any], **kw) -> str: # noqa: N802
124
+ def visit_VARCHAR(self, type_: types.String, **kw: Any) -> str: # noqa: N802
125
125
  if type_.length:
126
- return cast(str, self._render_string_type(type_, "VARCHAR"))
126
+ return self._render_string_type("VARCHAR", type_.length, type_.collation)
127
127
  return "STRING"
128
128
 
129
- def visit_NVARCHAR(self, type_: Type[Any], **kw) -> str: # noqa: N802
130
- return self.visit_VARCHAR(type_, **kw)
129
+ def visit_NVARCHAR(self, type_: types.NVARCHAR, **kw: Any) -> str: # noqa: N802
130
+ return self.visit_VARCHAR(type_, **kw) # type: ignore[arg-type]
131
131
 
132
- def visit_TEXT(self, type_: Type[Any], **kw) -> str: # noqa: N802
132
+ def visit_TEXT(self, type_: types.Text, **kw: Any) -> str: # noqa: N802
133
133
  return "STRING"
134
134
 
135
- def visit_BLOB(self, type_: Type[Any], **kw) -> str: # noqa: N802
136
- return self.visit_BINARY(type_, **kw)
135
+ def visit_BLOB(self, type_: types.LargeBinary, **kw: Any) -> str: # noqa: N802
136
+ return self.visit_BINARY(type_, **kw) # type: ignore[arg-type]
137
137
 
138
- def visit_BINARY(self, type_: Type[Any], **kw) -> str: # noqa: N802
138
+ def visit_BINARY(self, type_: types.BINARY, **kw: Any) -> str: # noqa: N802
139
139
  return "BINARY"
140
140
 
141
- def visit_VARBINARY(self, type_: Type[Any], **kw) -> str: # noqa: N802
142
- return self.visit_BINARY(type_, **kw)
141
+ def visit_VARBINARY(self, type_: types.VARBINARY, **kw: Any) -> str: # noqa: N802
142
+ return self.visit_BINARY(type_, **kw) # type: ignore[arg-type]
143
143
 
144
- def visit_BOOLEAN(self, type_: Type[Any], **kw) -> str: # noqa: N802
144
+ def visit_BOOLEAN(self, type_: types.Boolean, **kw: Any) -> str: # noqa: N802
145
145
  return "BOOLEAN"
146
146
 
147
- def visit_JSON(self, type_: Type[Any], **kw) -> str: # noqa: N802
147
+ def visit_JSON(self, type_: types.JSON, **kw: Any) -> str: # noqa: N802
148
148
  return "JSON"
149
149
 
150
150
  def visit_string(self, type_, **kw): # noqa: N802
@@ -219,10 +219,10 @@ class AthenaStatementCompiler(SQLCompiler):
219
219
  https://docs.aws.amazon.com/athena/latest/ug/ddl-sql-reference.html
220
220
  """
221
221
 
222
- def visit_char_length_func(self, fn: "FunctionElement[Any]", **kw):
222
+ def visit_char_length_func(self, fn: "Function[Any]", **kw: Any) -> str:
223
223
  return f"length{self.function_argspec(fn, **kw)}"
224
224
 
225
- def visit_filter_func(self, fn: "FunctionElement[Any]", **kw) -> str:
225
+ def visit_filter_func(self, fn: "Function[Any]", **kw: Any) -> str:
226
226
  """Compile Athena filter() function with lambda expressions.
227
227
 
228
228
  Supports syntax: filter(array_expr, lambda_expr)
@@ -370,7 +370,7 @@ class AthenaDDLCompiler(DDLCompiler):
370
370
  return f"COMMENT {self._escape_comment(comment)}"
371
371
 
372
372
  def _get_bucket_count(
373
- self, dialect_opts: "_DialectArgDict", connect_opts: Dict[str, Any]
373
+ self, dialect_opts: "_DialectArgDict", connect_opts: Mapping[str, Any]
374
374
  ) -> Optional[str]:
375
375
  if dialect_opts["bucket_count"]:
376
376
  bucket_count = dialect_opts["bucket_count"]
@@ -381,7 +381,7 @@ class AthenaDDLCompiler(DDLCompiler):
381
381
  return cast(str, bucket_count) if bucket_count is not None else None
382
382
 
383
383
  def _get_file_format(
384
- self, dialect_opts: "_DialectArgDict", connect_opts: Dict[str, Any]
384
+ self, dialect_opts: "_DialectArgDict", connect_opts: Mapping[str, Any]
385
385
  ) -> Optional[str]:
386
386
  if dialect_opts["file_format"]:
387
387
  file_format = dialect_opts["file_format"]
@@ -392,7 +392,7 @@ class AthenaDDLCompiler(DDLCompiler):
392
392
  return cast(Optional[str], file_format)
393
393
 
394
394
  def _get_file_format_specification(
395
- self, dialect_opts: "_DialectArgDict", connect_opts: Dict[str, Any]
395
+ self, dialect_opts: "_DialectArgDict", connect_opts: Mapping[str, Any]
396
396
  ) -> str:
397
397
  file_format = self._get_file_format(dialect_opts, connect_opts)
398
398
  text = []
@@ -401,7 +401,7 @@ class AthenaDDLCompiler(DDLCompiler):
401
401
  return "\n".join(text)
402
402
 
403
403
  def _get_row_format(
404
- self, dialect_opts: "_DialectArgDict", connect_opts: Dict[str, Any]
404
+ self, dialect_opts: "_DialectArgDict", connect_opts: Mapping[str, Any]
405
405
  ) -> Optional[str]:
406
406
  if dialect_opts["row_format"]:
407
407
  row_format = dialect_opts["row_format"]
@@ -412,7 +412,7 @@ class AthenaDDLCompiler(DDLCompiler):
412
412
  return cast(Optional[str], row_format)
413
413
 
414
414
  def _get_row_format_specification(
415
- self, dialect_opts: "_DialectArgDict", connect_opts: Dict[str, Any]
415
+ self, dialect_opts: "_DialectArgDict", connect_opts: Mapping[str, Any]
416
416
  ) -> str:
417
417
  row_format = self._get_row_format(dialect_opts, connect_opts)
418
418
  text = []
@@ -421,7 +421,7 @@ class AthenaDDLCompiler(DDLCompiler):
421
421
  return "\n".join(text)
422
422
 
423
423
  def _get_serde_properties(
424
- self, dialect_opts: "_DialectArgDict", connect_opts: Dict[str, Any]
424
+ self, dialect_opts: "_DialectArgDict", connect_opts: Mapping[str, Any]
425
425
  ) -> Optional[Union[str, Dict[str, Any]]]:
426
426
  if dialect_opts["serdeproperties"]:
427
427
  serde_properties = dialect_opts["serdeproperties"]
@@ -432,7 +432,7 @@ class AthenaDDLCompiler(DDLCompiler):
432
432
  return cast(Optional[str], serde_properties)
433
433
 
434
434
  def _get_serde_properties_specification(
435
- self, dialect_opts: "_DialectArgDict", connect_opts: Dict[str, Any]
435
+ self, dialect_opts: "_DialectArgDict", connect_opts: Mapping[str, Any]
436
436
  ) -> str:
437
437
  serde_properties = self._get_serde_properties(dialect_opts, connect_opts)
438
438
  text = []
@@ -446,7 +446,7 @@ class AthenaDDLCompiler(DDLCompiler):
446
446
  return "\n".join(text)
447
447
 
448
448
  def _get_table_location(
449
- self, table: "Table", dialect_opts: "_DialectArgDict", connect_opts: Dict[str, Any]
449
+ self, table: "Table", dialect_opts: "_DialectArgDict", connect_opts: Mapping[str, Any]
450
450
  ) -> Optional[str]:
451
451
  if dialect_opts["location"]:
452
452
  location = cast(str, dialect_opts["location"])
@@ -464,7 +464,7 @@ class AthenaDDLCompiler(DDLCompiler):
464
464
  return location
465
465
 
466
466
  def _get_table_location_specification(
467
- self, table: "Table", dialect_opts: "_DialectArgDict", connect_opts: Dict[str, Any]
467
+ self, table: "Table", dialect_opts: "_DialectArgDict", connect_opts: Mapping[str, Any]
468
468
  ) -> str:
469
469
  location = self._get_table_location(table, dialect_opts, connect_opts)
470
470
  text = []
@@ -482,7 +482,7 @@ class AthenaDDLCompiler(DDLCompiler):
482
482
  return "\n".join(text)
483
483
 
484
484
  def _get_table_properties(
485
- self, dialect_opts: "_DialectArgDict", connect_opts: Dict[str, Any]
485
+ self, dialect_opts: "_DialectArgDict", connect_opts: Mapping[str, Any]
486
486
  ) -> Optional[Union[Dict[str, str], str]]:
487
487
  if dialect_opts["tblproperties"]:
488
488
  table_properties = cast(str, dialect_opts["tblproperties"])
@@ -493,7 +493,7 @@ class AthenaDDLCompiler(DDLCompiler):
493
493
  return table_properties
494
494
 
495
495
  def _get_compression(
496
- self, dialect_opts: "_DialectArgDict", connect_opts: Dict[str, Any]
496
+ self, dialect_opts: "_DialectArgDict", connect_opts: Mapping[str, Any]
497
497
  ) -> Optional[str]:
498
498
  if dialect_opts["compression"]:
499
499
  compression = cast(str, dialect_opts["compression"])
@@ -504,7 +504,7 @@ class AthenaDDLCompiler(DDLCompiler):
504
504
  return compression
505
505
 
506
506
  def _get_table_properties_specification(
507
- self, dialect_opts: "_DialectArgDict", connect_opts: Dict[str, Any]
507
+ self, dialect_opts: "_DialectArgDict", connect_opts: Mapping[str, Any]
508
508
  ) -> str:
509
509
  properties = self._get_table_properties(dialect_opts, connect_opts)
510
510
  if properties:
@@ -554,26 +554,22 @@ class AthenaDDLCompiler(DDLCompiler):
554
554
  text.append(f"{self._get_comment_specification(column.comment)}")
555
555
  return " ".join(text)
556
556
 
557
- def visit_check_constraint(self, constraint: "CheckConstraint", **kw) -> Optional[str]:
558
- return None
557
+ def visit_check_constraint(self, constraint: "CheckConstraint", **kw: Any) -> str:
558
+ return ""
559
559
 
560
- def visit_column_check_constraint(self, constraint: "CheckConstraint", **kw) -> Optional[str]:
561
- return None
560
+ def visit_column_check_constraint(self, constraint: "CheckConstraint", **kw: Any) -> str:
561
+ return ""
562
562
 
563
- def visit_foreign_key_constraint(
564
- self, constraint: "ForeignKeyConstraint", **kw
565
- ) -> Optional[str]:
566
- return None
563
+ def visit_foreign_key_constraint(self, constraint: "ForeignKeyConstraint", **kw: Any) -> str:
564
+ return ""
567
565
 
568
- def visit_primary_key_constraint(
569
- self, constraint: "PrimaryKeyConstraint", **kw
570
- ) -> Optional[str]:
571
- return None
566
+ def visit_primary_key_constraint(self, constraint: "PrimaryKeyConstraint", **kw: Any) -> str:
567
+ return ""
572
568
 
573
- def visit_unique_constraint(self, constraint: "UniqueConstraint", **kw) -> Optional[str]:
574
- return None
569
+ def visit_unique_constraint(self, constraint: "UniqueConstraint", **kw: Any) -> str:
570
+ return ""
575
571
 
576
- def _get_connect_option_partitions(self, connect_opts: Dict[str, Any]) -> List[str]:
572
+ def _get_connect_option_partitions(self, connect_opts: Mapping[str, Any]) -> List[str]:
577
573
  if connect_opts:
578
574
  partition = cast(str, connect_opts.get("partition"))
579
575
  partitions = partition.split(",") if partition else []
@@ -581,7 +577,7 @@ class AthenaDDLCompiler(DDLCompiler):
581
577
  partitions = []
582
578
  return partitions
583
579
 
584
- def _get_connect_option_buckets(self, connect_opts: Dict[str, Any]) -> List[str]:
580
+ def _get_connect_option_buckets(self, connect_opts: Mapping[str, Any]) -> List[str]:
585
581
  if connect_opts:
586
582
  bucket = cast(str, connect_opts.get("cluster"))
587
583
  buckets = bucket.split(",") if bucket else []
@@ -624,7 +620,7 @@ class AthenaDDLCompiler(DDLCompiler):
624
620
  table: "Table",
625
621
  is_iceberg: bool,
626
622
  create_columns: List["CreateColumn"],
627
- connect_opts: Dict[str, Any],
623
+ connect_opts: Mapping[str, Any],
628
624
  ) -> Tuple[List[str], List[str], List[str]]:
629
625
  columns, partitions, buckets = [], [], []
630
626
  conn_partitions = self._get_connect_option_partitions(connect_opts)
@@ -42,9 +42,17 @@ awsathena = "pyathena.sqlalchemy.base:AthenaDialect"
42
42
  "awsathena.arrow" = "pyathena.sqlalchemy.arrow:AthenaArrowDialect"
43
43
  "awsathena.polars" = "pyathena.sqlalchemy.polars:AthenaPolarsDialect"
44
44
  "awsathena.s3fs" = "pyathena.sqlalchemy.s3fs:AthenaS3FSDialect"
45
+ "awsathena.aiorest" = "pyathena.aio.sqlalchemy.rest:AthenaAioRestDialect"
46
+ "awsathena.aiopandas" = "pyathena.aio.sqlalchemy.pandas:AthenaAioPandasDialect"
47
+ "awsathena.aioarrow" = "pyathena.aio.sqlalchemy.arrow:AthenaAioArrowDialect"
48
+ "awsathena.aiopolars" = "pyathena.aio.sqlalchemy.polars:AthenaAioPolarsDialect"
49
+ "awsathena.aios3fs" = "pyathena.aio.sqlalchemy.s3fs:AthenaAioS3FSDialect"
45
50
 
46
51
  [project.optional-dependencies]
47
52
  sqlalchemy = ["sqlalchemy>=1.0.0"]
53
+ aiosqlalchemy = [
54
+ "sqlalchemy[asyncio]>=2.0.0",
55
+ ]
48
56
  pandas = [
49
57
  "pandas>=1.3.0; python_version<'3.13'",
50
58
  "pandas>=2.3.0; python_version>='3.13'",
@@ -59,7 +67,7 @@ polars = [
59
67
 
60
68
  [dependency-groups]
61
69
  dev = [
62
- "sqlalchemy>=1.0.0",
70
+ "sqlalchemy[asyncio]>=1.0.0",
63
71
  "pandas>=1.3.0; python_version<'3.13'",
64
72
  "pandas>=2.3.0; python_version>='3.13'",
65
73
  "numpy>=1.26.0; python_version<'3.13'",
@@ -170,7 +178,7 @@ exclude = [
170
178
  legacy_tox_ini = """
171
179
  [tox]
172
180
  isolated_build = true
173
- envlist = py{310,311,312,313,314}
181
+ envlist = py{310,311,312,313,314}-{pyathena,sqla,sqla_async}
174
182
 
175
183
  [gh-actions]
176
184
  python =
@@ -180,6 +188,12 @@ python =
180
188
  3.13: py313
181
189
  3.14: py314
182
190
 
191
+ [gh-actions:env]
192
+ TEST_TYPE =
193
+ pyathena: pyathena
194
+ sqla: sqla
195
+ sqla_async: sqla_async
196
+
183
197
  [testenv]
184
198
  allowlist_externals =
185
199
  uv
@@ -187,8 +201,9 @@ allowlist_externals =
187
201
  make
188
202
  commands =
189
203
  uv sync --group dev
190
- make test
191
- make test-sqla
204
+ pyathena: make test
205
+ sqla: make test-sqla
206
+ sqla_async: make test-sqla-async
192
207
  passenv =
193
208
  TOXENV
194
209
  AWS_*
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes