PyAthena 3.29.0__tar.gz → 3.29.1__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.
- {pyathena-3.29.0 → pyathena-3.29.1}/PKG-INFO +3 -1
- {pyathena-3.29.0 → pyathena-3.29.1}/pyathena/_version.py +2 -2
- pyathena-3.29.1/pyathena/aio/sqlalchemy/arrow.py +55 -0
- pyathena-3.29.1/pyathena/aio/sqlalchemy/base.py +221 -0
- pyathena-3.29.1/pyathena/aio/sqlalchemy/pandas.py +61 -0
- pyathena-3.29.1/pyathena/aio/sqlalchemy/polars.py +55 -0
- pyathena-3.29.1/pyathena/aio/sqlalchemy/rest.py +37 -0
- pyathena-3.29.1/pyathena/aio/sqlalchemy/s3fs.py +44 -0
- pyathena-3.29.1/pyathena/sqlalchemy/__init__.py +1 -0
- {pyathena-3.29.0 → pyathena-3.29.1}/pyathena/sqlalchemy/compiler.py +65 -69
- {pyathena-3.29.0 → pyathena-3.29.1}/pyproject.toml +10 -1
- {pyathena-3.29.0 → pyathena-3.29.1}/.gitignore +0 -0
- {pyathena-3.29.0 → pyathena-3.29.1}/LICENSE +0 -0
- {pyathena-3.29.0 → pyathena-3.29.1}/README.md +0 -0
- {pyathena-3.29.0 → pyathena-3.29.1}/pyathena/__init__.py +0 -0
- {pyathena-3.29.0 → pyathena-3.29.1}/pyathena/aio/__init__.py +0 -0
- {pyathena-3.29.0 → pyathena-3.29.1}/pyathena/aio/arrow/__init__.py +0 -0
- {pyathena-3.29.0 → pyathena-3.29.1}/pyathena/aio/arrow/cursor.py +0 -0
- {pyathena-3.29.0 → pyathena-3.29.1}/pyathena/aio/common.py +0 -0
- {pyathena-3.29.0 → pyathena-3.29.1}/pyathena/aio/connection.py +0 -0
- {pyathena-3.29.0 → pyathena-3.29.1}/pyathena/aio/cursor.py +0 -0
- {pyathena-3.29.0 → pyathena-3.29.1}/pyathena/aio/pandas/__init__.py +0 -0
- {pyathena-3.29.0 → pyathena-3.29.1}/pyathena/aio/pandas/cursor.py +0 -0
- {pyathena-3.29.0 → pyathena-3.29.1}/pyathena/aio/polars/__init__.py +0 -0
- {pyathena-3.29.0 → pyathena-3.29.1}/pyathena/aio/polars/cursor.py +0 -0
- {pyathena-3.29.0 → pyathena-3.29.1}/pyathena/aio/result_set.py +0 -0
- {pyathena-3.29.0 → pyathena-3.29.1}/pyathena/aio/s3fs/__init__.py +0 -0
- {pyathena-3.29.0 → pyathena-3.29.1}/pyathena/aio/s3fs/cursor.py +0 -0
- {pyathena-3.29.0 → pyathena-3.29.1}/pyathena/aio/spark/__init__.py +0 -0
- {pyathena-3.29.0 → pyathena-3.29.1}/pyathena/aio/spark/cursor.py +0 -0
- {pyathena-3.29.0/pyathena/arrow → pyathena-3.29.1/pyathena/aio/sqlalchemy}/__init__.py +0 -0
- {pyathena-3.29.0 → pyathena-3.29.1}/pyathena/aio/util.py +0 -0
- {pyathena-3.29.0/pyathena/filesystem → pyathena-3.29.1/pyathena/arrow}/__init__.py +0 -0
- {pyathena-3.29.0 → pyathena-3.29.1}/pyathena/arrow/async_cursor.py +0 -0
- {pyathena-3.29.0 → pyathena-3.29.1}/pyathena/arrow/converter.py +0 -0
- {pyathena-3.29.0 → pyathena-3.29.1}/pyathena/arrow/cursor.py +0 -0
- {pyathena-3.29.0 → pyathena-3.29.1}/pyathena/arrow/result_set.py +0 -0
- {pyathena-3.29.0 → pyathena-3.29.1}/pyathena/arrow/util.py +0 -0
- {pyathena-3.29.0 → pyathena-3.29.1}/pyathena/async_cursor.py +0 -0
- {pyathena-3.29.0 → pyathena-3.29.1}/pyathena/common.py +0 -0
- {pyathena-3.29.0 → pyathena-3.29.1}/pyathena/connection.py +0 -0
- {pyathena-3.29.0 → pyathena-3.29.1}/pyathena/converter.py +0 -0
- {pyathena-3.29.0 → pyathena-3.29.1}/pyathena/cursor.py +0 -0
- {pyathena-3.29.0 → pyathena-3.29.1}/pyathena/error.py +0 -0
- {pyathena-3.29.0/pyathena/s3fs → pyathena-3.29.1/pyathena/filesystem}/__init__.py +0 -0
- {pyathena-3.29.0 → pyathena-3.29.1}/pyathena/filesystem/s3.py +0 -0
- {pyathena-3.29.0 → pyathena-3.29.1}/pyathena/filesystem/s3_object.py +0 -0
- {pyathena-3.29.0 → pyathena-3.29.1}/pyathena/formatter.py +0 -0
- {pyathena-3.29.0 → pyathena-3.29.1}/pyathena/model.py +0 -0
- {pyathena-3.29.0 → pyathena-3.29.1}/pyathena/pandas/__init__.py +0 -0
- {pyathena-3.29.0 → pyathena-3.29.1}/pyathena/pandas/async_cursor.py +0 -0
- {pyathena-3.29.0 → pyathena-3.29.1}/pyathena/pandas/converter.py +0 -0
- {pyathena-3.29.0 → pyathena-3.29.1}/pyathena/pandas/cursor.py +0 -0
- {pyathena-3.29.0 → pyathena-3.29.1}/pyathena/pandas/result_set.py +0 -0
- {pyathena-3.29.0 → pyathena-3.29.1}/pyathena/pandas/util.py +0 -0
- {pyathena-3.29.0 → pyathena-3.29.1}/pyathena/polars/__init__.py +0 -0
- {pyathena-3.29.0 → pyathena-3.29.1}/pyathena/polars/async_cursor.py +0 -0
- {pyathena-3.29.0 → pyathena-3.29.1}/pyathena/polars/converter.py +0 -0
- {pyathena-3.29.0 → pyathena-3.29.1}/pyathena/polars/cursor.py +0 -0
- {pyathena-3.29.0 → pyathena-3.29.1}/pyathena/polars/result_set.py +0 -0
- {pyathena-3.29.0 → pyathena-3.29.1}/pyathena/polars/util.py +0 -0
- {pyathena-3.29.0 → pyathena-3.29.1}/pyathena/py.typed +0 -0
- {pyathena-3.29.0 → pyathena-3.29.1}/pyathena/result_set.py +0 -0
- {pyathena-3.29.0/pyathena/spark → pyathena-3.29.1/pyathena/s3fs}/__init__.py +0 -0
- {pyathena-3.29.0 → pyathena-3.29.1}/pyathena/s3fs/async_cursor.py +0 -0
- {pyathena-3.29.0 → pyathena-3.29.1}/pyathena/s3fs/converter.py +0 -0
- {pyathena-3.29.0 → pyathena-3.29.1}/pyathena/s3fs/cursor.py +0 -0
- {pyathena-3.29.0 → pyathena-3.29.1}/pyathena/s3fs/reader.py +0 -0
- {pyathena-3.29.0 → pyathena-3.29.1}/pyathena/s3fs/result_set.py +0 -0
- {pyathena-3.29.0/pyathena/sqlalchemy → pyathena-3.29.1/pyathena/spark}/__init__.py +0 -0
- {pyathena-3.29.0 → pyathena-3.29.1}/pyathena/spark/async_cursor.py +0 -0
- {pyathena-3.29.0 → pyathena-3.29.1}/pyathena/spark/common.py +0 -0
- {pyathena-3.29.0 → pyathena-3.29.1}/pyathena/spark/cursor.py +0 -0
- {pyathena-3.29.0 → pyathena-3.29.1}/pyathena/sqlalchemy/arrow.py +0 -0
- {pyathena-3.29.0 → pyathena-3.29.1}/pyathena/sqlalchemy/base.py +0 -0
- {pyathena-3.29.0 → pyathena-3.29.1}/pyathena/sqlalchemy/constants.py +0 -0
- {pyathena-3.29.0 → pyathena-3.29.1}/pyathena/sqlalchemy/pandas.py +0 -0
- {pyathena-3.29.0 → pyathena-3.29.1}/pyathena/sqlalchemy/polars.py +0 -0
- {pyathena-3.29.0 → pyathena-3.29.1}/pyathena/sqlalchemy/preparer.py +0 -0
- {pyathena-3.29.0 → pyathena-3.29.1}/pyathena/sqlalchemy/requirements.py +0 -0
- {pyathena-3.29.0 → pyathena-3.29.1}/pyathena/sqlalchemy/rest.py +0 -0
- {pyathena-3.29.0 → pyathena-3.29.1}/pyathena/sqlalchemy/s3fs.py +0 -0
- {pyathena-3.29.0 → pyathena-3.29.1}/pyathena/sqlalchemy/types.py +0 -0
- {pyathena-3.29.0 → pyathena-3.29.1}/pyathena/sqlalchemy/util.py +0 -0
- {pyathena-3.29.0 → pyathena-3.29.1}/pyathena/util.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: PyAthena
|
|
3
|
-
Version: 3.29.
|
|
3
|
+
Version: 3.29.1
|
|
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'
|
|
@@ -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.
|
|
32
|
-
__version_tuple__ = version_tuple = (3, 29,
|
|
31
|
+
__version__ = version = '3.29.1'
|
|
32
|
+
__version_tuple__ = version_tuple = (3, 29, 1)
|
|
33
33
|
|
|
34
34
|
__commit_id__ = commit_id = None
|
|
@@ -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,
|
|
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.
|
|
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 =
|
|
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_:
|
|
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_:
|
|
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_:
|
|
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_:
|
|
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_:
|
|
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_:
|
|
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_:
|
|
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_:
|
|
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_:
|
|
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_:
|
|
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_:
|
|
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_:
|
|
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_:
|
|
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_:
|
|
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_:
|
|
116
|
+
def visit_CHAR(self, type_: types.CHAR, **kw: Any) -> str: # noqa: N802
|
|
117
117
|
if type_.length:
|
|
118
|
-
return
|
|
118
|
+
return self._render_string_type("CHAR", type_.length, type_.collation)
|
|
119
119
|
return "STRING"
|
|
120
120
|
|
|
121
|
-
def visit_NCHAR(self, type_:
|
|
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_:
|
|
124
|
+
def visit_VARCHAR(self, type_: types.String, **kw: Any) -> str: # noqa: N802
|
|
125
125
|
if type_.length:
|
|
126
|
-
return
|
|
126
|
+
return self._render_string_type("VARCHAR", type_.length, type_.collation)
|
|
127
127
|
return "STRING"
|
|
128
128
|
|
|
129
|
-
def visit_NVARCHAR(self, type_:
|
|
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_:
|
|
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_:
|
|
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_:
|
|
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_:
|
|
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_:
|
|
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_:
|
|
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: "
|
|
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: "
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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) ->
|
|
558
|
-
return
|
|
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) ->
|
|
561
|
-
return
|
|
560
|
+
def visit_column_check_constraint(self, constraint: "CheckConstraint", **kw: Any) -> str:
|
|
561
|
+
return ""
|
|
562
562
|
|
|
563
|
-
def visit_foreign_key_constraint(
|
|
564
|
-
|
|
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
|
-
|
|
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) ->
|
|
574
|
-
return
|
|
569
|
+
def visit_unique_constraint(self, constraint: "UniqueConstraint", **kw: Any) -> str:
|
|
570
|
+
return ""
|
|
575
571
|
|
|
576
|
-
def _get_connect_option_partitions(self, connect_opts:
|
|
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:
|
|
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:
|
|
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'",
|
|
@@ -189,6 +197,7 @@ commands =
|
|
|
189
197
|
uv sync --group dev
|
|
190
198
|
make test
|
|
191
199
|
make test-sqla
|
|
200
|
+
make test-sqla-async
|
|
192
201
|
passenv =
|
|
193
202
|
TOXENV
|
|
194
203
|
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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|