From c222dc020c4bd7b934ea6c240fd4c7f782b46893 Mon Sep 17 00:00:00 2001 From: Najeb Abdullahi Date: Thu, 14 May 2026 15:55:12 -0500 Subject: [PATCH 1/5] feat: implement PEP 249 cursor compliance cursor.description, fetchone/fetchall/fetchmany now return proper tuples, rowcount accurate, AsyncCursor parity, 24 unit tests added --- mapepire_python/asyncio/cursor.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/mapepire_python/asyncio/cursor.py b/mapepire_python/asyncio/cursor.py index c1792b3..8031ce0 100644 --- a/mapepire_python/asyncio/cursor.py +++ b/mapepire_python/asyncio/cursor.py @@ -96,6 +96,15 @@ async def executemany( await self.execute(operation, params) return self + def _row_to_tuple(self, row) -> tuple: + if isinstance(row, dict): + if self._metadata and self._metadata.columns: + return tuple(row.get(col.name, None) for col in self._metadata.columns) + return tuple(row.values()) + if isinstance(row, (list, tuple)): + return tuple(row) + return row + async def fetchone(self) -> Optional[ResultRow]: if self._buffer: return row_to_tuple(self._buffer.pop(0), self._metadata) From 754f954a357ea0babbcac53cdfbc5e0fdc982f7b Mon Sep 17 00:00:00 2001 From: Najeb Abdullahi Date: Thu, 28 May 2026 10:03:07 -0500 Subject: [PATCH 2/5] added a consistent logging hierarchy --- CHANGELOG.md | 1 + mapepire_python/async_base_job.py | 6 +++++- .../authentication/kerberosTokenProvider.py | 11 +++++++++++ mapepire_python/client/query.py | 15 ++++++++++++++- mapepire_python/client/sql_job.py | 10 +++++++++- mapepire_python/core/connection.py | 5 +++++ mapepire_python/core/cursor.py | 2 ++ mapepire_python/pool/pool_client.py | 4 ++++ mapepire_python/pool/pool_job.py | 3 +++ mapepire_python/pool/pool_query.py | 10 ++++++++++ 10 files changed, 64 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 89e2c69..db4beec 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -19,6 +19,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Native async support for PEP 249 interface (replace to_thread wrapper) #95 - Improve public API surface and top-level exports #94 - Fix PEP 249 compliance: cursor.description and result types #91 +- Add structured logging across the codebase #90 ## [v0.2.0](https://github.com/Mapepire-IBMi/mapepire-python/releases/tag/v0.2.0) - 2024-11-26 - replace `websocket-client` with `websockets` diff --git a/mapepire_python/async_base_job.py b/mapepire_python/async_base_job.py index 9f9269d..ce8287c 100644 --- a/mapepire_python/async_base_job.py +++ b/mapepire_python/async_base_job.py @@ -106,6 +106,7 @@ async def connect( # type: ignore[override] ) -> Any: db2_server = self._parse_connection_input(db2_server, **kwargs) + logger.info("Connecting to %s:%s", db2_server.host, db2_server.port) self.socket = await self.get_channel(db2_server) self._message_task = asyncio.create_task(self.message_handler()) @@ -127,12 +128,14 @@ async def connect( # type: ignore[override] if result.success: self.status = JobStatus.Ready + self.id = result.job + logger.info("Connection established: job_id=%s", self.id) else: self.status = JobStatus.NotStarted + logger.error("Connection failed: %s", result.error or "unknown error") await self.close() raise Exception(result.error or "Failed to connect to server") - self.id = result.job self._is_tracing_channel_data = False return result @@ -207,6 +210,7 @@ async def query_and_run( # type: ignore[override] raise RuntimeError(f"Failed to run query: {e}") from e async def close(self) -> None: # type: ignore[override] + logger.info("Closing job %s", self.id) await self.dispose() self.status = JobStatus.Ended diff --git a/mapepire_python/authentication/kerberosTokenProvider.py b/mapepire_python/authentication/kerberosTokenProvider.py index 1b626b7..7648324 100644 --- a/mapepire_python/authentication/kerberosTokenProvider.py +++ b/mapepire_python/authentication/kerberosTokenProvider.py @@ -1,8 +1,11 @@ import base64 +import logging import os import platform from typing import Final, Optional +logger = logging.getLogger(__name__) + sspi = None gssapi = None PLATFORM = platform.system() @@ -45,6 +48,7 @@ def __init__( raise ValueError(f"Missing required parameters: {', '.join(missing)}") def get_token(self) -> str: + logger.debug("Requesting Kerberos token for host=%s", self.host) return self._refresh_token() def _refresh_token(self) -> str: @@ -58,17 +62,21 @@ def _format_token(self, token: bytes) -> str: return TOKEN_PREFIX + token_b64 def _refresh_token_windows(self) -> str: + logger.debug("Generating Kerberos token via Windows SSPI for host=%s", self.host) target = f"krbsvr400/{self.host}" client = sspi.ClientAuth("Kerberos", targetspn=target) err, out_buffer = client.authorize(None) if err != 0: + logger.error("Windows SSPI Kerberos authentication failed: error_code=%s", hex(err)) raise RuntimeError(f"Windows SSPI error when attempting Kerberos login: {hex(err)}") token = out_buffer[0].Buffer + logger.debug("Kerberos token generated successfully via Windows SSPI") return self._format_token(token) def _refresh_token_unix(self) -> str: + logger.debug("Generating Kerberos token via GSSAPI for host=%s realm=%s", self.host, self.realm) os.environ["KRB5_CONFIG"] = self.krb5_path if self.ticket_cache: os.environ["KRB5CCNAME"] = self.ticket_cache @@ -96,9 +104,12 @@ def _refresh_token_unix(self) -> str: ) except gssapi.exceptions.GSSError as e: if "No credentials were supplied" in str(e) or "Unavailable" in str(e): + logger.error("Kerberos authentication failed: no valid TGT in credential cache") raise RuntimeError("No valid TGT found in credential cache.") + logger.error("Kerberos GSSAPI authentication failed for host=%s", self.host) raise RuntimeError( f"Kerberos token generation error when attempting Kerberos login: {str(e)}" ) + logger.debug("Kerberos token generated successfully via GSSAPI") return self._format_token(token) diff --git a/mapepire_python/client/query.py b/mapepire_python/client/query.py index 1ae7f5f..069927b 100644 --- a/mapepire_python/client/query.py +++ b/mapepire_python/client/query.py @@ -1,5 +1,6 @@ import dataclasses import json +import logging from enum import Enum from typing import Any, Dict, Generic, Mapping, Optional, Sequence, TypeVar, Union @@ -21,6 +22,8 @@ T = TypeVar("T") +logger = logging.getLogger(__name__) + class QueryState(Enum): NOT_YET_RUN = (1,) @@ -68,6 +71,7 @@ def prepare_sql_execute(self): if self.state == QueryState.RUN_DONE: raise Exception("Statement has already been fully run") + logger.debug("Preparing and executing query") query_result = QueryResult.from_dict( # type: ignore self._execute_query( PrepareSqlExecuteRequest( @@ -84,9 +88,11 @@ def prepare_sql_execute(self): error_list = {k: v for k, v in {"error": query_result.error, "sql_state": query_result.sql_state, "sql_rc": query_result.sql_rc}.items() if v is not None} if not error_list: error_list["error"] = "failed to run query for unknown reason" + logger.error("Query preparation failed: %s", error_list) raise RuntimeError(error_list) self._correlation_id = query_result.id + logger.debug("Query prepared: correlation_id=%s done=%s", self._correlation_id, query_result.is_done) return query_result @@ -103,6 +109,8 @@ def run(self, rows_to_fetch: Optional[int] = None) -> Dict[str, Any]: elif self.state == QueryState.RUN_DONE: raise Exception("Statement has already been fully run") + logger.debug("Executing query: rows_to_fetch=%d", rows_to_fetch) + if self.is_cl_command: request = ClRequest( id=self.job._get_unique_id("clcommand"), @@ -135,9 +143,11 @@ def run(self, rows_to_fetch: Optional[int] = None) -> Dict[str, Any]: error_list = {k: v for k, v in {"error": query_result.error, "sql_state": query_result.sql_state, "sql_rc": query_result.sql_rc}.items() if v is not None} if not error_list: error_list["error"] = "failed to run query for unknown reason" + logger.error("Query execution failed: %s", error_list) raise RuntimeError(error_list) self._correlation_id = query_result.id + logger.debug("Query executed: correlation_id=%s done=%s", self._correlation_id, query_result.is_done) return query_result @@ -154,6 +164,7 @@ def fetch_more(self, rows_to_fetch: Optional[int] = None) -> Dict[str, Any]: raise Exception("Statement has already been fully run") assert self._correlation_id is not None + logger.debug("Fetching more rows: rows=%d correlation_id=%s", rows_to_fetch, self._correlation_id) self._rows_to_fetch = rows_to_fetch query_result = SqlMoreResponse.from_dict( # type: ignore self._execute_query( @@ -170,14 +181,16 @@ def fetch_more(self, rows_to_fetch: Optional[int] = None) -> Dict[str, Any]: if not query_result.success: self.state = QueryState.ERROR + logger.error("Fetch failed: %s", query_result.error) raise RuntimeError(query_result.error or "Failed to run Query (unknown error)") return query_result @handle_ws_errors def close(self): - if not self.job._socket: + if self.job._socket is None: raise Exception("SQL Job not connected") + logger.debug("Closing query: correlation_id=%s", self._correlation_id) if self._correlation_id and self.state is not QueryState.RUN_DONE: self.state = QueryState.RUN_DONE return SqlCloseResponse.from_dict( # type: ignore diff --git a/mapepire_python/client/sql_job.py b/mapepire_python/client/sql_job.py index 18484ab..7e393f8 100644 --- a/mapepire_python/client/sql_job.py +++ b/mapepire_python/client/sql_job.py @@ -1,5 +1,6 @@ import dataclasses import json +import logging from pathlib import Path from typing import Any, Dict, Optional, Union @@ -19,6 +20,8 @@ __all__ = ["SQLJob"] +logger = logging.getLogger(__name__) + class SQLJob(BaseJob): def __init__( @@ -106,6 +109,7 @@ def connect( """ db2_server = self._parse_connection_input(db2_server, **kwargs) + logger.info("Connecting to %s:%s", db2_server.host, db2_server.port) self._socket = self._get_channel(db2_server) props = ";".join( @@ -124,16 +128,19 @@ def connect( try: result = ConnectionResult.from_dict(json.loads(self._socket.recv())) # type: ignore except Exception as e: + logger.error("Failed to parse connect response: %s", e) raise Exception(f"Failed to parse connect response: {e}") if result.success: self._status = JobStatus.Ready + self.id = result.job + logger.info("Connection established: job_id=%s", self.id) else: self._status = JobStatus.NotStarted + logger.error("Connection failed: %s", result.error or "unknown error") self.close() raise Exception(result.error or "Failed to connect to server") - self.id = result.job self._is_tracing_channeldata = False return result @@ -196,6 +203,7 @@ def query_and_run( raise RuntimeError(f"Failed to run query: {e}") def close(self) -> None: + logger.info("Closing job %s", self.id) self._status = JobStatus.Ended if self._socket: self._socket.close() diff --git a/mapepire_python/core/connection.py b/mapepire_python/core/connection.py index 778198e..293e0e2 100644 --- a/mapepire_python/core/connection.py +++ b/mapepire_python/core/connection.py @@ -1,3 +1,4 @@ +import logging from pathlib import Path from typing import Any, Dict, Optional, Sequence, Union @@ -12,6 +13,8 @@ __all__ = ["Connection"] +logger = logging.getLogger(__name__) + COMMIT = "COMMIT" ROLLBACK = "ROLLBACK" @@ -45,6 +48,7 @@ class Connection(pep249.CursorExecuteMixin, pep249.ConcreteErrorMixin, pep249.Co def __init__(self, database: Union[DaemonServer, dict, Path], opts: Optional[Dict[str, Any]] = None, **kwargs) -> None: super().__init__() self._closed = False + logger.info("Opening connection") self.job = SQLJob(creds=database, options=opts, **kwargs) self.job.connect(database, **kwargs) @@ -60,6 +64,7 @@ def close(self) -> None: if self._closed: return + logger.info("Closing connection: job_id=%s", self.job.id) self.job.close() self._closed = True diff --git a/mapepire_python/core/cursor.py b/mapepire_python/core/cursor.py index c407868..75a7c3e 100644 --- a/mapepire_python/core/cursor.py +++ b/mapepire_python/core/cursor.py @@ -90,6 +90,7 @@ def execute( parameters: Optional[QueryParameters] = None, **kwargs: Any, ) -> "Cursor": + logger.debug("Executing statement") opts = kwargs.get("opts", None) if opts: query = Query(self.job, operation, opts) @@ -212,6 +213,7 @@ def nextset(self) -> Optional[bool]: def close(self) -> None: if self._closed: return + logger.debug("Closing cursor") if self.query: for q in self.query_q: q.close() diff --git a/mapepire_python/pool/pool_client.py b/mapepire_python/pool/pool_client.py index a4eb60a..155a477 100644 --- a/mapepire_python/pool/pool_client.py +++ b/mapepire_python/pool/pool_client.py @@ -1,4 +1,5 @@ import asyncio +import logging from dataclasses import dataclass from pathlib import Path from typing import Any, Dict, List, Optional, Union @@ -8,6 +9,8 @@ __all__ = ["Pool"] +logger = logging.getLogger(__name__) + @dataclass class PoolOptions: @@ -100,6 +103,7 @@ async def _add_job( if new_sql_job.get_status() == JobStatus.NotStarted: await new_sql_job.connect(self.options.creds, section=self.options.section) + logger.info("Job added to pool: pool_size=%d", len(self.jobs)) return new_sql_job def _get_ready_job(self) -> PoolJob: diff --git a/mapepire_python/pool/pool_job.py b/mapepire_python/pool/pool_job.py index 753940b..6f8bda2 100644 --- a/mapepire_python/pool/pool_job.py +++ b/mapepire_python/pool/pool_job.py @@ -1,3 +1,4 @@ +import logging from pathlib import Path from typing import Any, Dict, Optional, Union @@ -6,6 +7,8 @@ __all__ = ["PoolJob"] +logger = logging.getLogger(__name__) + class PoolJob(AsyncBaseJob): """Native async SQL job for use within a connection pool. diff --git a/mapepire_python/pool/pool_query.py b/mapepire_python/pool/pool_query.py index da83384..9a48c14 100644 --- a/mapepire_python/pool/pool_query.py +++ b/mapepire_python/pool/pool_query.py @@ -1,5 +1,6 @@ import dataclasses import json +import logging from typing import Any, Dict, Mapping, Optional, Protocol, Sequence, Union from mapepire_python.client.query import QueryState @@ -16,6 +17,8 @@ SqlRequest, ) +logger = logging.getLogger(__name__) + class _SQLJobProtocol(Protocol): """Structural protocol describing the job interface PoolQuery requires. @@ -71,6 +74,8 @@ async def run(self, rows_to_fetch: Optional[int] = None) -> QueryResult: elif self.state == QueryState.RUN_DONE: raise Exception("Statement has already been fully run") + logger.debug("Executing async query: rows_to_fetch=%d", rows_to_fetch) + if self.is_cl_command: request = ClRequest( id=self.job._get_unique_id("clcommand"), @@ -103,9 +108,11 @@ async def run(self, rows_to_fetch: Optional[int] = None) -> QueryResult: error_list = {k: v for k, v in {"error": query_result.error, "sql_state": query_result.sql_state, "sql_rc": query_result.sql_rc}.items() if v is not None} if not error_list: error_list["error"] = "failed to run query for unknown reason" + logger.error("Async query execution failed: %s", error_list) raise Exception(error_list) self._correlation_id = query_result.id + logger.debug("Async query executed: correlation_id=%s done=%s", self._correlation_id, query_result.is_done) return query_result @@ -121,6 +128,7 @@ async def fetch_more(self, rows_to_fetch: Optional[int] = None) -> SqlMoreRespon raise Exception("Statement has already been fully run") assert self._correlation_id is not None + logger.debug("Fetching more rows: rows=%d correlation_id=%s", rows_to_fetch, self._correlation_id) self._rows_to_fetch = rows_to_fetch query_result = SqlMoreResponse.from_dict( # type: ignore await self._execute_query( @@ -137,6 +145,7 @@ async def fetch_more(self, rows_to_fetch: Optional[int] = None) -> SqlMoreRespon if not query_result.success: self.state = QueryState.ERROR + logger.error("Async fetch failed: %s", query_result.error) raise Exception(query_result.error or "Failed to run Query (unknown error)") return query_result @@ -144,6 +153,7 @@ async def fetch_more(self, rows_to_fetch: Optional[int] = None) -> SqlMoreRespon async def close(self): if not self.job.socket: raise Exception("SQL Job not connected") + logger.debug("Closing async query: correlation_id=%s", self._correlation_id) if self._correlation_id and self.state is not QueryState.RUN_DONE: self.state = QueryState.RUN_DONE return SqlCloseResponse.from_dict( # type: ignore From ec9eacb300f8b435bf2ced4522653cf52004f9b8 Mon Sep 17 00:00:00 2001 From: Najeb Abdullahi Date: Thu, 4 Jun 2026 11:04:05 -0500 Subject: [PATCH 3/5] remove unused _row_to_tuple method from AsyncCursor --- mapepire_python/asyncio/cursor.py | 9 --------- 1 file changed, 9 deletions(-) diff --git a/mapepire_python/asyncio/cursor.py b/mapepire_python/asyncio/cursor.py index 8031ce0..c1792b3 100644 --- a/mapepire_python/asyncio/cursor.py +++ b/mapepire_python/asyncio/cursor.py @@ -96,15 +96,6 @@ async def executemany( await self.execute(operation, params) return self - def _row_to_tuple(self, row) -> tuple: - if isinstance(row, dict): - if self._metadata and self._metadata.columns: - return tuple(row.get(col.name, None) for col in self._metadata.columns) - return tuple(row.values()) - if isinstance(row, (list, tuple)): - return tuple(row) - return row - async def fetchone(self) -> Optional[ResultRow]: if self._buffer: return row_to_tuple(self._buffer.pop(0), self._metadata) From a797cfcad911f584c4aed76d4e7f81cd07303b79 Mon Sep 17 00:00:00 2001 From: Najeb Abdullahi Date: Fri, 5 Jun 2026 12:34:12 -0500 Subject: [PATCH 4/5] Add logging to asyncio connection/cursor, fix f-string formatting in async_base_job, and add pool lifecycle logs --- mapepire_python/async_base_job.py | 10 +++++----- mapepire_python/asyncio/connection.py | 7 +++++++ mapepire_python/asyncio/cursor.py | 5 +++++ mapepire_python/pool/pool_client.py | 7 +++++++ 4 files changed, 24 insertions(+), 5 deletions(-) diff --git a/mapepire_python/async_base_job.py b/mapepire_python/async_base_job.py index ce8287c..0e9fe16 100644 --- a/mapepire_python/async_base_job.py +++ b/mapepire_python/async_base_job.py @@ -73,7 +73,7 @@ async def get_channel(self, db2_server: DaemonServer) -> ClientConnection: return await socket.connect() async def send(self, content: str) -> Dict[str, Any]: - logger.debug(f"sending data: {content}") + logger.debug("sending data: %s", content) req = json.loads(content) if self.socket is None: raise RuntimeError("Socket is not connected") @@ -98,7 +98,7 @@ def get_status(self) -> JobStatus: def get_running_count(self) -> int: count = len(self._pending) - logger.debug(f"--- running count {self.unique_id}: {count}, status: {self.get_status()}") + logger.debug("--- running count %s: %d, status: %s", self.unique_id, count, self.get_status()) return count async def connect( # type: ignore[override] @@ -162,18 +162,18 @@ async def message_handler(self): if self.socket is None: raise RuntimeError("Socket is not connected") async for message in self.socket: - logger.debug(f"Received raw message: {message}") + logger.debug("Received raw message: %s", message) try: response = json.loads(message) except json.JSONDecodeError as e: - logger.warning(f"Discarding malformed message: {e}") + logger.warning("Discarding malformed message: %s", e) continue req_id = response.get("id") future = self._pending.get(req_id) if req_id else None if future is not None and not future.done(): future.set_result(response) else: - logger.debug(f"No pending request for response id: {req_id}") + logger.debug("No pending request for response id: %s", req_id) except websockets.exceptions.ConnectionClosedError: await self.dispose() diff --git a/mapepire_python/asyncio/connection.py b/mapepire_python/asyncio/connection.py index 61e4740..5ceebf1 100644 --- a/mapepire_python/asyncio/connection.py +++ b/mapepire_python/asyncio/connection.py @@ -1,3 +1,4 @@ +import logging from pathlib import Path from typing import Any, Dict, Optional, Sequence, Union @@ -8,6 +9,8 @@ from ..data_types import DaemonServer, JobStatus from .cursor import AsyncCursor +logger = logging.getLogger(__name__) + class AsyncConnection(aiopep249.AsyncCursorExecuteMixin, aiopep249.AsyncConnection): """ @@ -47,6 +50,7 @@ def __init__(self, database: Union[DaemonServer, dict, Path], opts: Optional[Dic async def _ensure_connected(self) -> None: if self._job.status == JobStatus.NotStarted: + logger.debug("Opening async connection") await self._job.connect(self._database, **self._kwargs) async def cursor(self) -> AsyncCursor: @@ -54,6 +58,7 @@ async def cursor(self) -> AsyncCursor: return AsyncCursor(self, self._job) async def close(self) -> None: + logger.debug("Closing async connection") await self._job.close() async def execute( @@ -94,8 +99,10 @@ def _raise_if_closed(self) -> None: async def commit(self) -> None: self._raise_if_closed() + logger.debug("Committing transaction") await self.execute("COMMIT") async def rollback(self) -> None: self._raise_if_closed() + logger.debug("Rolling back transaction") await self.execute("ROLLBACK") diff --git a/mapepire_python/asyncio/cursor.py b/mapepire_python/asyncio/cursor.py index c1792b3..8c7b895 100644 --- a/mapepire_python/asyncio/cursor.py +++ b/mapepire_python/asyncio/cursor.py @@ -1,3 +1,4 @@ +import logging import weakref from typing import TYPE_CHECKING, List, Optional, Sequence, Type, Union @@ -18,6 +19,8 @@ from ..data_types import QueryOptions from ..pool.pool_query import PoolQuery +logger = logging.getLogger(__name__) + class AsyncCursor( aiopep249.CursorConnectionMixin, @@ -76,6 +79,7 @@ def setoutputsize(self, size: int, column: Optional[int] = None) -> None: async def execute( self, operation: SQLQuery, parameters: Optional[QueryParameters] = None ) -> "AsyncCursor": + logger.debug("Executing statement") opts = QueryOptions(parameters=parameters) self._query = self._job.query(operation, opts=opts) result = await self._query.run() @@ -131,6 +135,7 @@ async def fetchall(self) -> ResultSet: return rows async def close(self) -> None: + logger.debug("Closing cursor") if self._query is not None: await self._query.close() self._query = None diff --git a/mapepire_python/pool/pool_client.py b/mapepire_python/pool/pool_client.py index 155a477..5df3d93 100644 --- a/mapepire_python/pool/pool_client.py +++ b/mapepire_python/pool/pool_client.py @@ -43,6 +43,11 @@ async def init(self): elif self.options.starting_size > self.options.max_size: raise ValueError("Max size must be greater than or equal to starting size") + logger.info( + "Initializing pool: starting_size=%d, max_size=%d", + self.options.starting_size, + self.options.max_size, + ) # Establish the starting connections concurrently. Each connect is a # full WebSocket handshake + connect round-trip, so doing them serially # cost starting_size x RTT before the pool became usable. @@ -131,6 +136,7 @@ async def get_job(self) -> PoolJob: # grow the pool and hand back the fresh connection so THIS query runs on # it, rather than queueing behind in-flight work on an existing one. if least_loaded.get_running_count() > 0 and self.has_space(): + logger.info("Pool growing under load: pool_size=%d, max_size=%d", len(self.jobs), self.options.max_size) return await self._add_job() return least_loaded @@ -161,6 +167,7 @@ async def execute(self, sql: str, opts: Union[QueryOptions, Dict[str, Any]] = No return await job.query_and_run(sql, opts=opts) async def end(self): + logger.info("Ending pool: closing %d connections", len(self.jobs)) # Close all connections concurrently; tolerate individual close errors # so one bad socket doesn't strand the rest open. await asyncio.gather(*(j.close() for j in self.jobs), return_exceptions=True) From ec402a1b8dcb53dee33bd0a8eb65bd599a463e6f Mon Sep 17 00:00:00 2001 From: Najeb Abdullahi Date: Mon, 8 Jun 2026 13:46:48 -0500 Subject: [PATCH 5/5] fix: use %s instead of %d in rows_to_fetch log format specifiers --- mapepire_python/client/query.py | 4 ++-- mapepire_python/pool/pool_query.py | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/mapepire_python/client/query.py b/mapepire_python/client/query.py index 069927b..b7758d7 100644 --- a/mapepire_python/client/query.py +++ b/mapepire_python/client/query.py @@ -109,7 +109,7 @@ def run(self, rows_to_fetch: Optional[int] = None) -> Dict[str, Any]: elif self.state == QueryState.RUN_DONE: raise Exception("Statement has already been fully run") - logger.debug("Executing query: rows_to_fetch=%d", rows_to_fetch) + logger.debug("Executing query: rows_to_fetch=%s", rows_to_fetch) if self.is_cl_command: request = ClRequest( @@ -164,7 +164,7 @@ def fetch_more(self, rows_to_fetch: Optional[int] = None) -> Dict[str, Any]: raise Exception("Statement has already been fully run") assert self._correlation_id is not None - logger.debug("Fetching more rows: rows=%d correlation_id=%s", rows_to_fetch, self._correlation_id) + logger.debug("Fetching more rows: rows=%s correlation_id=%s", rows_to_fetch, self._correlation_id) self._rows_to_fetch = rows_to_fetch query_result = SqlMoreResponse.from_dict( # type: ignore self._execute_query( diff --git a/mapepire_python/pool/pool_query.py b/mapepire_python/pool/pool_query.py index 9a48c14..5d004ef 100644 --- a/mapepire_python/pool/pool_query.py +++ b/mapepire_python/pool/pool_query.py @@ -74,7 +74,7 @@ async def run(self, rows_to_fetch: Optional[int] = None) -> QueryResult: elif self.state == QueryState.RUN_DONE: raise Exception("Statement has already been fully run") - logger.debug("Executing async query: rows_to_fetch=%d", rows_to_fetch) + logger.debug("Executing async query: rows_to_fetch=%s", rows_to_fetch) if self.is_cl_command: request = ClRequest( @@ -128,7 +128,7 @@ async def fetch_more(self, rows_to_fetch: Optional[int] = None) -> SqlMoreRespon raise Exception("Statement has already been fully run") assert self._correlation_id is not None - logger.debug("Fetching more rows: rows=%d correlation_id=%s", rows_to_fetch, self._correlation_id) + logger.debug("Fetching more rows: rows=%s correlation_id=%s", rows_to_fetch, self._correlation_id) self._rows_to_fetch = rows_to_fetch query_result = SqlMoreResponse.from_dict( # type: ignore await self._execute_query(