Repository navigation
Check the user guides and docstrings against the implementation #927
Description
Use case
While filling the missing docstrings for #882 (PR #919), the docstrings around the new ones turned out to contain claims that do not match the code. They include wrong examples, attributes that do not exist, and supported types that are not supported. The same pass found one inaccuracy in docs/usage.md (the on_start_query_execution timing on a cache_size hit), which #919 fixes. The user guides under docs/ have not been checked against the implementation as a whole. Many of their examples were written together with these docstrings, so they likely have similar problems.
Readers copy these examples and rely on the described arguments and behavior, so an inaccuracy turns into a failing call or a wrong assumption.
Docstring inaccuracies found so far
These were reported during #919 and are not yet verified one by one. Line numbers are on master 775874c.
pyathena/model.py:37: theAthenaQueryExecutionexample usescursor._last_query_execution, which does not exist.pyathena/model.py:411: theAthenaCalculationExecution"See Also" linksAPI_CalculationSummary. The class readsGetCalculationExecutionfields.pyathena/async_cursor.py:29-33:Attributes:describesdescriptionas a sequence and listsrowcount. OnAsyncCursor,descriptionis a method that takes a query ID, and there is norowcount.pyathena/async_cursor.py:39,:294: the examples treat the return value ofexecute()as a future. It is a(query_id, future)tuple.pyathena/aio/cursor.py:33,:220:async with AioConnection.create(...)lacksawait.pyathena/formatter.py:129: thewrap_unloadexample output ends inuuid//. The code builds one trailing slash. The docstring also has noRaises:for theProgrammingErroron an empty query.pyathena/formatter.py:383:DefaultParameterFormatterliststimeas supported._DEFAULT_FORMATTERShas notimeentry, so atimeparameter raisesTypeError. Either the docstring or the formatter is wrong.pyathena/pandas/cursor.py:109:**kwargsis described as passed topandas.read_csv.__init__passes it to the base cursor.pyathena/pandas/result_set.py:267-268:arraysizeis described as "not used for pandas processing" (it is thefetchmany()default), andretry_configas being for S3 operations (it is also used forGetQueryResults).pyathena/aio/result_set.py:72,pyathena/pandas/result_set.py:282,pyathena/polars/result_set.py:233:result_set_type_hintsis described as keyed by column name. The keys can also be zero-based column indexes, and names match case-insensitively.pyathena/aio/result_set.py:161:fetchonereturns "a tuple"; the dict result set returns a dict.:179:fetchmanyfalls back toarraysizefor non-positive sizes too, not onlyNone.pyathena/spark/common.py:52,spark/cursor.py:36,spark/async_cursor.py:42:Attributes:listsengine_configuration, which has no public attribute or property.spark/async_cursor.py:40listsmax_workers; check whether a public attribute exists.pyathena/polars/converter.py:44: says the converter handles decimal types. Check whether the conversion mapping does, or onlyget_dtype().pyathena/filesystem/s3_object.py:47: theS3StorageClassdocstring leaves outBUCKETandDIRECTORY(:80-81).pyathena/sqlalchemy/compiler.py:86-87: "TEXT, NCHAR, NVARCHAR all map to STRING" holds only without a length, and "FLOAT maps to REAL in CAST expressions" applies only to the DML cast path.
Proposed change
- Check the user guides against the implementation. Read each page under
docs/:aio.md,api.md,arrow.md,cursor.md,filesystem.md,introduction.md,null_handling.md,pandas.md,polars.md,s3fs.md,spark.md,sqlalchemy.md,testing.mdandusage.md. Check examples, argument names and defaults, return types, raised exceptions, and behavior descriptions against the code.contributing.mdandindex.mdare out of scope unless they state behavior. Add the confirmed findings to this issue with file and line, and keep unconfirmed ones separate. - Verify the docstring list above in the code, and drop or correct the entries that do not hold.
- Fix the confirmed findings. Decide here whether the docstrings and the guides go in one PR or two, depending on the number of findings. Where the code is wrong rather than the documentation (for example, the
timeformatter), file a bug or feature issue instead of documenting the defect.
This builds on #919 (docstrings for every public API). The docstring fixes touch the same files, so they should start after #919 is merged.
Validation plan (if implementing)
just lint,just docs lint, and a Sphinx build (sphinx-build -b html docs <out>) with no new warnings against master.- Run the corrected examples that need no AWS access (for example, the formatter output) and confirm their output. Examples that need Athena are checked against the code, not run, unless a finding depends on service behavior.
- Documentation-only changes need no AWS integration tests.
Activity
laughingman7743 commentedon Oct 3, 2026
Audit results against master 9b74767 (after #919). Line numbers are from that commit.
Docstring list above
13 entries are confirmed as written, and 2 hold only in part. None were dropped.
DefaultParameterFormattertime: this is a documentation error.timewas never implemented; the wording came in with the docstring commit 41ed5c4. The docstring will be fixed, and no bug issue is needed.pandas/result_set.pyarraysize/retry_config: both descriptions are wrong.arraysizeis thefetchmany()default.retry_configcoversGetQueryResultsand the result set's own HeadObject/GetObject; file reads use the connection's retry configuration.polars/converter.py: decimal holds throughget_dtype()→pl.Decimal(precision, scale). The wrong part is "Preserving JSON":jsonvalues are parsed for fetched rows, and the converter functions do not apply toas_polars().result_set_type_hints"column names" wording is also in 16execute()docstrings. The fetch wording fromaio/result_set.pyis also inaio/cursor.pyand the syncWithFetch.Also found in the same classes:
pandas/cursor.py:53,64,200andpandas/result_set.py:214:cursor.fetchall() # Returns pandas DataFrame. It returns tuples.pandas/cursor.py:56-59andpandas/result_set.py:216-218:for chunk_df in cursoryields rows, not DataFrames.pandas/cursor.py:47: an Attributechunksizethat does not exist.auto_optimize_chunksize=True.spark/cursor.py:52:cursor.fetchall(). Spark cursors have no fetch methods.spark/cursor.py:37:descriptionis the calculation's description, not the session's.cursor.py:36: a tuple parameter with%s. pyformat needs a dict.options.py:37-39: "reset a field withmerge".mergeignores None.filesystem/s3_async.py:56-63: sync wrappers onasynchronous=True.formatter.py:142: "Must be a SELECT or WITH statement" contradicts the method's own Note.User guides
Examples that fail as written
pandas.md:768(AsyncPandasCursor),arrow.md:462(AsyncArrowCursor), andcursor.md:264-268(AsyncDictCursor).usage.md:113(import pyathena),polars.md:312(polars as pl), ands3fs.md:133(typing.Any).arrow.md:163-165:convertis not indented into the class, so the class stays abstract and cannot be instantiated.filesystem.md:51,142,167:connect(region_name=...)alone raisesProgrammingError(no staging dir or work group).aio.md:293-301: the sync wrapper is used onAioS3FileSystem(asynchronous=True).polars.md:338-378:as_polars()always returns one DataFrame, never aPolarsDataFrameIterator, so all three examples iterate columns or call a missing method. Onlyiter_chunks()returns the iterator.sqlalchemy.md:847,863,996,1012:select().from_statement(text(...))raisesNotImplementedErroron Core.sqlalchemy.md:871,1020:json.loads()is applied to values the converter already decoded.usage.md:434-455andsqlalchemy.md:606-684: the cancellation examples always wait out the full sleep (ThreadPoolExecutorexit). The SQLAlchemy one also cancels nothing: a new cursor has noquery_id, socancel()raisesProgrammingError.Wrong descriptions
usage.md:146-149:AsyncCursor-based cursors do not takeon_start_query_execution. It is ignored when passed inoptions; onAsyncPandasCursor, a keyword argument goes toread_csv.usage.md:262:expiration_timeshould becache_expiration_time.usage.md:283-284: thecache_sizematch also requires a SUCCEEDED query and the same schema and catalog. It never matches withunload=True, because the location includes a fresh UUID.aio.md:212-225: not every aio cursor downloads inexecute().AioCursorpagesGetQueryResults,AioS3FSCursorstreams, and a chunkedas_pandas()reads S3 synchronously while iterating.aio.md:265-267: file operations useasyncio.to_thread, which is threads.arrow.md:293-294:connect_timeout/request_timeoutapply to the pyarrow filesystem only. HeadObject and the UNLOAD manifest GetObject use boto3.spark.md:308:AsyncSparkCursordoes not inherit fromAsyncCursor.spark.md:403-415:get_std_out/get_std_errorcan returnNone.s3fs.md:125-128: there is nobinarymapping,timestamp with time zoneis missing, and nested scalars stay strings.introduction.md:63: "connection pooling" does not exist.sqlalchemy.md:331,397,412: "≥ 0" should be "positive"; 0 is treated as unset.sqlalchemy.md:559: the callback runs afterStartQueryExecution, not before.sqlalchemy.md:723-729: theaio*dialects also support the callback.sqlalchemy.md:854,882,896,951: ROW scalars are shown as ints; they stay strings.sqlalchemy.md:1165:INTEGERshould beINTin the DDL.sqlalchemy.md:827,977,1164: without Iceberg, the dialect emitsCREATE EXTERNAL TABLE ... LOCATION.convertes→converter(pandas.md:295,arrow.md:171),AthenaResultSetObject→AthenaResultSet(pandas.md:665,arrow.md:368,polars.md:453,s3fs.md:315), the self-referencing "As with AsyncXCursor" (pandas.md:738,752,arrow.md:432,446,polars.md:511,525), andAsyncDIctCursor/DictCursorin the AsyncDictCursor section (cursor.md:219-222).pandas.mddoes not say thatto_sqland unload need pyarrow.arrow.md:197-199does not give the UNLOAD scope (SELECT/WITH only, location, staging dir required).testing.md,null_handling.md, andapi.mdhave no mismatches; all 119 autodoc targets exist.Service-dependent claims, checked against Athena
CAST('<text>' AS JSON)returns a JSON string scalar.sqlalchemy.types.JSONturns it into a dict only by decoding a second time. Real JSON values (json_parse, JSON columns,CAST(ROW/MAP AS JSON)) raiseTypeError→ SQLAlchemy JSON columns fail on JSON objects and arrays returned by Athena #934.CAST('[1, 2, 3]' AS JSON)works, so the "will raise InvalidRequestException" text atsqlalchemy.md:1387,1403-1405is wrong.CAST(ROW('John', 30) AS JSON)returns{"":"John","":30}, which decodes to{'': 30}. The example atsqlalchemy.md:871will use a named ROW type.Code defects filed instead of documented
#934 (SQLAlchemy JSON), #935 (
as_pandas()/as_polars()consume the iterator), #936 (version_idonopen()/cat_file()), #937 (lowercasefile_formatcompression), and #938 (enhancement:AsyncPandasCursorconstructor options). They join the earlier #923 and #924. The guides keep describing the intended behavior for these.Not filed:
AsyncPandasCursorforwarding extra keyword arguments toread_csvis documented. The guide text is fixed instead.AioS3FileSystem(asynchronous=True)creates its executor with no loop, but no documented path opens files in that mode.Split
There are about 25 docstring fixes and 40 guide fixes, so they go in two PRs: docstrings under
pyathena/(Part of #927), then thedocs/*.mdguides (Closes #927).Correct docstrings that contradict the implementation