11import os
2+ from pydoc import Helper
23import re
34import sys
45import json
1415import psycopg2
1516from psycopg2 import Error , OperationalError
1617from psycopg2 .extensions import (
17- QueryCanceledError , POLL_OK , POLL_READ , POLL_WRITE , STATUS_BEGIN ,
18+ QueryCanceledError , POLL_OK , POLL_READ , POLL_WRITE , STATUS_BEGIN , Union
1819)
19-
20+ from typing import Any , List , Optional , TypedDict , Iterable , Sequence , Mapping , Union , Tuple
2021from . import __version__
2122
23+ KernelDictType = TypedDict (
24+ 'KernelDictType' ,
25+ {
26+ "argv" : List [str ],
27+ "display_name" : str ,
28+ "mimetype" : str ,
29+ "language" : str ,
30+ "name" : str
31+ },
32+ total = False )
33+
34+ HelpLinkDictType = TypedDict (
35+ 'HelpLinkDictType' ,
36+ {
37+ "text" : str ,
38+ "url" : str
39+ },
40+ total = False )
41+
42+ LanguageInfoDictType = TypedDict (
43+ 'LanguageInfoDictType' ,
44+ {
45+ 'mimetype' : str ,
46+ 'name' : str ,
47+ 'file_extension' : str ,
48+ 'version' : str ,
49+ 'help_links' : List [HelpLinkDictType ]
50+ },
51+ total = False )
52+
2253version_pat = re .compile (r'^PostgreSQL (\d+(\.\d+)+)' )
2354CONN_STRING_COMMENT = re .compile (r'--\s*connection:\s*(.*)\s*$' )
2455AUTOCOMMIT_SWITCH_COMMENT = re .compile (r'--\s*autocommit:\s*(\w+)\s*$' )
2556
26- HELP_LINKS = [
57+ HELP_LINKS : List [ HelpLinkDictType ] = [
2758 {
2859 'text' : "PostgreSQL" ,
2960 'url' : "http://www.postgres.cn/docs/12/index.html" ,
3263 'text' : "SQL" ,
3364 'url' : "https://blog.hszofficial.site/TutorialForSQL/#/" ,
3465 },
35- # {
36- # 'text': "PostgreSQL Kernel",
37- # 'url': "https://github.com/Calysto/octave_kernel",
38- # },
66+
3967
4068] + MetaKernel .help_links
4169
4270
43- def get_kernel_json ():
71+ def get_kernel_json () -> KernelDictType :
4472 """Get the kernel json for the kernel."""
4573 here = Path (__file__ )
4674 default_json_file = here .parent .joinpath ('kernel.json' )
@@ -51,11 +79,11 @@ def get_kernel_json():
5179 return data
5280
5381
54- def wait_select_inter (conn ) :
55- """等待连接建立
82+ def wait_select_inter (conn : psycopg2 . connection ) -> None :
83+ """等待连接建立.
5684
5785 Args:
58- conn (psycopg2.Connection ): pg的连接
86+ conn (psycopg2.connection ): pg的连接
5987
6088 Raises:
6189 conn.OperationalError: 连接报错
@@ -83,7 +111,7 @@ class MissingConnection(Exception):
83111
84112
85113class RowsDisplay :
86- def __init__ (self , header , rows ) :
114+ def __init__ (self , header : Union [ str , Mapping [ str , str ], Sequence [ str ]], rows : Union [ Mapping [ str , Iterable ], Iterable [ Iterable ]]) -> None :
87115 self .header = header
88116 self .rows = rows
89117
@@ -98,44 +126,53 @@ def _repr_latex_(self) -> str:
98126
99127
100128class PostgreSQLKernel (MetaKernel ):
101- app_name = 'postgresql_kernel'
102- implementation = 'PostgreSQL Kernel'
103- implementation_version = __version__ ,
104- language = 'sql'
105- help_links = HELP_LINKS
106- kernel_json = Dict (get_kernel_json ()).tag (config = True )
107- _language_version = None
108- _banner = None
129+
130+ app_name : str = 'postgresql_kernel'
131+ implementation : str = 'PostgreSQL Kernel'
132+ implementation_version : str = __version__
133+ language : str = 'sql'
134+ help_links : List [HelpLinkDictType ] = HELP_LINKS
135+ kernel_json : Dict = Dict (get_kernel_json ()).tag (config = True )
136+ _language_version : Optional [str ] = None
137+ _banner : Optional [str ] = None
138+
139+ _conn_string : str
140+ _autocommit : bool
141+ _conn : Optional [psycopg2 .connection ]
109142
110143 @property
111- def language_version (self ):
144+ def language_version (self ) -> str :
112145 if self ._language_version :
113146 return self ._language_version
114-
115- m = version_pat .search (self .banner )
116- if m :
117- self ._language_version = m .group (1 )
118- return self ._language_version
147+ if self .banner :
148+ m = version_pat .search (self .banner )
149+ if m :
150+ self ._language_version = m .group (1 )
151+ return self ._language_version
152+ else :
153+ return "unknown"
119154 else :
120155 return "unknown"
121156
122157 @property
123- def banner (self ):
158+ def banner (self ) -> Optional [ str ] :
124159 if self ._banner is None :
125160 if self ._conn is None :
126161 return 'not yet connected to a database'
127- self ._banner = self .fetchone ('SELECT VERSION();' )[0 ]
162+ res = self .fetchone ('SELECT VERSION();' )
163+ if res and len (res ) >= 1 :
164+ self ._banner = res [0 ]
128165 return self ._banner
129166
130167 @property
131- def language_info (self ):
168+ def language_info (self ) -> LanguageInfoDictType :
132169 return {'mimetype' : 'text/x-sql' ,
133170 'name' : 'sql' ,
134171 'file_extension' : '.sql' ,
135172 'version' : self .language_version ,
136173 'help_links' : HELP_LINKS }
137174
138- def __init__ (self , * args , ** kwargs ):
175+ def __init__ (self , * args : Any , ** kwargs : Any ):
139176 super (PostgreSQLKernel , self ).__init__ (* args , ** kwargs )
140177 psycopg2 .extensions .set_wait_callback (wait_select_inter )
141178 self ._conn_string = os .getenv ('DATABASE_URL' , '' )
@@ -144,7 +181,7 @@ def __init__(self, *args, **kwargs):
144181 if self ._conn_string :
145182 self ._start_connection ()
146183
147- def _start_connection (self ):
184+ def _start_connection (self ) -> None :
148185 """与pg建立连接."""
149186 self .log .info ('starting connection' )
150187 try :
@@ -156,23 +193,25 @@ def _start_connection(self):
156193 self .send_response (self .iopub_socket , 'stream' ,
157194 {'name' : 'stderr' , 'text' : message })
158195
159- def fetchone (self , query ) :
196+ def fetchone (self , query : str ) -> Optional [ Sequence [ Union [ str , bytes , int , float ]]] :
160197 """拉取一行数据
161198
162199 Args:
163200 query (str): 请求的sql语句
164201
165202 Returns:
166- [type]: [description]
203+ Optional[Sequence[Union[str, bytes, int, float]]]: 获取的结果
167204 """
168- self .log .info (f'fetching one from: \n { query } ' )
169- with self ._conn .cursor () as c :
170- c .execute (query )
171- one = c .fetchone ()
172- self .log .info (one )
173- return one
174-
175- def fetchall (self , query ):
205+ if self ._conn :
206+ self .log .info (f'fetching one from: \n { query } ' )
207+ with self ._conn .cursor () as c :
208+ c .execute (query )
209+ one = c .fetchone ()
210+ self .log .info (one )
211+ return one
212+ return None
213+
214+ def fetchall (self , query : str ) -> Tuple [Optional [List [str ]], Optional [List [Sequence [Union [str , bytes , int , float ]]]]]:
176215 """拉取多行数据.
177216
178217 Args:
@@ -181,21 +220,24 @@ def fetchall(self, query):
181220 Returns:
182221 [type]: [description]
183222 """
184- self .log .info (f'fetching all from: \n { query } ' )
185- with self ._conn .cursor () as c :
186- c .execute (query )
187- desc = c .description
188- if desc :
189- keys = [col [0 ] for col in desc ]
190- return keys , c .fetchall ()
191- return None , None
192-
193- def change_connection (self , conn_string ):
223+ if not self ._conn :
224+ raise Exception ("need to connect to pg first" )
225+ else :
226+ self .log .info (f'fetching all from: \n { query } ' )
227+ with self ._conn .cursor () as c :
228+ c .execute (query )
229+ desc = c .description
230+ if desc :
231+ keys = [col [0 ] for col in desc ]
232+ return keys , c .fetchall ()
233+ return None , None
234+
235+ def change_connection (self , conn_string : str ) -> None :
194236 """更换连接的库."""
195237 self ._conn_string = conn_string
196238 self ._start_connection ()
197239
198- def switch_autocommit (self , switch_to ) :
240+ def switch_autocommit (self , switch_to : bool ) -> bool :
199241 """切换是否要自动提交."""
200242 self ._autocommit = switch_to
201243 committed = False
@@ -208,7 +250,7 @@ def switch_autocommit(self, switch_to):
208250 self ._start_connection ()
209251 return committed
210252
211- def change_autocommit_mode (self , switch ) :
253+ def change_autocommit_mode (self , switch : str ) -> None :
212254 """根据输入的字符串切换是否要自动提交.
213255
214256 如果输入的字符串的全小写是true或者false则按指定的值设置,否则抛出错误.
@@ -223,7 +265,7 @@ def change_autocommit_mode(self, switch):
223265 )
224266
225267 switch_bool = (parsed_switch == 'true' )
226- committed = self .switch_autocommit (switch_bool ) | ''
268+ committed = "True" if self .switch_autocommit (switch_bool ) else ''
227269 message = f'committed current transaction & { committed } switched autocommit mode to { self ._autocommit } '
228270
229271 self .send_response (
@@ -233,7 +275,7 @@ def change_autocommit_mode(self, switch):
233275 }
234276 )
235277
236- def get_kernel_help_on (self , info , level = 1 , none_on_fail = False ):
278+ def get_kernel_help_on (self , info : Mapping [ str , str ], level : int = 1 , none_on_fail : bool = False ) -> Optional [ str ] :
237279 self .log .warning ("get kernel help" )
238280 code = info ['code' ].strip ()
239281 if not code or len (code .split ()) > 1 :
@@ -244,7 +286,7 @@ def get_kernel_help_on(self, info, level=1, none_on_fail=False):
244286 shell_magic = self .line_magics ['shell' ]
245287 return shell_magic .get_help_on (info , 1 )
246288
247- def do_execute_meta (self , code ) :
289+ def do_execute_meta (self , code : str ) -> ExceptionWrapper :
248290 """
249291 Execute meta code in the kernel. This uses the execute infrastructure
250292 but allows JavaScript to talk directly to the kernel bypassing normal
@@ -261,11 +303,11 @@ def do_execute_meta(self, code):
261303 tb = traceback .format_exception (exc_type , exc_value , exc_traceback )
262304 return ExceptionWrapper (ename = str (type (e )), evalue = str (e ), traceback = tb )
263305
264- def do_execute_direct (self , code , silent = False ):
306+ def do_execute_direct (self , code : str , silent : bool = False ) -> Optional [ Union [ RowsDisplay , ExceptionWrapper ]] :
265307 if code .strip ().lower () in ['quit' , 'quit()' , 'exit' , 'exit()' ]:
266308 self .do_shutdown (True )
267309 self .payload = [{"source" : "ask_exit" }]
268- return
310+ return None
269311 try :
270312 connection_string = CONN_STRING_COMMENT .findall (code )
271313 autocommit_switch = AUTOCOMMIT_SWITCH_COMMENT .findall (code )
@@ -276,7 +318,7 @@ def do_execute_direct(self, code, silent=False):
276318
277319 code = AUTOCOMMIT_SWITCH_COMMENT .sub ('' , CONN_STRING_COMMENT .sub ('' , code ))
278320 if not code .strip ():
279- return
321+ return None
280322 if self ._conn is None :
281323 raise MissingConnection (f'''\
282324 Error: Unable to connect to a database at "{ self ._conn_string } ".
@@ -289,8 +331,7 @@ def do_execute_direct(self, code, silent=False):
289331 self ._conn .rollback ()
290332 raise qce
291333 except Error as e :
292- self .send_response (self .iopub_socket , 'stream' ,
293- {'name' : 'stderr' , 'text' : str (e )})
334+ self .send_response (self .iopub_socket , 'stream' , {'name' : 'stderr' , 'text' : str (e )})
294335 self ._conn .rollback ()
295336 raise e
296337 else :
@@ -308,9 +349,10 @@ def do_execute_direct(self, code, silent=False):
308349 'text' : str (notice )
309350 })
310351 self ._conn .notices = []
311- if header is not None and len (rows ) > 0 :
352+ if header is not None and rows and len (rows ) > 0 :
312353 return RowsDisplay (header , rows )
313-
354+ else :
355+ return None
314356 except Exception as e :
315357 exc_type , exc_value , exc_traceback = sys .exc_info ()
316358 tb = traceback .format_exception (exc_type , exc_value , exc_traceback )
0 commit comments