Repository navigation
v4.0.0
v4.0.0 changes the project's copyright notices. Please read the Copyright and license notices section first and review NOTICE. The license itself is still the MIT License.
v4.0.0 is a major release from master, which was branched from the 3.x line at v3.36.0. It drops Python 3.10, raises the minimum versions of the core and optional dependencies, makes connection and cursor settings keyword-only, and rejects arguments that used to be ignored silently. It also contains a large number of fixes and behavior changes across the cursors, the SQLAlchemy dialect, Spark, and the S3 filesystem.
Read Breaking changes before upgrading. The usage guide has migration sections for the keyword-only arguments, the worker settings, and the environment fallbacks ("... in PyAthena 4.0" in docs/usage.md), and the Spark and S3FS guides have their own 4.0 sections. Users who need Python 3.10, pandas 2, an older pyarrow or Polars, or SQLAlchemy 1.x can stay on the 3.x maintenance line (latest v3.39.0).
Several fixes in this release were already backported to 3.x in v3.37.0, v3.38.0, and v3.39.0. They are listed under Already in 3.x releases at the end and marked in "What's Changed"; users upgrading from v3.39.0 already have them.
Thanks to @mvanhorn for the STRUCT DDL fix (#870), to @aminghadersohi for the bare awsathena:// scheme fix (#839) and for reporting #854, #855, and #857, and to @innicoder for reporting the memory issue #417, which #1001 addresses.
Copyright and license notices
- The copyright holder in
LICENSEchanges from "laughingman7743" to "The PyAthena authors" (Copyright 2017 The PyAthena authors). The license terms are unchanged (MIT) (#809, #790). - The package metadata lists "The PyAthena authors" as the author and laughingman7743 as the maintainer, and uses PEP 639 license metadata: the license expression
MITwithLICENSEandNOTICEas license files, so both files are included in the sdist and wheel (#809). - Files that contain only original work by the PyAthena authors carry the header
Copyright <year> The PyAthena authorswithSPDX-License-Identifier: MIT. Existing files written solely by the maintainer received it, new original files must carry it, and a check enforces the headers across the repository (#805, #808). - The new
NOTICEfile acknowledges PyHive, which informed PyAthena's early development, and lists the parts that still follow it, with PyHive's Apache License 2.0 notice. It also records the other third-party material (the GitHub Actions OIDC CloudFormation template andstrtobool) and explains which files do not carry the header, such as files with contributions from other contributors, whose authorship is recorded in the Git history (#802). The per-file classification is recorded in #790. - Redistributors and packagers should include
NOTICEtogether withLICENSE.
Breaking changes
Python and dependencies
- Python 3.10 is no longer supported; PyAthena requires Python 3.11 or later (#884, #864).
- Minimum versions of the core dependencies (#1036, #1055, #1126, #1125):
boto3>=1.43.31andbotocore>=1.43.31(was 1.41.2). botocore 1.43.31 is the first release with the S3 annotation operations used by multipart copies.fsspec>=2026.9.0(no lower bound before).tenacity>=8.0.1(was 4.1.0). Older releases breakretry_api_call(): releases before 6.1.0 fail on Python 3.11+, and 8.0.0 raisesTypeErrorfromafter_logon the first retry. (Also in v3.39.0.)python-dateutilis no longer a dependency;tzdatais required.
- Minimum versions of the extras (#858, #885, #828):
sqlalchemy:sqlalchemy>=2.0.0(was 1.0.0). Environments pinned to SQLAlchemy 1.4 cannot installpyathena[sqlalchemy]4.0.0.pandas:pandas>=3.0.0.arrow:pyarrow>=22.0.0.polars:polars>=1.39.0(was 1.0.0). This applies even to users who never setchunksize: Polars 1.34–1.36 silently truncate chunked reads on errors, and Polars before 1.39 fill a local file cache that is not released.
- The package is marked
Development Status :: 5 - Production/Stable(#904, #865).
Connections and cursors
- Connection settings and cursor constructor settings are keyword-only; positional settings raise
TypeError.execute()keepsoperationandparameterspositional and requires keywords after them.connection.cursor(CursorClass)still works (#1101, #1092). - Unknown cursor constructor keywords, given directly or through the connection's
cursor_kwargs, raiseTypeError.execute()of the default, dict, Arrow, S3FS, and Spark cursors raisesTypeErrorfor unknown keywords before changing cursor state or starting a query. The pandas and Polars cursors still pass unusedexecute()keywords to the reader, which rejects unknown ones when it reads the results.connection.cursor(OtherCursor)raises ifcursor_kwargscontain options thatOtherCursordoes not accept. A cursor subclass that passes its own settings fromcursor_kwargson to the base constructor must consume them first (#1102, #1093). - The S3 read worker argument of
PandasCursor,PolarsCursor,AioPandasCursor, andAioPolarsCursoris renamed frommax_workerstos3_max_workers, in the constructor and inexecute(). InAsyncPandasCursorandAsyncPolarsCursor,max_workerssizes only the cursor's thread pool ands3_max_workerssets the S3 read workers; pass both toAsyncPolarsCursorto keep its former values. The old name raisesTypeError(inexecute(), when the result set is created after the query has run) (#1103, #1094). arraysizepassed at cursor creation now takes effect and is validated:Cursor,DictCursor,AioCursor,AioDictCursor,AsyncCursor, andAsyncDictCursorraiseProgrammingErrorfor values above 1000, and every SQL cursor raises it for zero or negative values. Before, the value was ignored byCursor,DictCursor,AioCursor, andAioDictCursor(since v2.0.0), so a small value now means moreGetQueryResultscalls. The Arrow, pandas, Polars, and S3FS cursors still accept values above 1000 (#901, #897).connect(endpoint_url=...)no longer applies to S3. Setups that serve Athena and S3 from one endpoint (for example LocalStack) needAWS_ENDPOINT_URL_S3orAWS_ENDPOINT_URL. This affects the result sets,S3FileSystem(connection=...), Polarsstorage_options, the Spark cursors, andto_sql()(#1058, #1011).- The connection's
work_groupfollows the same environment fallback rule ass3_staging_dir: only an omitted argument orNonereadsAWS_ATHENA_WORK_GROUP; an explicit empty string skips the fallback and means no work group (#1108, #1096). DefaultCSVReader(S3FS cursor) is renamed toEmptyStringAsNullCSVReader, without an alias (#1107, #1097).- Removed compatibility APIs (#1100, #1095, #1088, #1087):
pyathena.formatter._escape_presto; use_escape_trino.- The individual execution keywords of the private
BaseCursor._execute()/AioBaseCursor._execute(); passoptions=ExecuteOptions(...). pyathena.util.strtobool()returnsboolinstead ofint; callint(strtobool(value))where an integer is required.
SQLAlchemy
- Resolve the bare awsathena scheme to the REST dialect by @aminghadersohi in #839 (also in v3.38.0)
pyathena.sqlalchemy.types.get_double_typeis removed; usesqlalchemy.types.DOUBLE(#860).AthenaDialectdeclares SQLAlchemy 2.0'stype_compiler_clsinstead of the legacytype_compiler, so code that readsAthenaDialect.type_compileron the class no longer finds the compiler class (#895, #894).- Type rendering is split into a Hive DDL compiler and a Trino DML compiler (#961):
TypeEngine.compile(dialect)rendersAthenaStructasSTRUCT<name:type>instead ofROW(name type), andInteger(including subclasses andTypeDecorators over it) asINTin all DDL.Integer().with_variant(OtherType(), "awsathena")renders the variant inCREATE TABLE.- A
JSONcolumn, anAthenaStructwithout fields, andNullTypein DDL raiseCompileErrorinstead of emitting DDL that Athena rejects.CAST(... AS JSON)is unchanged.CLOB/NCLOBrenderSTRINGinstead ofBINARY.
Spark
SparkCursor,AsyncSparkCursor, andAioSparkCursorraiseNotSupportedErrorfor anyparametersvalue other thanNone, and theirexecute()no longer takeswork_group(TypeError, even forNone). Before, both were silently ignored (#1108, #1096).- Closing a Spark cursor created with
session_idno longer terminates that session. Passterminate_session_on_close=Trueto keep the previous behavior (#844, #793, #796).
S3 filesystem
S3FileSystem.PATTERN_PATHandS3FileSystem.parse_path()/AioS3FileSystem.parse_path()are removed. UseS3Path.parse(path)and itsbucket,key, andversion_idattributes (#1100, #1052).S3FileSystem.OBJECT_ACLS,BUCKET_ACLS, and theMULTIPART_UPLOAD_*limits are removed; useS3Core.OBJECT_ACLS/S3Core.BUCKET_ACLS(for examplefs.core.OBJECT_ACLS) (#1090).- The
max_workerkeyword ofopen()andpipe_file()is removed; passmax_workers(#948, #925). open()for writing or appending (wb,ab,xb) raisesValueErrorfor a path with?versionId=(#958, #936).find(maxdepth=n)listsnlevels instead ofn + 1, andmaxdepth=0raisesValueError.rm(),glob(),du(),expand_path(),copy(), andget()passmaxdepththroughfind(), sorm(path, recursive=True, maxdepth=1)no longer deletes the objects one level below the path. Callers relying on the old numbering needmaxdepth + 1(#956, #933).S3FileSystem.request_kwargsis a read-only property that returns a copy; passrequester_pays=Trueto the constructor instead of changing it (#1060).S3Filecallssubmit_to()on its executor. An executor passed toS3Filethat does not subclassS3Executormust provide it (#1113).
Dependencies and packaging
- New public API:
S3Core(S3FileSystem.core,AioS3FileSystem.core) with typed listing entries, pages, and delete/multipart/copy/tagging/ACL/presign requests;S3Path;S3MultipartWriter;S3Executor.submit_to();connect(s3_config=...)andConnection.s3_client;TERMINAL_STATESon the query and calculation execution models (#1060, #1052, #1085, #1113, #1058, #881). - Every public API in
pyathena/has a Google-style docstring, checked with ruff (#919).
Connections and cursors
- With the default
kill_on_interrupt=True, an interrupt duringexecute()of a SQL cursor now propagates asKeyboardInterrupt/asyncio.CancelledErrorafter the query is stopped, instead of surfacing asOperationalErroror being lost when the query succeeded.asyncio.wait_for()now raisesTimeoutError. An interrupt while a query is being started waits forStartQueryExecutionand stops the query it started (#853, #840). - An explicit
cursor()argument now overrides the same key in the connection'scursor_kwargs; before,cursor_kwargswon silently (#872, #859). dict_typepassed to one dict cursor no longer changes the row type of other dict cursors, anddict_type=Noneuses the default (#930, #923).- One S3 client and connection pool per connection is shared across result sets and filesystems, and
Connection.close()closes the network connections of its clients. SQLAlchemy URLs withuse_ssl=falsenow disable TLS, asconnect(use_ssl=False)does (#1058, #1011). - Result-set filesystems are kept out of fsspec's instance cache, and the pandas result set reads through one filesystem and owns the CSV file it reads (#1001, #417, #978).
- Managed query result storage (no output location): the pandas, Arrow, and Polars cursors read the results as an S3 CSV result file would be read, with the same types and values, and the cursor's converter applies to them, including a custom one. For example, pandas
datecolumns aredatetime64,array/map/roware Athena text, a result without rows has its columns, andresult_set_type_hintsno longer applies to these cursors on managed storage (#1061, #1028, #1042). ArrowCursorreadstimestampcolumns astimestamp[us]instead oftimestamp[ms]; the Arrow and (unchunked) Polars cursors read timestamps with more than 6 fractional digits, truncated to the column's unit (#1061).- Time zones (#1033, #1018, #1030, #1125):
time with time zonevalues are awaredatetime.timeobjects inCursor,S3FSCursor,ArrowCursor,PolarsCursor, andPandasCursor(strings or naive times before).timestamp with time zonevalues are awaredatetimeobjects in the Arrow and Polars cursors and in everyPandasCursorcolumn.- Zone-name values use
zoneinfo.ZoneInfoinstead ofdateutil.tz.tzfile; instants and offsets are unchanged. WithPandasCursor, the dtype changes from, for example,datetime64[us, tzfile('/usr/share/zoneinfo/America/New_York')]todatetime64[us, America/New_York]. A zone that the time zone database cannot provide logs a warning. TIME(0)andTIME(p)withp > 6no longer raiseValueError.
- pandas results:
- Without
chunksize, repeatedas_pandas()/as_polars()/as_arrow()calls return the whole result,as_pandas()/as_polars()return the same object untilclose()or the nextexecute(), and fetches after them return the rows (#1005, #935). - JSON columns with NULL keep exact decoded values with
Nonefor NULL (objectdtype), and integer columns on managed storage areInt64(#1044, #1027). CSV reads with the C engine and PyAthena's JSON converter default tolow_memory=Falseto avoidDtypeWarning(#1080, #1078). - With
engine="pyarrow", CSV results are read withpyarrow.csv: multi-line values that cross a read block are read correctly, and string columns are no longer changed ("007"used to become"7.0") (#1057, #1048, #1065, #1062). - Columns with the same name keep their own values and types (#1050, #1032, #1066, #1051; also for the Arrow and Polars cursors).
execute()can override the cursor's read options, andAsyncPandasCursor(block_size=..., cache_type=..., auto_optimize_chunksize=...)now takes effect (#1022, #938).to_sql()uses the connection'sconfig/s3_configand, withconnect(session=...), that session's credentials (#1068, #1067).
- Without
- JSON:
PandasCursorandArrowCursorreturnNonefor SQL NULLjsonvalues; with managed storage, json columns inas_arrow()/as_polars()are string columns, andS3FSCursoruses the configured converter (#1010, #934). PolarsCursorwithunload=Trueandchunksizedescribes and returns the Parquet columns (broken since v3.24.0) (#1012, #1007).ArrowCursormaps Arrowdecimal32/decimal64columns todecimal(decimal256 also in v3.38.0) (#900, #888).get_table_metadata(),list_table_metadata(), andlist_databases()answer a throttled request from the AWS Glue Data Catalog inAwsDataCatalogand S3 Tables catalogs. The fallback needsglue:GetTable,glue:GetTables, andglue:GetDatabases; without them, the Athena request is retried as before after one failed Glue call and a warning. Turn it off withconnect(glue_metadata_fallback=False)orglue_metadata_fallback=falsein a SQLAlchemy URL.retry_api_call()/async_retry_api_call()accept a keyword-onlystop_onpredicate (#803, #786, #890, #887).executemany()reports the total affected row count instead of-1(#768, #754).
SQLAlchemy
executemanyinserts are batched into multi-rowINSERTstatements (insertmanyvalues, 100 rows per statement by default;rowcountstays the total). A failed statement fails one page of rows and earlier pages stay inserted, andbefore_cursor_executelisteners see multi-row statements. Very wide rows may exceed Athena's query length limit; lowerinsertmanyvalues_page_sizeor setuse_insertmanyvalues=False. Both options are also accepted in the URL query and byengine_from_config(#838, #835, #843).- Datetime literals and precision (#818, #756):
- Generic
DateandDateTimerender typedDATE '...'/TIMESTAMP '...'literals, andliteral_executeno longer raisesNotImplementedError. - Timestamp literals keep microseconds (6 digits when the value has a sub-millisecond part, 3 otherwise), and
cast()toDateTimerendersTIMESTAMP(6). Hive CTAS andUNLOAD(including cursors withunload=True) rejecttimestamp(6)output columns; usecast(col, AthenaTimestamp(precision=3))orCAST(... AS TIMESTAMP(3))there. AthenaTimestamp(precision=p)(0–6) casts toTIMESTAMP(p)and truncates bound values.
- Generic
cast()to aTypeDecorator(or awith_variant()type) casts to its implementation type (#836, #829).- Reflection:
- Top-level
map<...>columns reflect asAthenaMapandstruct<...>/row(...)columns asAthenaStructwith their fields; unparsable types reflect asNullTypewith a warning (#995, #886, #857; reflection also in v3.38.0). - Throttled metadata requests fall back to
information_schema, and listed metadata is reused within one Inspector (#777, #780). The dialect's own queries always use an API cursor, whatevercursor_classis configured, and a federated catalog's missing table is resolved frominformation_schema(#798, #781, #783, #785). has_table()results are cached untilInspector.clear_cache(), and table names up to 255 characters can be reflected (#771).- A missing table in a federated catalog raises
NoSuchTableErrorfromget_table_comment()andget_table_options()(#801). An empty Glue comment reflects asNone(#782).
- Top-level
JSONcolumns return the value Athena returns:type_coerce(literal_column("CAST('<text>' AS JSON)"), JSON)returns thestrinstead of decoding it a second time (usejson_parse()for the parsed value), JSON objects and arrays no longer raiseTypeError, andjson_deserializerno longer applies tojsonresults (#1010, #934).CREATE TABLEcompression (#1025, #1020, #1015, #937):- Parquet, text, and JSON tables created with
awsathena_compressionnow get the requested codec: the DDL emitsparquet.compression/write.compression, which Athena applies (it ignored the former properties and used GZIP).to_sql()tables also carryparquet.compression. Fix existing tables withALTER TABLE ... SET TBLPROPERTIES. file_formatis matched case-insensitively.
- Parquet, text, and JSON tables created with
Spark
- After
kill_on_interruptcancellation, Spark cursors re-raiseKeyboardInterrupt/asyncio.CancelledErrorinstead of returning or raisingOperationalError(#833, #797). - Spark cursors send a generated
ClientRequestTokenunlessclient_request_tokenis given, and an interrupt while a calculation is being started stops that calculation (#861, #841).
S3 filesystem
The filesystem was audited against fsspec and s3fs and restructured around the typed S3Core. Behavior changes, by area:
- Paths and versions (#1055, #979, #957, #960, #1024, #1084):
- Keys containing
?can be used; only a version query such as?versionId=xis read as a version. ls(versions=True)returns version-qualified names, andopen(),cat_file(), andget_file()acceptversion_id(#958).- Each explicit version is cached separately, and deleting a version invalidates the object path.
- Keys containing
- Listings and lookups (#993, #1006, #1002):
find(path, withdirs=True)includespathitself when it is a directory,prefixlevels count towardmaxdepth, andfind(object_path, maxdepth=n)returns the object.- A cached parent listing answers
info(),exists(),isfile(),size(), andopen(). Objects written by others after the listing was cached are reported missing untilrefresh=Trueorinvalidate_cache(), as in s3fs. exists()returnsTruefor a bucket that HeadBucket denies.
- Reads (#987, #998, #990):
cat_file()/cat_ranges()returnb""for empty, reversed, or past-the-end ranges, and a negativestartwithout an end reads a suffix range.open(prefix, "rb")andget_file()of a prefix raiseFileNotFoundError.get_file()follows fsspec's contract for directories and file objects.
- Writes (#992, #968, #1003, #1017, #1009, #1038, #1047):
- A failed
pipe()/put_file()leaves the existing object unchanged and aborts its multipart upload. open(path, "xb"),put_file(mode="create"), andpipe_file(mode="create")raiseFileExistsErrorfor an existing object.- Writes needing more than 10,000 parts and block sizes above 5 GiB raise
ValueError; failed multipart uploads wait for running parts before aborting, and an upload whose abort failed is kept fordiscard(). pipe_file(..., compression=...)works for small values, and an unsupported codec raisesValueError.
- A failed
- Copies and moves (#1036, #955, #1013, #993):
- Copies over 5 GiB keep the source's content headers, metadata, tags, and annotations by default, as CopyObject does. They need
s3:GetObjectTaggingand the annotation permissions; useTaggingDirective="REPLACE"orAnnotationDirective="EXCLUDE"to skip them. mv()deletes only the copied source objects, moves directory trees, and raisesValueErrorfor conflicting destinations.
- Copies over 5 GiB keep the source's content headers, metadata, tags, and annotations by default, as CopyObject does. They need
- Request parameters (#1000, #1024, #985):
- Parameters are sent only to the operations that accept them, and parameters given to
open(),pipe(), orput()take precedence overs3_additional_kwargs. - Lookup parameters such as
ExpectedBucketOwnerand SSE-C keys are sent with lookups. - The s3fs
profileargument is used as the boto3 profile name.
- Parameters are sent only to the operations that accept them, and parameters given to
rm()raisesOSErrorwhen S3 cannot delete some objects, andrm("s3://bucket", recursive=True)onAioS3FileSystem(or a bucket path with//) raisesValueErrorinstead of emptying the bucket (#986).setxattr()keeps system metadata, storage class, and encryption, and rejects version paths (#1016, #975).AioS3FileSystemmatchesS3FileSystemin transactions,open(), andtouch()(#988). Interrupted or cancelled multipart creations, part scheduling, and copies abort their uploads (#1056, #1074, #1085, #1113, #1122).S3Objectfollows the mapping contract: a missing key raisesKeyErrorand an unknown attribute raisesAttributeError(#991).- Multipart uploads and object versions listed for a key path include only that key and the keys under it (#989).
CI, tests, and development
- The AWS cost of the integration tests is substantially lower without reducing real-service coverage (#834, #846).
- The Test workflow runs the PyAthena, SQLAlchemy, and Spark suites in parallel, only for pull requests marked ready and only for the suites whose paths changed, on the newest Python version; fork pull requests skip the AWS jobs (#814, #837, #863, #906, #913, #789).
- The Release workflow runs every suite on every supported Python version before publishing (#862).
- Test isolation and stability: each test session gets its own S3 Tables namespace, the executemany and partition tests use their own tables, cache-hit tests run in the test work group, and tests are rerun once after a service-side Athena query failure (#815, #873, #817, #811).
- Test setup: the shared test tables are defined in Python with generated data, fixtures are set up only in processes that run tests, and SQLAlchemy compliance fixture rows are inserted once for read-only classes (#874, #866, #867).
- Leaked test databases are swept daily (#779, #907).
- Test coverage: SQLAlchemy parameter and SQL rendering compliance tests are re-enabled, and the test conventions and regression assertions were audited (#825, #1082).
- The locked data libraries were upgraded and the code is type-checked per interpreter (#822).
- New cursor benchmark tooling with a fleet runner (#773, #813, #823, #824, #827).
- MIT license headers, a license header check, and a collective
LICENSEattribution with PEP 639 metadata;NOTICEacknowledges PyHive (#802, #805, #808, #809).
Already in 3.x releases
These changes are included in 4.0.0 and were already released on the 3.x line:
- v3.37.0: SQLAlchemy ARRAY support (#774, #775, #776), STRUCT DDL (#870),
DOUBLEcasts (#826), CSV binary NULLs (#770). - v3.38.0: bare
awsathena://scheme (#839),to_sqltable name case (#800), synchronous iteration of asyncio cursors raisesTypeError(#899), qmark cache (#959),as_pandas()with auto chunking (#950), Spark session fixes (#830, #831, #832), ArrowCursor fixes (#1023, #1031, #1045),PolarsDataFrameIteratorclose (#1029), PandasCursor DDL text (#1075), S3 append and multipart fixes (#928, #929, #947, #955), and parts of #900, #995, and #1033. - v3.39.0: Arrow session credentials (#1116), Iceberg detection (#1043), native array and typed JSON fixes (#1054, #1127), parts of #1005 and #1033, and the
tenacityfloor (#1126).
What's Changed
Dependencies and packaging
- Require polars>=1.39.0 for chunked PolarsCursor reads by @laughingman7743 in #828
- Require pandas 3.0 and pyarrow 22.0 by @laughingman7743 in #885
- Mark the package as Production/Stable by @laughingman7743 in #904
- Drop Python 3.10 support by @laughingman7743 in #884
- Review the package metadata for 4.0.0 by @laughingman7743 in #1126 (also in v3.39.0 (tenacity floor only))
Connections, cursors, and result sets
- Replace name-mangled helpers with single-underscore methods by @laughingman7743 in #881
- Share the pure cursor-base logic between the sync and asyncio cursors by @laughingman7743 in #883
- Restore WithResultSet as a mixin with sync and async sibling bases by @laughingman7743 in #909
- Share the asyncio fetch methods in WithAsyncFetch by @laughingman7743 in #914
- Avoid duplicate default ExecuteOptions construction by @laughingman7743 in #1110
- Find an existing table in to_sql whatever the name's case by @laughingman7743 in #800 (also in v3.38.0)
- Answer throttled metadata requests from Glue in the cursor by @laughingman7743 in #803
- Let explicit cursor() arguments override cursor_kwargs by @laughingman7743 in #872
- Reject synchronous iteration of the asyncio cursors and result sets by @laughingman7743 in #899 (also in v3.38.0)
- Honor the arraysize argument of the SQL cursors by @laughingman7743 in #901
- Map Arrow decimal32/64/256 to Athena decimal by @laughingman7743 in #900 (also in v3.38.0 (decimal256 only))
- Re-raise and stop interrupted queries in the SQL cursors, sharing the Spark handling by @laughingman7743 in #853
- Apply dict_type only to the dict cursor it was given to by @laughingman7743 in #930
- Skip the result cache for qmark queries with parameters by @laughingman7743 in #959 (also in v3.38.0)
- Return the whole result from as_pandas() when auto chunking applies by @laughingman7743 in #950 (also in v3.38.0)
- Keep result-set filesystems out of the fsspec instance cache and read pandas results through one filesystem by @laughingman7743 in #1001
- Keep the whole pandas and Polars results reusable by @laughingman7743 in #1005 (also in v3.39.0 (in part))
- Describe chunked Polars UNLOAD results with the Parquet schema by @laughingman7743 in #1012
- Do not convert ArrowCursor fallback values twice by @laughingman7743 in #1023 (also in v3.38.0)
- Stop a closed PolarsDataFrameIterator for every reader kind by @laughingman7743 in #1029 (also in v3.38.0)
- Return Athena JSON values from SQLAlchemy JSON columns and the pandas, Arrow, and S3FS cursors by @laughingman7743 in #1010
- Keep NULL rows of single-column CSV results in ArrowCursor by @laughingman7743 in #1031 (also in v3.38.0)
- Let execute() override cursor read options and accept them in the AsyncPandasCursor constructor by @laughingman7743 in #1022
- Convert time zone values to aware times and empty TIME/JSON text to NULL by @laughingman7743 in #1033 (also in v3.38.0/v3.39.0 (in part))
- Read multi-line CSV values that cross a block in ArrowCursor by @laughingman7743 in #1045 (also in v3.38.0)
- Keep complex values that the parsers lost or mixed up by @laughingman7743 in #1054 (also in v3.39.0)
- Keep integer and JSON values exact in PandasCursor results with NULL by @laughingman7743 in #1044
- Read CSV results for the pandas PyArrow engine with pyarrow.csv by @laughingman7743 in #1057
- Keep columns with the same name, and key CSV column types by the reader's column labels by @laughingman7743 in #1050
- Share one S3 client per connection across result sets and filesystems by @laughingman7743 in #1058
- Read columns with the same name by their own types from CSV result files by @laughingman7743 in #1066
- Use the connection's s3_config and session credentials in to_sql() by @laughingman7743 in #1068
- Read string columns as text for the pandas PyArrow engine by @laughingman7743 in #1065
- Read managed query results as a CSV result file in the pandas, Arrow, and Polars cursors by @laughingman7743 in #1061
- Use the C engine for Pandas DDL text results by @laughingman7743 in #1075 (also in v3.38.0)
- Avoid Pandas JSON dtype warnings by defaulting to whole-chunk parsing by @laughingman7743 in #1080
- Remove the legacy keyword arguments from the private _execute() by @laughingman7743 in #1088
- BREAKING: Make connection and cursor arguments keyword-only by @laughingman7743 in #1101
- Remove obsolete compatibility APIs for PyAthena 4.0 by @laughingman7743 in #1100
- Rename the opt-in S3FS CSV reader and define its protocol by @laughingman7743 in #1107
- BREAKING: Reject unknown cursor keyword arguments by @laughingman7743 in #1102
- Separate query and S3 read worker settings by @laughingman7743 in #1103
- Read Arrow results with the connection session's credentials by @laughingman7743 in #1116 (also in v3.39.0)
- Replace python-dateutil with zoneinfo by @laughingman7743 in #1125
- Split typed arrays of comma-free element types at every comma by @laughingman7743 in #1127 (also in v3.39.0)
SQLAlchemy
- Support executemany row counts and restore DML compliance coverage by @laughingman7743 in #768
- Enable Iceberg UPDATE target subquery compliance by @laughingman7743 in #769 (also in v3.37.0)
- Restore SQLAlchemy reflection and identifier coverage by @laughingman7743 in #771
- Fix metadata reflection throttling and reuse listed metadata by @laughingman7743 in #777
- Restore SQLAlchemy CTE compliance coverage by @laughingman7743 in #772 (also in v3.37.0)
- Restore SQLAlchemy binary compliance and preserve CSV binary NULLs by @laughingman7743 in #770 (also in v3.37.0)
- Preserve Hive string types in metadata fallback reflection by @laughingman7743 in #784
- Reflect an empty information_schema comment as no comment by @laughingman7743 in #782
- Support native SQLAlchemy ARRAY types and typed round trips by @laughingman7743 in #774 (also in v3.37.0)
- Support SQLAlchemy ARRAY expressions in SELECT and WHERE by @laughingman7743 in #775 (also in v3.37.0)
- Support SQLAlchemy ARRAY partial UPDATE and slice resizing by @laughingman7743 in #776 (also in v3.37.0)
- Read the dialect's own queries through an API cursor and resolve federated absence from information_schema by @laughingman7743 in #798
- Report a missing federated table from table comment and option reflection by @laughingman7743 in #801
- Support SQLAlchemy datetime literals and microsecond timestamps by @laughingman7743 in #818
- SQLAlchemy compliance: audit numeric range and float precision by @laughingman7743 in #826 (also in v3.37.0)
- Batch SQLAlchemy executemany inserts into multi-row INSERT statements by @laughingman7743 in #838
- Cast to a TypeDecorator as its implementation type by @laughingman7743 in #836
- Accept insertmanyvalues options from the URL and engine_from_config by @laughingman7743 in #843
- Require SQLAlchemy 2.0 or later for the SQLAlchemy dialect by @laughingman7743 in #858
- Remove get_double_type from the SQLAlchemy types module by @laughingman7743 in #860
- fix: render Hive STRUCT syntax in table column DDL by @mvanhorn in #870 (also in v3.37.0)
- Leave unset storage values out of the Glue table parameters by @laughingman7743 in #890
- Use SQLAlchemy 2.0's type_compiler_cls and type_compiler_instance by @laughingman7743 in #895
- Split SQLAlchemy type rendering into Hive DDL and Trino DML compilers by @laughingman7743 in #961
- Reflect the field and value types of top-level STRUCT and MAP columns by @laughingman7743 in #995 (also in v3.38.0 (reflection only))
- Match file_format case-insensitively for the compression table property by @laughingman7743 in #1015
- Use the compression table properties that Athena applies on write by @laughingman7743 in #1025
- Detect Iceberg tables from the table_type property only by @laughingman7743 in #1043 (also in v3.39.0)
Spark
- Stop Spark session readiness polling on failure states by @laughingman7743 in #831 (also in v3.38.0)
- Shut down the AsyncSparkCursor executor when session termination fails by @laughingman7743 in #830 (also in v3.38.0)
- Terminate a newly started Spark session when cursor setup fails by @laughingman7743 in #832 (also in v3.38.0)
- Define best-effort Spark calculation cancellation by @laughingman7743 in #833
- Terminate only the Spark sessions a cursor started on close by @laughingman7743 in #844
- Cancel a Spark calculation interrupted while it is being started by @laughingman7743 in #861
- Remove the pass-through Spark cursor initializers and BaseCursor._calculate by @laughingman7743 in #912
- BREAKING: Reject ineffective Spark arguments and unify environment fallbacks by @laughingman7743 in #1108
S3 filesystem
- Fix stale S3FileSystem listing and object caches by @laughingman7743 in #928 (also in v3.38.0)
- Keep the existing object when appending within a larger block size by @laughingman7743 in #929 (also in v3.38.0)
- Accept max_workers in S3FileSystem.open() and pipe_file() by @laughingman7743 in #948
- Merge a short trailing block into the previous multipart part by @laughingman7743 in #947 (also in v3.38.0)
- Keep multipart copy parts within the S3 part size limits by @laughingman7743 in #955 (also in v3.38.0)
- Accept version_id in S3FileSystem.open() and cat_file() by @laughingman7743 in #958
- Cache each explicit version of an object separately in info() by @laughingman7743 in #957
- Invalidate the object path when a version is deleted by @laughingman7743 in #960
- Count S3FileSystem.find() maxdepth levels like fsspec by @laughingman7743 in #956
- Wait for running parts before aborting multipart uploads, and validate S3File before init by @laughingman7743 in #992
- Clamp byte ranges to the object in cat_file() and S3File reads by @laughingman7743 in #987
- Keep multipart uploads within the 10,000-part limit by @laughingman7743 in #968
- Align AioS3FileSystem with S3FileSystem in transactions, open() and touch() by @laughingman7743 in #988
- Fix rm() bulk deletes in S3FileSystem and AioS3FileSystem by @laughingman7743 in #986
- Check the part limit in AioS3FileSystem transaction writes by @laughingman7743 in #999
- Read a negative cat_file() start without an end as a suffix range by @laughingman7743 in #998
- Treat dircache entries removed by another thread as cache misses by @laughingman7743 in #1002
- Make S3Object follow the mapping contract by @laughingman7743 in #991
- Route S3 request parameters to the operations that accept them by @laughingman7743 in #1000
- Limit multipart uploads and object versions to the key and keys under it by @laughingman7743 in #989
- Implement exclusive create for open("xb"), put_file() and pipe_file() by @laughingman7743 in #1009
- Use the s3fs profile argument as the boto3 profile name by @laughingman7743 in #985
- Follow fsspec's get_file() contract for directories and file objects by @laughingman7743 in #990
- Write pipe_file() data without committing a failed write by @laughingman7743 in #1003
- Answer info() and exists() from cached parent listings and fix bucket lookups by @laughingman7743 in #1006
- Delete only the copied source objects in mv() by @laughingman7743 in #1013
- Leave the existing object unchanged when put_file() fails by @laughingman7743 in #1017
- Keep system metadata in setxattr() and reject version paths by @laughingman7743 in #1016
- Send the lookup parameters of a file with its lookups by @laughingman7743 in #1024
- Align S3FileSystem.find() with fsspec for the path itself and prefix levels by @laughingman7743 in #993
- Compress pipe_file() values up front, and route and key them consistently by @laughingman7743 in #1038
- Copy metadata, tags and annotations in multipart copies by @laughingman7743 in #1036
- Move the write-and-close helpers from S3FileSystem to S3File by @laughingman7743 in #1035
- Model S3 paths with an S3Path value class by @laughingman7743 in #1052
- Keep a multipart upload whose abort fails so that discard() retries it by @laughingman7743 in #1047
- Abort a cancelled async multipart copy after its running parts by @laughingman7743 in #1056
- Allow ? in keys and address versions in listings, files and copies by @laughingman7743 in #1055
- Add S3Core with typed listing entries and list/HEAD operations by @laughingman7743 in #1060
- Move the delete requests into S3Core (step 3.1 of #1063) by @laughingman7743 in #1064
- Move the multipart upload requests into S3Core (step 3.2 of #1063) by @laughingman7743 in #1069
- Move the copy requests and the multipart copy plan into S3Core, and abort uploads interrupted during creation (step 3.3 of #1063) by @laughingman7743 in #1074
- Use multipart upload objects and fix checksum completion and cleanup races by @laughingman7743 in #1072
- Add the pure S3PathPairing planner and S3Path.target (step 3.4 of #1063) by @laughingman7743 in #1081
- Recover interrupted S3File creation with a public multipart writer by @laughingman7743 in #1085
- Fix null-version moves in versioning-enabled S3 buckets by @laughingman7743 in #1084
- Move object GET and PUT requests into S3Core by @laughingman7743 in #1091
- Move tagging, ACL, metadata and presign requests into S3Core by @laughingman7743 in #1090
- Move bucket lifecycle, multipart upload listing and the size check into S3Core by @laughingman7743 in #1089
- Move the bucket versioning request into S3Core by @laughingman7743 in #1098
- Fix interrupted multipart creation scheduling and flaky SIGINT tests by @laughingman7743 in #1113
- Track multipart parts before scheduling and own multipart copy cleanup by @laughingman7743 in #1122
Documentation
- Define contribution, AWS validation, and attribution requirements by @laughingman7743 in #788
- Fix PandasCursor performance tuning examples by @laughingman7743 in #902
- Tighten the Spark and SQL cancellation notes in the docs by @laughingman7743 in #920
- Document every public API in pyathena/ and check docstrings with ruff by @laughingman7743 in #919
- Correct docstrings that contradict the implementation by @laughingman7743 in #939
- Correct user guide claims that contradict the implementation by @laughingman7743 in #940
- Document schema_overrides for PolarsCursor column types by @laughingman7743 in #1114
- Document that executemany() with no parameters checks no keywords by @laughingman7743 in #1117
CI, tests, and development
- Pin the CI test badge to the master branch by @laughingman7743 in #766
- Bump soupsieve from 2.8.4 to 2.9 by @dependabot[bot] in #767
- Replace legacy benchmarks with isolated cursor benchmark tooling by @laughingman7743 in #773
- Add shared development and review skills for Codex and Claude Code by @laughingman7743 in #778
- Sweep stale test databases after weekly tests by @laughingman7743 in #779
- Skip AWS integration jobs for external fork pull requests by @laughingman7743 in #789 (also in v3.37.0)
- Add NOTICE acknowledging PyHive's influence by @laughingman7743 in #802
- Add MIT headers to existing files written solely by the maintainer by @laughingman7743 in #805
- Rerun tests once on Athena service-side query failures by @laughingman7743 in #811 (also in v3.37.0)
- Manage the benchmark project as a uv workspace member by @laughingman7743 in #813
- Give each test session its own S3 Tables namespace by @laughingman7743 in #815 (also in v3.37.0)
- Run the three test suites in parallel by @laughingman7743 in #814 (also in v3.37.0)
- Check license headers across the repository by @laughingman7743 in #808
- Use collective LICENSE attribution and PEP 639 license metadata by @laughingman7743 in #809
- Assert cache hits in the test work group instead of primary by @laughingman7743 in #817 (also in v3.38.0)
- Upgrade the locked data libraries and type-check per interpreter by @laughingman7743 in #822
- Run benchmark jobs on a fleet of identical hosts by @laughingman7743 in #823
- Count initialization row validation as API-heavy fleet work by @laughingman7743 in #824
- Re-enable SQLAlchemy parameter and SQL rendering compliance tests by @laughingman7743 in #825 (also in v3.37.0)
- Allow Graviton benchmark hosts with an architecture parameter by @laughingman7743 in #827
- Run AWS test suites only on ready pull requests and related changes by @laughingman7743 in #837 (also in v3.37.0)
- Run pull-request AWS tests on the newest Python version only by @laughingman7743 in #863 (also in v3.37.0)
- Run the full test matrix in the Release workflow before publishing by @laughingman7743 in #862 (also in v3.37.0)
- Set up the PyAthena test fixtures only in processes that run tests by @laughingman7743 in #866 (also in v3.37.0)
- Insert SQLAlchemy compliance fixture rows once for read-only classes by @laughingman7743 in #867 (also in v3.37.0)
- Limit the multiversion docs build to the newest minor versions by @laughingman7743 in #869
- Give the executemany and partition tests their own tables by @laughingman7743 in #873 (also in v3.37.0)
- Define the shared test tables in Python and generate their data by @laughingman7743 in #874
- Align the compliance-suite dialect registry with the entry points by @laughingman7743 in #905 (also in v3.38.0)
- Sweep leaked test databases daily with a one-day cutoff by @laughingman7743 in #907
- Bump urllib3 from 2.7.0 to 2.8.0 by @dependabot[bot] in #908
- ci: run SQLAlchemy and Spark tests when shared core modules change by @laughingman7743 in #906 (also in v3.38.0)
- Run the PyAthena suite only when its files change by @laughingman7743 in #913 (also in v3.38.0)
- Stub every page in the listing resume test by @laughingman7743 in #871
- Mark the asyncio overrides of sync methods with @OverRide by @laughingman7743 in #917
- Mark every checkable override with @OverRide by @laughingman7743 in #918
- Audit test conventions and improve regression setup and assertions by @laughingman7743 in #1082
New Contributors
- @mvanhorn made their first contribution in #870
- @aminghadersohi made their first contribution in #839
Full Changelog: v3.36.0...v4.0.0