1
1
from __future__ import annotations
2
2
3
+ from typing import Any , Callable , Coroutine , List , Optional , Tuple , TypeVar
4
+
3
5
import pyodbc
4
6
5
7
from .log import logger
8
10
__all__ = ["Cursor" ]
9
11
10
12
13
+ _T = TypeVar ("_T" )
14
+
15
+
11
16
class Cursor :
12
17
"""Cursors represent a database cursor (and map to ODBC HSTMTs), which
13
18
is used to manage the context of a fetch operation.
@@ -17,13 +22,23 @@ class Cursor:
17
22
the other cursors.
18
23
"""
19
24
20
- def __init__ (self , pyodbc_cursor : pyodbc .Cursor , connection , echo = False ):
25
+ def __init__ (
26
+ self ,
27
+ pyodbc_cursor : pyodbc .Cursor ,
28
+ connection : Connection ,
29
+ echo : bool = False ,
30
+ ) -> None :
21
31
self ._conn = connection
22
32
self ._impl : pyodbc .Cursor = pyodbc_cursor
23
33
self ._loop = connection .loop
24
34
self ._echo : bool = echo
25
35
26
- async def _run_operation (self , func , * args , ** kwargs ):
36
+ async def _run_operation (
37
+ self ,
38
+ func : Callable [..., _T ],
39
+ * args : Any ,
40
+ ** kwargs : Any ,
41
+ ) -> _T :
27
42
# execute func in thread pool of attached to cursor connection
28
43
if not self ._conn :
29
44
raise pyodbc .OperationalError ("Cursor is closed." )
@@ -37,17 +52,17 @@ async def _run_operation(self, func, *args, **kwargs):
37
52
raise
38
53
39
54
@property
40
- def echo (self ):
55
+ def echo (self ) -> bool :
41
56
"""Return echo mode status."""
42
57
return self ._echo
43
58
44
59
@property
45
- def connection (self ):
60
+ def connection (self ) -> Optional [ Connection ] :
46
61
"""Cursors database connection"""
47
62
return self ._conn
48
63
49
64
@property
50
- def autocommit (self ):
65
+ def autocommit (self ) -> bool :
51
66
"""Show autocommit mode for current database session. True if
52
67
connection is in autocommit mode; False otherwse. The default
53
68
is False.
@@ -56,12 +71,12 @@ def autocommit(self):
56
71
return self ._conn .autocommit
57
72
58
73
@autocommit .setter
59
- def autocommit (self , value ) :
74
+ def autocommit (self , value : bool ) -> None :
60
75
assert self ._conn is not None # mypy
61
76
self ._conn .autocommit = value
62
77
63
78
@property
64
- def rowcount (self ):
79
+ def rowcount (self ) -> int :
65
80
"""The number of rows modified by the previous DDL statement.
66
81
67
82
This is -1 if no SQL has been executed or if the number of rows is
@@ -73,7 +88,7 @@ def rowcount(self):
73
88
return self ._impl .rowcount
74
89
75
90
@property
76
- def description (self ):
91
+ def description (self ) -> Tuple [ Tuple [ str , Any , int , int , int , int , bool ]] :
77
92
"""This read-only attribute is a list of 7-item tuples, each
78
93
containing (name, type_code, display_size, internal_size, precision,
79
94
scale, null_ok).
@@ -91,23 +106,23 @@ def description(self):
91
106
return self ._impl .description
92
107
93
108
@property
94
- def closed (self ):
109
+ def closed (self ) -> bool :
95
110
"""Read only property indicates if cursor has been closed"""
96
111
return self ._conn is None
97
112
98
113
@property
99
- def arraysize (self ):
114
+ def arraysize (self ) -> int :
100
115
"""This read/write attribute specifies the number of rows to fetch
101
116
at a time with .fetchmany() . It defaults to 1 meaning to fetch a
102
117
single row at a time.
103
118
"""
104
119
return self ._impl .arraysize
105
120
106
121
@arraysize .setter
107
- def arraysize (self , size ) :
122
+ def arraysize (self , size : int ) -> None :
108
123
self ._impl .arraysize = size
109
124
110
- async def close (self ):
125
+ async def close (self ) -> None :
111
126
"""Close the cursor now (rather than whenever __del__ is called).
112
127
113
128
The cursor will be unusable from this point forward; an Error
@@ -119,7 +134,7 @@ async def close(self):
119
134
await self ._run_operation (self ._impl .close )
120
135
self ._conn = None
121
136
122
- async def execute (self , sql , * params ) :
137
+ async def execute (self , sql : str , * params : Any ) -> Cursor :
123
138
"""Executes the given operation substituting any markers with
124
139
the given parameters.
125
140
@@ -136,7 +151,7 @@ async def execute(self, sql, *params):
136
151
await self ._run_operation (self ._impl .execute , sql , * params )
137
152
return self
138
153
139
- def executemany (self , sql , * params ) :
154
+ def executemany (self , sql : str , * params : Any ) -> Coroutine [ Any , Any , None ] :
140
155
"""Prepare a database query or command and then execute it against
141
156
all parameter sequences found in the sequence seq_of_params.
142
157
@@ -157,7 +172,7 @@ async def setoutputsize(self, *args, **kwargs):
157
172
"""Does nothing, required by DB API."""
158
173
return None
159
174
160
- def fetchone (self ):
175
+ def fetchone (self ) -> Coroutine [ Any , Any , Optional [ pyodbc . Row ]] :
161
176
"""Returns the next row or None when no more data is available.
162
177
163
178
A ProgrammingError exception is raised if no SQL has been executed
@@ -167,7 +182,7 @@ def fetchone(self):
167
182
fut = self ._run_operation (self ._impl .fetchone )
168
183
return fut
169
184
170
- def fetchall (self ):
185
+ def fetchall (self ) -> Coroutine [ Any , Any , List [ pyodbc . Row ]] :
171
186
"""Returns a list of all remaining rows.
172
187
173
188
Since this reads all rows into memory, it should not be used if
@@ -181,7 +196,7 @@ def fetchall(self):
181
196
fut = self ._run_operation (self ._impl .fetchall )
182
197
return fut
183
198
184
- def fetchmany (self , size = 0 ) :
199
+ def fetchmany (self , size : int = 0 ) -> Coroutine [ Any , Any , List [ pyodbc . Row ]] :
185
200
"""Returns a list of remaining rows, containing no more than size
186
201
rows, used to process results in chunks. The list will be empty when
187
202
there are no more rows.
@@ -200,7 +215,7 @@ def fetchmany(self, size=0):
200
215
fut = self ._run_operation (self ._impl .fetchmany , size )
201
216
return fut
202
217
203
- def nextset (self ):
218
+ def nextset (self ) -> Coroutine [ Any , Any , bool ] :
204
219
"""This method will make the cursor skip to the next available
205
220
set, discarding any remaining rows from the current set.
206
221
@@ -214,7 +229,13 @@ def nextset(self):
214
229
fut = self ._run_operation (self ._impl .nextset )
215
230
return fut
216
231
217
- def tables (self , ** kw ):
232
+ def tables (
233
+ self ,
234
+ table : Optional [str ] = None ,
235
+ catalog : Optional [str ] = None ,
236
+ schema : Optional [str ] = None ,
237
+ tableType : Optional [str ] = None ,
238
+ ) -> Coroutine [Any , Any , pyodbc .Cursor ]:
218
239
"""Creates a result set of tables in the database that match the
219
240
given criteria.
220
241
@@ -223,10 +244,22 @@ def tables(self, **kw):
223
244
:param schema: the schmea name
224
245
:param tableType: one of TABLE, VIEW, SYSTEM TABLE ...
225
246
"""
226
- fut = self ._run_operation (self ._impl .tables , ** kw )
247
+ fut = self ._run_operation (
248
+ self ._impl .tables ,
249
+ table = table ,
250
+ catalog = catalog ,
251
+ schema = schema ,
252
+ tableType = tableType ,
253
+ )
227
254
return fut
228
255
229
- def columns (self , ** kw ):
256
+ def columns (
257
+ self ,
258
+ table : Optional [str ] = None ,
259
+ catalog : Optional [str ] = None ,
260
+ schema : Optional [str ] = None ,
261
+ column : Optional [str ] = None ,
262
+ ) -> Coroutine [Any , Any , pyodbc .Cursor ]:
230
263
"""Creates a results set of column names in specified tables by
231
264
executing the ODBC SQLColumns function. Each row fetched has the
232
265
following columns.
@@ -236,10 +269,23 @@ def columns(self, **kw):
236
269
:param schema: the schmea name
237
270
:param column: string search pattern for column names.
238
271
"""
239
- fut = self ._run_operation (self ._impl .columns , ** kw )
272
+ fut = self ._run_operation (
273
+ self ._impl .columns ,
274
+ table = table ,
275
+ catalog = catalog ,
276
+ schema = schema ,
277
+ column = column ,
278
+ )
240
279
return fut
241
280
242
- def statistics (self , table , catalog = None , schema = None , unique = False , quick = True ):
281
+ def statistics (
282
+ self ,
283
+ table : str ,
284
+ catalog : Optional [str ] = None ,
285
+ schema : Optional [str ] = None ,
286
+ unique : bool = False ,
287
+ quick : bool = True ,
288
+ ) -> Coroutine [Any , Any , pyodbc .Cursor ]:
243
289
"""Creates a results set of statistics about a single table and
244
290
the indexes associated with the table by executing SQLStatistics.
245
291
@@ -262,8 +308,12 @@ def statistics(self, table, catalog=None, schema=None, unique=False, quick=True)
262
308
return fut
263
309
264
310
def rowIdColumns (
265
- self , table , catalog = None , schema = None , nullable = True # nopep8
266
- ):
311
+ self ,
312
+ table : str ,
313
+ catalog : Optional [str ] = None ,
314
+ schema : Optional [str ] = None ,
315
+ nullable : bool = True ,
316
+ ) -> Coroutine [Any , Any , pyodbc .Cursor ]:
267
317
"""Executes SQLSpecialColumns with SQL_BEST_ROWID which creates a
268
318
result set of columns that uniquely identify a row
269
319
"""
@@ -277,8 +327,12 @@ def rowIdColumns(
277
327
return fut
278
328
279
329
def rowVerColumns (
280
- self , table , catalog = None , schema = None , nullable = True # nopep8
281
- ):
330
+ self ,
331
+ table : str ,
332
+ catalog : Optional [str ] = None ,
333
+ schema : Optional [str ] = None ,
334
+ nullable : bool = True ,
335
+ ) -> Coroutine [Any , Any , pyodbc .Cursor ]:
282
336
"""Executes SQLSpecialColumns with SQL_ROWVER which creates a
283
337
result set of columns that are automatically updated when any
284
338
value in the row is updated.
@@ -292,68 +346,112 @@ def rowVerColumns(
292
346
)
293
347
return fut
294
348
295
- def primaryKeys (self , table , catalog = None , schema = None ): # nopep8
349
+ def primaryKeys (
350
+ self ,
351
+ table : str ,
352
+ catalog : Optional [str ] = None ,
353
+ schema : Optional [str ] = None ,
354
+ ) -> Coroutine [Any , Any , pyodbc .Cursor ]:
296
355
"""Creates a result set of column names that make up the primary key
297
356
for a table by executing the SQLPrimaryKeys function."""
298
357
fut = self ._run_operation (
299
358
self ._impl .primaryKeys , table , catalog = catalog , schema = schema
300
359
)
301
360
return fut
302
361
303
- def foreignKeys (self , * a , ** kw ): # nopep8
362
+ def foreignKeys (
363
+ self ,
364
+ table : Optional [str ] = None ,
365
+ catalog : Optional [str ] = None ,
366
+ schema : Optional [str ] = None ,
367
+ foreignTable : Optional [str ] = None ,
368
+ foreignCatalog : Optional [str ] = None ,
369
+ foreignSchema : Optional [str ] = None ,
370
+ ) -> Coroutine [Any , Any , pyodbc .Cursor ]:
304
371
"""Executes the SQLForeignKeys function and creates a result set
305
372
of column names that are foreign keys in the specified table (columns
306
373
in the specified table that refer to primary keys in other tables)
307
374
or foreign keys in other tables that refer to the primary key in
308
375
the specified table.
309
376
"""
310
- fut = self ._run_operation (self ._impl .foreignKeys , * a , ** kw )
377
+ fut = self ._run_operation (
378
+ self ._impl .foreignKeys ,
379
+ table = table ,
380
+ catalog = catalog ,
381
+ schema = schema ,
382
+ foreignTable = foreignTable ,
383
+ foreignCatalog = foreignCatalog ,
384
+ foreignSchema = foreignSchema ,
385
+ )
311
386
return fut
312
387
313
- def getTypeInfo (self , sql_type ): # nopep8
388
+ def getTypeInfo (
389
+ self ,
390
+ sql_type : Optional [int ] = None ,
391
+ ) -> Coroutine [Any , Any , pyodbc .Cursor ]:
314
392
"""Executes SQLGetTypeInfo a creates a result set with information
315
393
about the specified data type or all data types supported by the
316
394
ODBC driver if not specified.
317
395
"""
318
396
fut = self ._run_operation (self ._impl .getTypeInfo , sql_type )
319
397
return fut
320
398
321
- def procedures (self , * a , ** kw ):
399
+ def procedures (
400
+ self ,
401
+ procedure : Optional [str ] = None ,
402
+ catalog : Optional [str ] = None ,
403
+ schema : Optional [str ] = None ,
404
+ ) -> Coroutine [Any , Any , pyodbc .Cursor ]:
322
405
"""Executes SQLProcedures and creates a result set of information
323
406
about the procedures in the data source.
324
407
"""
325
- fut = self ._run_operation (self ._impl .procedures , * a , ** kw )
408
+ fut = self ._run_operation (
409
+ self ._impl .procedures ,
410
+ procedure = procedure ,
411
+ catalog = catalog ,
412
+ schema = schema ,
413
+ )
326
414
return fut
327
415
328
- def procedureColumns (self , * a , ** kw ): # nopep8
329
- fut = self ._run_operation (self ._impl .procedureColumns , * a , ** kw )
416
+ def procedureColumns (
417
+ self ,
418
+ procedure : Optional [str ] = None ,
419
+ catalog : Optional [str ] = None ,
420
+ schema : Optional [str ] = None ,
421
+ ) -> Coroutine [Any , Any , pyodbc .Cursor ]:
422
+ fut = self ._run_operation (
423
+ self ._impl .procedureColumns ,
424
+ procedure = procedure ,
425
+ catalog = catalog ,
426
+ schema = schema ,
427
+ )
330
428
return fut
331
429
332
- def skip (self , count ) :
430
+ def skip (self , count : int ) -> Coroutine [ Any , Any , None ] :
333
431
fut = self ._run_operation (self ._impl .skip , count )
334
432
return fut
335
433
336
- def commit (self ):
434
+ def commit (self ) -> Coroutine [ Any , Any , None ] :
337
435
fut = self ._run_operation (self ._impl .commit )
338
436
return fut
339
437
340
- def rollback (self ):
438
+ def rollback (self ) -> Coroutine [ Any , Any , None ] :
341
439
fut = self ._run_operation (self ._impl .rollback )
342
440
return fut
343
441
344
- def __aiter__ (self ):
442
+ def __aiter__ (self ) -> Cursor :
345
443
return self
346
444
347
- async def __anext__ (self ):
445
+ async def __anext__ (self ) -> pyodbc . Row :
348
446
ret = await self .fetchone ()
349
447
if ret is not None :
350
448
return ret
351
449
else :
352
450
raise StopAsyncIteration
353
451
354
- async def __aenter__ (self ):
452
+ async def __aenter__ (self ) -> Cursor :
355
453
return self
356
454
357
- async def __aexit__ (self , exc_type , exc_val , exc_tb ):
455
+ async def __aexit__ (self , exc_type , exc_val , exc_tb ) -> None :
358
456
await self .close ()
359
457
return
0 commit comments