7
7
from __future__ import annotations
8
8
9
9
import asyncio
10
+ import configparser
10
11
import collections
11
12
from collections .abc import Callable
12
13
import enum
@@ -87,6 +88,9 @@ class SSLNegotiation(compat.StrEnum):
87
88
PGPASSFILE = '.pgpass'
88
89
89
90
91
+ PG_SERVICEFILE = '.pg_service.conf'
92
+
93
+
90
94
def _read_password_file (passfile : pathlib .Path ) \
91
95
-> typing .List [typing .Tuple [str , ...]]:
92
96
@@ -271,6 +275,7 @@ def _dot_postgresql_path(filename) -> typing.Optional[pathlib.Path]:
271
275
272
276
def _parse_connect_dsn_and_args (* , dsn , host , port , user ,
273
277
password , passfile , database , ssl ,
278
+ service , servicefile ,
274
279
direct_tls , server_settings ,
275
280
target_session_attrs , krbsrvname , gsslib ):
276
281
# `auth_hosts` is the version of host information for the purposes
@@ -283,6 +288,32 @@ def _parse_connect_dsn_and_args(*, dsn, host, port, user,
283
288
if dsn :
284
289
parsed = urllib .parse .urlparse (dsn )
285
290
291
+ query = None
292
+ if parsed .query :
293
+ query = urllib .parse .parse_qs (parsed .query , strict_parsing = True )
294
+ for key , val in query .items ():
295
+ if isinstance (val , list ):
296
+ query [key ] = val [- 1 ]
297
+
298
+ if 'service' in query :
299
+ val = query .pop ('service' )
300
+ if not service and val :
301
+ service = val
302
+
303
+ connection_service_file = servicefile
304
+
305
+ if connection_service_file is None :
306
+ connection_service_file = os .getenv ('PGSERVICEFILE' )
307
+
308
+ if connection_service_file is None :
309
+ homedir = compat .get_pg_home_directory ()
310
+ if homedir :
311
+ connection_service_file = homedir / PG_SERVICEFILE
312
+ else :
313
+ connection_service_file = None
314
+ else :
315
+ connection_service_file = pathlib .Path (connection_service_file )
316
+
286
317
if parsed .scheme not in {'postgresql' , 'postgres' }:
287
318
raise exceptions .ClientConfigurationError (
288
319
'invalid DSN: scheme is expected to be either '
@@ -317,11 +348,7 @@ def _parse_connect_dsn_and_args(*, dsn, host, port, user,
317
348
if password is None and dsn_password :
318
349
password = urllib .parse .unquote (dsn_password )
319
350
320
- if parsed .query :
321
- query = urllib .parse .parse_qs (parsed .query , strict_parsing = True )
322
- for key , val in query .items ():
323
- if isinstance (val , list ):
324
- query [key ] = val [- 1 ]
351
+ if query :
325
352
326
353
if 'port' in query :
327
354
val = query .pop ('port' )
@@ -408,12 +435,124 @@ def _parse_connect_dsn_and_args(*, dsn, host, port, user,
408
435
if gsslib is None :
409
436
gsslib = val
410
437
438
+ if 'service' in query :
439
+ val = query .pop ('service' )
440
+ if service is None :
441
+ service = val
442
+
411
443
if query :
412
444
if server_settings is None :
413
445
server_settings = query
414
446
else :
415
447
server_settings = {** query , ** server_settings }
416
448
449
+ if connection_service_file is not None and service is not None :
450
+ pg_service = configparser .ConfigParser ()
451
+ pg_service .read (connection_service_file )
452
+ if service in pg_service .sections ():
453
+ service_params = pg_service [service ]
454
+ if 'port' in service_params :
455
+ val = service_params .pop ('port' )
456
+ if not port and val :
457
+ port = [int (p ) for p in val .split (',' )]
458
+
459
+ if 'host' in service_params :
460
+ val = service_params .pop ('host' )
461
+ if not host and val :
462
+ host , port = _parse_hostlist (val , port )
463
+
464
+ if 'dbname' in service_params :
465
+ val = service_params .pop ('dbname' )
466
+ if database is None :
467
+ database = val
468
+
469
+ if 'database' in service_params :
470
+ val = service_params .pop ('database' )
471
+ if database is None :
472
+ database = val
473
+
474
+ if 'user' in service_params :
475
+ val = service_params .pop ('user' )
476
+ if user is None :
477
+ user = val
478
+
479
+ if 'password' in service_params :
480
+ val = service_params .pop ('password' )
481
+ if password is None :
482
+ password = val
483
+
484
+ if 'passfile' in service_params :
485
+ val = service_params .pop ('passfile' )
486
+ if passfile is None :
487
+ passfile = val
488
+
489
+ if 'sslmode' in service_params :
490
+ val = service_params .pop ('sslmode' )
491
+ if ssl is None :
492
+ ssl = val
493
+
494
+ if 'sslcert' in service_params :
495
+ val = service_params .pop ('sslcert' )
496
+ if sslcert is None :
497
+ sslcert = val
498
+
499
+ if 'sslkey' in service_params :
500
+ val = service_params .pop ('sslkey' )
501
+ if sslkey is None :
502
+ sslkey = val
503
+
504
+ if 'sslrootcert' in service_params :
505
+ val = service_params .pop ('sslrootcert' )
506
+ if sslrootcert is None :
507
+ sslrootcert = val
508
+
509
+ if 'sslnegotiation' in service_params :
510
+ val = service_params .pop ('sslnegotiation' )
511
+ if sslnegotiation is None :
512
+ sslnegotiation = val
513
+
514
+ if 'sslcrl' in service_params :
515
+ val = service_params .pop ('sslcrl' )
516
+ if sslcrl is None :
517
+ sslcrl = val
518
+
519
+ if 'sslpassword' in service_params :
520
+ val = service_params .pop ('sslpassword' )
521
+ if sslpassword is None :
522
+ sslpassword = val
523
+
524
+ if 'ssl_min_protocol_version' in service_params :
525
+ val = service_params .pop (
526
+ 'ssl_min_protocol_version'
527
+ )
528
+ if ssl_min_protocol_version is None :
529
+ ssl_min_protocol_version = val
530
+
531
+ if 'ssl_max_protocol_version' in service_params :
532
+ val = service_params .pop (
533
+ 'ssl_max_protocol_version'
534
+ )
535
+ if ssl_max_protocol_version is None :
536
+ ssl_max_protocol_version = val
537
+
538
+ if 'target_session_attrs' in service_params :
539
+ dsn_target_session_attrs = service_params .pop (
540
+ 'target_session_attrs'
541
+ )
542
+ if target_session_attrs is None :
543
+ target_session_attrs = dsn_target_session_attrs
544
+
545
+ if 'krbsrvname' in service_params :
546
+ val = service_params .pop ('krbsrvname' )
547
+ if krbsrvname is None :
548
+ krbsrvname = val
549
+
550
+ if 'gsslib' in service_params :
551
+ val = service_params .pop ('gsslib' )
552
+ if gsslib is None :
553
+ gsslib = val
554
+ if not service :
555
+ service = os .environ .get ('PGSERVICE' )
417
556
if not host :
418
557
hostspec = os .environ .get ('PGHOST' )
419
558
if hostspec :
@@ -726,7 +865,8 @@ def _parse_connect_arguments(*, dsn, host, port, user, password, passfile,
726
865
max_cached_statement_lifetime ,
727
866
max_cacheable_statement_size ,
728
867
ssl , direct_tls , server_settings ,
729
- target_session_attrs , krbsrvname , gsslib ):
868
+ target_session_attrs , krbsrvname , gsslib ,
869
+ service , servicefile ):
730
870
local_vars = locals ()
731
871
for var_name in {'max_cacheable_statement_size' ,
732
872
'max_cached_statement_lifetime' ,
@@ -756,7 +896,8 @@ def _parse_connect_arguments(*, dsn, host, port, user, password, passfile,
756
896
direct_tls = direct_tls , database = database ,
757
897
server_settings = server_settings ,
758
898
target_session_attrs = target_session_attrs ,
759
- krbsrvname = krbsrvname , gsslib = gsslib )
899
+ krbsrvname = krbsrvname , gsslib = gsslib ,
900
+ service = service , servicefile = servicefile )
760
901
761
902
config = _ClientConfiguration (
762
903
command_timeout = command_timeout ,
0 commit comments