77from __future__ import annotations
88
99import asyncio
10+ import configparser
1011import collections
1112from collections .abc import Callable
1213import enum
@@ -87,6 +88,9 @@ class SSLNegotiation(compat.StrEnum):
8788 PGPASSFILE = '.pgpass'
8889
8990
91+ PG_SERVICEFILE = '.pg_service.conf'
92+
93+
9094def _read_password_file (passfile : pathlib .Path ) \
9195 -> typing .List [typing .Tuple [str , ...]]:
9296
@@ -268,7 +272,7 @@ def _dot_postgresql_path(filename) -> typing.Optional[pathlib.Path]:
268272
269273
270274def _parse_connect_dsn_and_args (* , dsn , host , port , user ,
271- password , passfile , database , ssl ,
275+ password , passfile , database , ssl , service ,
272276 direct_tls , server_settings ,
273277 target_session_attrs , krbsrvname , gsslib ):
274278 # `auth_hosts` is the version of host information for the purposes
@@ -278,6 +282,120 @@ def _parse_connect_dsn_and_args(*, dsn, host, port, user,
278282 ssl_min_protocol_version = ssl_max_protocol_version = None
279283 sslnegotiation = None
280284
285+ if dsn :
286+ parsed = urllib .parse .urlparse (dsn )
287+ if parsed .query :
288+ query = urllib .parse .parse_qs (parsed .query , strict_parsing = True )
289+ for key , val in query .items ():
290+ if isinstance (val , list ):
291+ query [key ] = val [- 1 ]
292+
293+ if 'service' in query :
294+ val = query .pop ('service' )
295+ if not service and val :
296+ service = val
297+
298+ connection_service_file = os .getenv ('PGSERVICEFILE' )
299+ if connection_service_file is None :
300+ homedir = compat .get_pg_home_directory ()
301+ if homedir :
302+ connection_service_file = homedir / PG_SERVICEFILE
303+ else :
304+ connection_service_file = None
305+ else :
306+ connection_service_file = pathlib .Path (connection_service_file )
307+
308+ if connection_service_file is not None and service is not None :
309+ # TODO Open and parse connection service file
310+ pg_service = configparser .ConfigParser ()
311+ pg_service .read (connection_service_file )
312+ if service in pg_service .sections ():
313+ service_params = pg_service [service ]
314+ if 'port' in service_params :
315+ val = service_params .pop ('port' )
316+ if not port and val :
317+ port = [int (p ) for p in val .split (',' )]
318+
319+ if 'host' in service_params :
320+ val = service_params .pop ('host' )
321+ if not host and val :
322+ host , port = _parse_hostlist (val , port )
323+
324+ if 'dbname' in service_params :
325+ val = service_params .pop ('dbname' )
326+ if database is None :
327+ database = val
328+
329+ if 'database' in service_params :
330+ val = service_params .pop ('database' )
331+ if database is None :
332+ database = val
333+
334+ if 'user' in service_params :
335+ val = service_params .pop ('user' )
336+ if user is None :
337+ user = val
338+
339+ if 'password' in service_params :
340+ val = service_params .pop ('password' )
341+ if password is None :
342+ password = val
343+
344+ if 'passfile' in service_params :
345+ val = service_params .pop ('passfile' )
346+ if passfile is None :
347+ passfile = val
348+
349+ if 'sslmode' in service_params :
350+ val = service_params .pop ('sslmode' )
351+ if ssl is None :
352+ ssl = val
353+
354+ if 'sslcert' in service_params :
355+ sslcert = service_params .pop ('sslcert' )
356+
357+ if 'sslkey' in service_params :
358+ sslkey = service_params .pop ('sslkey' )
359+
360+ if 'sslrootcert' in service_params :
361+ sslrootcert = service_params .pop ('sslrootcert' )
362+
363+ if 'sslnegotiation' in service_params :
364+ sslnegotiation = service_params .pop ('sslnegotiation' )
365+
366+ if 'sslcrl' in service_params :
367+ sslcrl = service_params .pop ('sslcrl' )
368+
369+ if 'sslpassword' in service_params :
370+ sslpassword = service_params .pop ('sslpassword' )
371+
372+ if 'ssl_min_protocol_version' in service_params :
373+ ssl_min_protocol_version = service_params .pop (
374+ 'ssl_min_protocol_version'
375+ )
376+
377+ if 'ssl_max_protocol_version' in service_params :
378+ ssl_max_protocol_version = service_params .pop (
379+ 'ssl_max_protocol_version'
380+ )
381+
382+ if 'target_session_attrs' in service_params :
383+ dsn_target_session_attrs = service_params .pop (
384+ 'target_session_attrs'
385+ )
386+ if target_session_attrs is None :
387+ target_session_attrs = dsn_target_session_attrs
388+
389+ if 'krbsrvname' in service_params :
390+ val = service_params .pop ('krbsrvname' )
391+ if krbsrvname is None :
392+ krbsrvname = val
393+
394+ if 'gsslib' in service_params :
395+ val = service_params .pop ('gsslib' )
396+ if gsslib is None :
397+ gsslib = val
398+
281399 if dsn :
282400 parsed = urllib .parse .urlparse (dsn )
283401
@@ -406,6 +524,9 @@ def _parse_connect_dsn_and_args(*, dsn, host, port, user,
406524 if gsslib is None :
407525 gsslib = val
408526
527+ if 'service' in query :
528+ val = query .pop ('service' )
529+
409530 if query :
410531 if server_settings is None :
411532 server_settings = query
@@ -491,6 +612,7 @@ def _parse_connect_dsn_and_args(*, dsn, host, port, user,
491612 database = database , user = user ,
492613 passfile = passfile )
493614
615+
494616 addrs = []
495617 have_tcp_addrs = False
496618 for h , p in zip (host , port ):
@@ -724,7 +846,7 @@ def _parse_connect_arguments(*, dsn, host, port, user, password, passfile,
724846 max_cached_statement_lifetime ,
725847 max_cacheable_statement_size ,
726848 ssl , direct_tls , server_settings ,
727- target_session_attrs , krbsrvname , gsslib ):
849+ target_session_attrs , krbsrvname , gsslib , service ):
728850 local_vars = locals ()
729851 for var_name in {'max_cacheable_statement_size' ,
730852 'max_cached_statement_lifetime' ,
@@ -754,7 +876,7 @@ def _parse_connect_arguments(*, dsn, host, port, user, password, passfile,
754876 direct_tls = direct_tls , database = database ,
755877 server_settings = server_settings ,
756878 target_session_attrs = target_session_attrs ,
757- krbsrvname = krbsrvname , gsslib = gsslib )
879+ krbsrvname = krbsrvname , gsslib = gsslib , service = service )
758880
759881 config = _ClientConfiguration (
760882 command_timeout = command_timeout ,
0 commit comments