22import configparser
33import io
44import platform
5+ import stat
56from os import getenv
67from pathlib import Path
78from typing import Optional
1011from .exceptions import ServiceFileNotFound , ServiceNotFound
1112
1213
14+ def _make_file_writable (path : Path ):
15+ """Attempt to add write permissions to a file.
16+
17+ Args:
18+ path: path to the file
19+
20+ Raises:
21+ PermissionError: when the file permissions cannot be changed
22+ """
23+ current_permission = stat .S_IMODE (path .stat ().st_mode )
24+ WRITE = stat .S_IWUSR | stat .S_IWGRP | stat .S_IWOTH
25+ path .chmod (current_permission | WRITE )
26+
27+
28+ def _when_read_only_try_to_add_write_permission (func ):
29+ """Decorator for functions that attempt to modify the service file.
30+
31+ If the file is read-only, a PermissionError exception will be raised.
32+ This decorator handles that error by attempting to set write permissions
33+ (which works if the user is the owner of the file or has proper rights to
34+ alter the file permissions), and rerunning the decorated function.
35+
36+ If the user cannot modify permissions on the file, the PermissionError
37+ is re-raised.
38+ """
39+
40+ def wrapper (* args , ** kwargs ):
41+ attempt = 0
42+ while attempt <= 1 :
43+ try :
44+ return func (* args , ** kwargs )
45+ except PermissionError :
46+ if attempt == 1 :
47+ raise
48+
49+ try :
50+ _make_file_writable (conf_path ())
51+ except PermissionError :
52+ pass
53+ finally :
54+ attempt += 1
55+
56+ return wrapper
57+
58+
1359def conf_path (create_if_missing : Optional [bool ] = False ) -> Path :
1460 """Returns the path found for the pg_service.conf on the system as string.
1561
@@ -66,6 +112,7 @@ def full_config(conf_file_path: Optional[Path] = None) -> configparser.ConfigPar
66112 return config
67113
68114
115+ @_when_read_only_try_to_add_write_permission
69116def remove_service (service_name : str , conf_file_path : Optional [Path ] = None ) -> None :
70117 """Remove a complete service from the service file.
71118
@@ -92,6 +139,7 @@ def remove_service(service_name: str, conf_file_path: Optional[Path] = None) ->
92139 config .write (configfile , space_around_delimiters = False )
93140
94141
142+ @_when_read_only_try_to_add_write_permission
95143def rename_service (old_name : str , new_name : str , conf_file_path : Optional [Path ] = None ) -> None :
96144 """Rename a service in the service file.
97145
@@ -124,6 +172,7 @@ def rename_service(old_name: str, new_name: str, conf_file_path: Optional[Path]
124172 config .write (configfile , space_around_delimiters = False )
125173
126174
175+ @_when_read_only_try_to_add_write_permission
127176def create_service (service_name : str , settings : dict , conf_file_path : Optional [Path ] = None ) -> bool :
128177 """Create a new service in the service file.
129178
@@ -153,6 +202,7 @@ def create_service(service_name: str, settings: dict, conf_file_path: Optional[P
153202 return True
154203
155204
205+ @_when_read_only_try_to_add_write_permission
156206def copy_service_settings (
157207 source_service_name : str ,
158208 target_service_name : str ,
@@ -217,6 +267,7 @@ def service_config(service_name: str, conf_file_path: Optional[Path] = None) ->
217267 return dict (config [service_name ])
218268
219269
270+ @_when_read_only_try_to_add_write_permission
220271def write_service_setting (
221272 service_name : str ,
222273 setting_key : str ,
@@ -250,6 +301,7 @@ def write_service_setting(
250301 config .write (configfile , space_around_delimiters = False )
251302
252303
304+ @_when_read_only_try_to_add_write_permission
253305def write_service (
254306 service_name : str , settings : dict , conf_file_path : Optional [Path ] = None , create_if_not_found : bool = False
255307) -> dict :
@@ -309,11 +361,13 @@ def write_service_to_text(service_name: str, settings: dict) -> str:
309361 return res .strip ()
310362
311363
312- def service_names (conf_file_path : Optional [Path ] = None ) -> list [str ]:
364+ def service_names (conf_file_path : Optional [Path ] = None , sorted_alphabetically : bool = False ) -> list [str ]:
313365 """Returns all service names in a list.
314366
315367 Args:
316368 conf_file_path: path to the pg_service.conf. If None the `conf_path()` is used, defaults to None
369+ sorted_alphabetically: whether to sort the names alphabetically (case-insensitive),
370+ defaults to False
317371
318372 Returns:
319373 list of every service registered
@@ -323,4 +377,5 @@ def service_names(conf_file_path: Optional[Path] = None) -> list[str]:
323377 """
324378
325379 config = full_config (conf_file_path )
326- return config .sections ()
380+ names = config .sections ()
381+ return sorted (names , key = str .lower ) if sorted_alphabetically else names
0 commit comments