PyAthena 3.32.0__tar.gz → 3.33.0__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 (88) hide show
  1. {pyathena-3.32.0 → pyathena-3.33.0}/PKG-INFO +2 -2
  2. {pyathena-3.32.0 → pyathena-3.33.0}/pyathena/_version.py +2 -2
  3. {pyathena-3.32.0 → pyathena-3.33.0}/pyathena/sqlalchemy/base.py +7 -2
  4. {pyathena-3.32.0 → pyathena-3.33.0}/pyathena/sqlalchemy/compiler.py +95 -13
  5. {pyathena-3.32.0 → pyathena-3.33.0}/pyproject.toml +5 -5
  6. {pyathena-3.32.0 → pyathena-3.33.0}/.gitignore +0 -0
  7. {pyathena-3.32.0 → pyathena-3.33.0}/LICENSE +0 -0
  8. {pyathena-3.32.0 → pyathena-3.33.0}/README.md +0 -0
  9. {pyathena-3.32.0 → pyathena-3.33.0}/pyathena/__init__.py +0 -0
  10. {pyathena-3.32.0 → pyathena-3.33.0}/pyathena/aio/__init__.py +0 -0
  11. {pyathena-3.32.0 → pyathena-3.33.0}/pyathena/aio/arrow/__init__.py +0 -0
  12. {pyathena-3.32.0 → pyathena-3.33.0}/pyathena/aio/arrow/cursor.py +0 -0
  13. {pyathena-3.32.0 → pyathena-3.33.0}/pyathena/aio/common.py +0 -0
  14. {pyathena-3.32.0 → pyathena-3.33.0}/pyathena/aio/connection.py +0 -0
  15. {pyathena-3.32.0 → pyathena-3.33.0}/pyathena/aio/cursor.py +0 -0
  16. {pyathena-3.32.0 → pyathena-3.33.0}/pyathena/aio/pandas/__init__.py +0 -0
  17. {pyathena-3.32.0 → pyathena-3.33.0}/pyathena/aio/pandas/cursor.py +0 -0
  18. {pyathena-3.32.0 → pyathena-3.33.0}/pyathena/aio/polars/__init__.py +0 -0
  19. {pyathena-3.32.0 → pyathena-3.33.0}/pyathena/aio/polars/cursor.py +0 -0
  20. {pyathena-3.32.0 → pyathena-3.33.0}/pyathena/aio/result_set.py +0 -0
  21. {pyathena-3.32.0 → pyathena-3.33.0}/pyathena/aio/s3fs/__init__.py +0 -0
  22. {pyathena-3.32.0 → pyathena-3.33.0}/pyathena/aio/s3fs/cursor.py +0 -0
  23. {pyathena-3.32.0 → pyathena-3.33.0}/pyathena/aio/spark/__init__.py +0 -0
  24. {pyathena-3.32.0 → pyathena-3.33.0}/pyathena/aio/spark/cursor.py +0 -0
  25. {pyathena-3.32.0 → pyathena-3.33.0}/pyathena/aio/sqlalchemy/__init__.py +0 -0
  26. {pyathena-3.32.0 → pyathena-3.33.0}/pyathena/aio/sqlalchemy/arrow.py +0 -0
  27. {pyathena-3.32.0 → pyathena-3.33.0}/pyathena/aio/sqlalchemy/base.py +0 -0
  28. {pyathena-3.32.0 → pyathena-3.33.0}/pyathena/aio/sqlalchemy/pandas.py +0 -0
  29. {pyathena-3.32.0 → pyathena-3.33.0}/pyathena/aio/sqlalchemy/polars.py +0 -0
  30. {pyathena-3.32.0 → pyathena-3.33.0}/pyathena/aio/sqlalchemy/rest.py +0 -0
  31. {pyathena-3.32.0 → pyathena-3.33.0}/pyathena/aio/sqlalchemy/s3fs.py +0 -0
  32. {pyathena-3.32.0 → pyathena-3.33.0}/pyathena/aio/util.py +0 -0
  33. {pyathena-3.32.0 → pyathena-3.33.0}/pyathena/arrow/__init__.py +0 -0
  34. {pyathena-3.32.0 → pyathena-3.33.0}/pyathena/arrow/async_cursor.py +0 -0
  35. {pyathena-3.32.0 → pyathena-3.33.0}/pyathena/arrow/converter.py +0 -0
  36. {pyathena-3.32.0 → pyathena-3.33.0}/pyathena/arrow/cursor.py +0 -0
  37. {pyathena-3.32.0 → pyathena-3.33.0}/pyathena/arrow/result_set.py +0 -0
  38. {pyathena-3.32.0 → pyathena-3.33.0}/pyathena/arrow/util.py +0 -0
  39. {pyathena-3.32.0 → pyathena-3.33.0}/pyathena/async_cursor.py +0 -0
  40. {pyathena-3.32.0 → pyathena-3.33.0}/pyathena/common.py +0 -0
  41. {pyathena-3.32.0 → pyathena-3.33.0}/pyathena/connection.py +0 -0
  42. {pyathena-3.32.0 → pyathena-3.33.0}/pyathena/converter.py +0 -0
  43. {pyathena-3.32.0 → pyathena-3.33.0}/pyathena/cursor.py +0 -0
  44. {pyathena-3.32.0 → pyathena-3.33.0}/pyathena/error.py +0 -0
  45. {pyathena-3.32.0 → pyathena-3.33.0}/pyathena/filesystem/__init__.py +0 -0
  46. {pyathena-3.32.0 → pyathena-3.33.0}/pyathena/filesystem/s3.py +0 -0
  47. {pyathena-3.32.0 → pyathena-3.33.0}/pyathena/filesystem/s3_async.py +0 -0
  48. {pyathena-3.32.0 → pyathena-3.33.0}/pyathena/filesystem/s3_executor.py +0 -0
  49. {pyathena-3.32.0 → pyathena-3.33.0}/pyathena/filesystem/s3_object.py +0 -0
  50. {pyathena-3.32.0 → pyathena-3.33.0}/pyathena/formatter.py +0 -0
  51. {pyathena-3.32.0 → pyathena-3.33.0}/pyathena/model.py +0 -0
  52. {pyathena-3.32.0 → pyathena-3.33.0}/pyathena/pandas/__init__.py +0 -0
  53. {pyathena-3.32.0 → pyathena-3.33.0}/pyathena/pandas/async_cursor.py +0 -0
  54. {pyathena-3.32.0 → pyathena-3.33.0}/pyathena/pandas/converter.py +0 -0
  55. {pyathena-3.32.0 → pyathena-3.33.0}/pyathena/pandas/cursor.py +0 -0
  56. {pyathena-3.32.0 → pyathena-3.33.0}/pyathena/pandas/result_set.py +0 -0
  57. {pyathena-3.32.0 → pyathena-3.33.0}/pyathena/pandas/util.py +0 -0
  58. {pyathena-3.32.0 → pyathena-3.33.0}/pyathena/parser.py +0 -0
  59. {pyathena-3.32.0 → pyathena-3.33.0}/pyathena/polars/__init__.py +0 -0
  60. {pyathena-3.32.0 → pyathena-3.33.0}/pyathena/polars/async_cursor.py +0 -0
  61. {pyathena-3.32.0 → pyathena-3.33.0}/pyathena/polars/converter.py +0 -0
  62. {pyathena-3.32.0 → pyathena-3.33.0}/pyathena/polars/cursor.py +0 -0
  63. {pyathena-3.32.0 → pyathena-3.33.0}/pyathena/polars/result_set.py +0 -0
  64. {pyathena-3.32.0 → pyathena-3.33.0}/pyathena/polars/util.py +0 -0
  65. {pyathena-3.32.0 → pyathena-3.33.0}/pyathena/py.typed +0 -0
  66. {pyathena-3.32.0 → pyathena-3.33.0}/pyathena/result_set.py +0 -0
  67. {pyathena-3.32.0 → pyathena-3.33.0}/pyathena/s3fs/__init__.py +0 -0
  68. {pyathena-3.32.0 → pyathena-3.33.0}/pyathena/s3fs/async_cursor.py +0 -0
  69. {pyathena-3.32.0 → pyathena-3.33.0}/pyathena/s3fs/converter.py +0 -0
  70. {pyathena-3.32.0 → pyathena-3.33.0}/pyathena/s3fs/cursor.py +0 -0
  71. {pyathena-3.32.0 → pyathena-3.33.0}/pyathena/s3fs/reader.py +0 -0
  72. {pyathena-3.32.0 → pyathena-3.33.0}/pyathena/s3fs/result_set.py +0 -0
  73. {pyathena-3.32.0 → pyathena-3.33.0}/pyathena/spark/__init__.py +0 -0
  74. {pyathena-3.32.0 → pyathena-3.33.0}/pyathena/spark/async_cursor.py +0 -0
  75. {pyathena-3.32.0 → pyathena-3.33.0}/pyathena/spark/common.py +0 -0
  76. {pyathena-3.32.0 → pyathena-3.33.0}/pyathena/spark/cursor.py +0 -0
  77. {pyathena-3.32.0 → pyathena-3.33.0}/pyathena/sqlalchemy/__init__.py +0 -0
  78. {pyathena-3.32.0 → pyathena-3.33.0}/pyathena/sqlalchemy/arrow.py +0 -0
  79. {pyathena-3.32.0 → pyathena-3.33.0}/pyathena/sqlalchemy/constants.py +0 -0
  80. {pyathena-3.32.0 → pyathena-3.33.0}/pyathena/sqlalchemy/pandas.py +0 -0
  81. {pyathena-3.32.0 → pyathena-3.33.0}/pyathena/sqlalchemy/polars.py +0 -0
  82. {pyathena-3.32.0 → pyathena-3.33.0}/pyathena/sqlalchemy/preparer.py +0 -0
  83. {pyathena-3.32.0 → pyathena-3.33.0}/pyathena/sqlalchemy/requirements.py +0 -0
  84. {pyathena-3.32.0 → pyathena-3.33.0}/pyathena/sqlalchemy/rest.py +0 -0
  85. {pyathena-3.32.0 → pyathena-3.33.0}/pyathena/sqlalchemy/s3fs.py +0 -0
  86. {pyathena-3.32.0 → pyathena-3.33.0}/pyathena/sqlalchemy/types.py +0 -0
  87. {pyathena-3.32.0 → pyathena-3.33.0}/pyathena/sqlalchemy/util.py +0 -0
  88. {pyathena-3.32.0 → pyathena-3.33.0}/pyathena/util.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: PyAthena
3
- Version: 3.32.0
3
+ Version: 3.33.0
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/
@@ -38,7 +38,7 @@ Classifier: Programming Language :: Python :: 3.13
38
38
  Classifier: Programming Language :: Python :: 3.14
39
39
  Classifier: Topic :: Database :: Front-Ends
40
40
  Requires-Python: >=3.10
41
- Requires-Dist: boto3>=1.38.2
41
+ Requires-Dist: boto3>=1.41.2
42
42
  Requires-Dist: botocore>=1.41.2
43
43
  Requires-Dist: fsspec
44
44
  Requires-Dist: python-dateutil
@@ -18,7 +18,7 @@ version_tuple: tuple[int | str, ...]
18
18
  commit_id: str | None
19
19
  __commit_id__: str | None
20
20
 
21
- __version__ = version = '3.32.0'
22
- __version_tuple__ = version_tuple = (3, 32, 0)
21
+ __version__ = version = '3.33.0'
22
+ __version_tuple__ = version_tuple = (3, 33, 0)
23
23
 
24
24
  __commit_id__ = commit_id = None
@@ -207,8 +207,7 @@ class AthenaDialect(DefaultDialect):
207
207
  # awsathena+rest://
208
208
  # {aws_access_key_id}:{aws_secret_access_key}@athena.{region_name}.amazonaws.com:443/
209
209
  # {schema_name}?s3_staging_dir={s3_staging_dir}&...
210
- self._connect_options = self._create_connect_args(url)
211
- return cast(tuple[str], ()), self._connect_options
210
+ return cast(tuple[str], ()), self._create_connect_args(url)
212
211
 
213
212
  def _create_connect_args(self, url: URL) -> dict[str, Any]:
214
213
  opts: dict[str, Any] = {
@@ -238,6 +237,12 @@ class AthenaDialect(DefaultDialect):
238
237
  opts.update({"result_reuse_enable": bool(strtobool(opts["result_reuse_enable"]))})
239
238
  if "result_reuse_minutes" in opts:
240
239
  opts.update({"result_reuse_minutes": int(opts["result_reuse_minutes"])})
240
+ # Store on the dialect so compilers can consult connection options
241
+ # (e.g. catalog_name for S3 Tables detection). Assigned here rather than
242
+ # in create_connect_args because subclass dialects call this method
243
+ # directly and mutate the returned dict afterwards; sharing the same
244
+ # object keeps _connect_options in sync with their updates.
245
+ self._connect_options = opts
241
246
  return opts
242
247
 
243
248
  @reflection.cache
@@ -39,6 +39,13 @@ if TYPE_CHECKING:
39
39
  _DialectArgDict = Mapping[str, Any]
40
40
  CreateColumn = Any
41
41
 
42
+ # Prefix of the Athena data catalog name registered for an Amazon S3 Tables
43
+ # table bucket (e.g. ``s3tablescatalog/my-bucket``). It is selected via the
44
+ # connection ``catalog_name``. S3 Tables are Iceberg-backed and use managed
45
+ # storage, so their CREATE TABLE statements must not include a LOCATION clause.
46
+ # https://docs.aws.amazon.com/AmazonS3/latest/userguide/s3-tables-integrations-query-athena.html
47
+ S3_TABLES_CATALOG_PREFIX = "s3tablescatalog/"
48
+
42
49
 
43
50
  class AthenaTypeCompiler(GenericTypeCompiler):
44
51
  """Type compiler for Amazon Athena SQL types.
@@ -305,6 +312,10 @@ class AthenaDDLCompiler(DDLCompiler):
305
312
 
306
313
  - External table creation (EXTERNAL keyword for Hive-style tables)
307
314
  - Iceberg table creation (managed tables with ACID support)
315
+ - Amazon S3 Tables (Iceberg-backed, managed storage): set the connection
316
+ ``catalog_name`` to ``s3tablescatalog/<table-bucket>`` and use the
317
+ namespace as the table ``schema``. The LOCATION clause is omitted since
318
+ storage is managed.
308
319
  - File formats: PARQUET, ORC, TEXTFILE, JSON, AVRO, etc.
309
320
  - Row formats with SerDe specifications
310
321
  - Compression settings for various file formats
@@ -445,6 +456,74 @@ class AthenaDDLCompiler(DDLCompiler):
445
456
  text.append(")")
446
457
  return "\n".join(text)
447
458
 
459
+ @staticmethod
460
+ def _is_s3_tables_catalog(connect_opts: Mapping[str, Any]) -> bool:
461
+ """Return whether the connection targets an Amazon S3 Tables catalog.
462
+
463
+ S3 Tables are queried by setting the connection ``catalog_name`` to
464
+ ``s3tablescatalog/<table-bucket>`` and using the namespace as the table
465
+ ``schema`` (a two-part ``namespace.table`` identifier). Athena rejects a
466
+ three-part ``catalog.namespace.table`` identifier in DDL, so the catalog
467
+ must be selected at the connection level. Such tables use managed
468
+ storage, so their CREATE TABLE statement must omit the LOCATION clause.
469
+
470
+ Args:
471
+ connect_opts: The dialect connection options.
472
+
473
+ Returns:
474
+ True if ``catalog_name`` names an S3 Tables catalog.
475
+ """
476
+ if not connect_opts:
477
+ return False
478
+ catalog = connect_opts.get("catalog_name") or ""
479
+ # Athena resolves catalog names case-insensitively.
480
+ return catalog.lower().startswith(S3_TABLES_CATALOG_PREFIX)
481
+
482
+ def _is_iceberg_table(
483
+ self, dialect_opts: _DialectArgDict, connect_opts: Mapping[str, Any]
484
+ ) -> bool:
485
+ """Return whether the table properties declare an Iceberg table.
486
+
487
+ Args:
488
+ dialect_opts: The table's ``awsathena_*`` dialect options.
489
+ connect_opts: The dialect connection options.
490
+
491
+ Returns:
492
+ True if the rendered TBLPROPERTIES set ``table_type`` to Iceberg.
493
+ """
494
+ table_properties = self._get_table_properties_specification(
495
+ dialect_opts, connect_opts
496
+ ).lower()
497
+ return ("table_type" in table_properties) and ("iceberg" in table_properties)
498
+
499
+ def _validate_s3_tables_create_table(
500
+ self, dialect_opts: _DialectArgDict, connect_opts: Mapping[str, Any]
501
+ ) -> None:
502
+ """Validate a CREATE TABLE compiled against an S3 Tables catalog.
503
+
504
+ S3 Tables support only Iceberg tables on managed storage, so the table
505
+ must declare ``table_type`` ICEBERG and must not specify a location.
506
+ Raising here surfaces a clear client-side error instead of emitting DDL
507
+ that Athena would reject.
508
+
509
+ Args:
510
+ dialect_opts: The table's ``awsathena_*`` dialect options.
511
+ connect_opts: The dialect connection options.
512
+
513
+ Raises:
514
+ exc.CompileError: If the table is not Iceberg or specifies a location.
515
+ """
516
+ if not self._is_iceberg_table(dialect_opts, connect_opts):
517
+ raise exc.CompileError(
518
+ "S3 Tables support only Iceberg tables; specify the dialect keyword "
519
+ "argument `awsathena_tblproperties={'table_type': 'ICEBERG'}`"
520
+ )
521
+ if dialect_opts["location"]:
522
+ raise exc.CompileError(
523
+ "S3 Tables use managed storage and do not accept a table location; "
524
+ "remove the dialect keyword argument `awsathena_location`"
525
+ )
526
+
448
527
  def _get_table_location(
449
528
  self, table: Table, dialect_opts: _DialectArgDict, connect_opts: Mapping[str, Any]
450
529
  ) -> str | None:
@@ -662,12 +741,7 @@ class AthenaDDLCompiler(DDLCompiler):
662
741
  dialect = cast("AthenaDialect", self.dialect)
663
742
  connect_opts = dialect._connect_options
664
743
 
665
- table_properties = self._get_table_properties_specification(
666
- dialect_opts, connect_opts
667
- ).lower()
668
- is_iceberg = False
669
- if ("table_type" in table_properties) and ("iceberg" in table_properties):
670
- is_iceberg = True
744
+ is_iceberg = self._is_iceberg_table(dialect_opts, connect_opts)
671
745
 
672
746
  # https://docs.aws.amazon.com/athena/latest/ug/querying-iceberg-creating-tables.html
673
747
  text = ["\nCREATE TABLE"] if is_iceberg else ["\nCREATE EXTERNAL TABLE"]
@@ -705,11 +779,19 @@ class AthenaDDLCompiler(DDLCompiler):
705
779
  dialect_opts: _DialectArgDict = table.dialect_options["awsathena"]
706
780
  dialect = cast("AthenaDialect", self.dialect)
707
781
  connect_opts = dialect._connect_options
708
- text = [
709
- self._get_row_format_specification(dialect_opts, connect_opts),
710
- self._get_serde_properties_specification(dialect_opts, connect_opts),
711
- self._get_file_format_specification(dialect_opts, connect_opts),
712
- self._get_table_location_specification(table, dialect_opts, connect_opts),
713
- self._get_table_properties_specification(dialect_opts, connect_opts),
714
- ]
782
+ if self._is_s3_tables_catalog(connect_opts):
783
+ # S3 Tables are managed Iceberg tables: ROW FORMAT, SERDEPROPERTIES,
784
+ # STORED AS, and LOCATION are not accepted, so emit only TBLPROPERTIES.
785
+ self._validate_s3_tables_create_table(dialect_opts, connect_opts)
786
+ text = [
787
+ self._get_table_properties_specification(dialect_opts, connect_opts),
788
+ ]
789
+ else:
790
+ text = [
791
+ self._get_row_format_specification(dialect_opts, connect_opts),
792
+ self._get_serde_properties_specification(dialect_opts, connect_opts),
793
+ self._get_file_format_specification(dialect_opts, connect_opts),
794
+ self._get_table_location_specification(table, dialect_opts, connect_opts),
795
+ self._get_table_properties_specification(dialect_opts, connect_opts),
796
+ ]
715
797
  return "\n".join([t for t in text if t])
@@ -5,7 +5,7 @@ authors = [
5
5
  {name = "laughingman7743", email = "laughingman7743@gmail.com"},
6
6
  ]
7
7
  dependencies = [
8
- "boto3>=1.38.2",
8
+ "boto3>=1.41.2",
9
9
  "botocore>=1.41.2",
10
10
  "tenacity>=4.1.0",
11
11
  "fsspec",
@@ -223,12 +223,12 @@ TEST_TYPE =
223
223
  allowlist_externals =
224
224
  uv
225
225
  uvx
226
- make
226
+ just
227
227
  commands =
228
228
  uv sync --group dev
229
- pyathena: make test/pyathena
230
- sqla: make test/sqla
231
- sqla_async: make test/sqla-async
229
+ pyathena: just test pyathena
230
+ sqla: just test sqla
231
+ sqla_async: just test sqla-async
232
232
  passenv =
233
233
  TOXENV
234
234
  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