Coverage for python/lsst/daf/butler/registry/connectionString.py: 38%

30 statements  

« prev     ^ index     » next       coverage.py v7.4.4, created at 2024-03-30 09:59 +0000

1# This file is part of daf_butler. 

2# 

3# Developed for the LSST Data Management System. 

4# This product includes software developed by the LSST Project 

5# (http://www.lsst.org). 

6# See the COPYRIGHT file at the top-level directory of this distribution 

7# for details of code ownership. 

8# 

9# This software is dual licensed under the GNU General Public License and also 

10# under a 3-clause BSD license. Recipients may choose which of these licenses 

11# to use; please see the files gpl-3.0.txt and/or bsd_license.txt, 

12# respectively. If you choose the GPL option then the following text applies 

13# (but note that there is still no warranty even if you opt for BSD instead): 

14# 

15# This program is free software: you can redistribute it and/or modify 

16# it under the terms of the GNU General Public License as published by 

17# the Free Software Foundation, either version 3 of the License, or 

18# (at your option) any later version. 

19# 

20# This program is distributed in the hope that it will be useful, 

21# but WITHOUT ANY WARRANTY; without even the implied warranty of 

22# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 

23# GNU General Public License for more details. 

24# 

25# You should have received a copy of the GNU General Public License 

26# along with this program. If not, see <http://www.gnu.org/licenses/>. 

27 

28from __future__ import annotations 

29 

30__all__ = ("DB_AUTH_ENVVAR", "DB_AUTH_PATH", "ConnectionStringFactory") 

31 

32from typing import TYPE_CHECKING 

33 

34from lsst.utils.db_auth import DbAuth, DbAuthNotFoundError 

35from sqlalchemy.engine import url 

36 

37if TYPE_CHECKING: 

38 from ._config import RegistryConfig 

39 

40DB_AUTH_ENVVAR = "LSST_DB_AUTH" 

41"""Default name of the environmental variable that will be used to locate DB 

42credentials configuration file. """ 

43 

44DB_AUTH_PATH = "~/.lsst/db-auth.yaml" 

45"""Default path at which it is expected that DB credentials are found.""" 

46 

47 

48class ConnectionStringFactory: 

49 """Factory for `sqlalchemy.engine.url.URL` instances. 

50 

51 The factory constructs a connection string URL object by parsing the 

52 connection string, the 'db' key in the registry configuration. 

53 Username, password, host, port or database can be specified as keys in the 

54 config explicitly. If username or password are missing a matching DB is 

55 found in the credentials file pointed to by `DB_AUTH_ENVVAR` or 

56 `DB_AUTH_PATH` values. 

57 """ 

58 

59 keys = ("username", "password", "host", "port", "database") 

60 

61 @classmethod 

62 def fromConfig(cls, registryConfig: RegistryConfig) -> url.URL: 

63 """Parse the `db`, and, if they exist, username, password, host, port 

64 and database keys from the given config. 

65 

66 If no username and password are found in the connection string, or in 

67 the config, they are retrieved from a file at `DB_AUTH_PATH` or 

68 `DB_AUTH_ENVVAR`. Sqlite dialect does not require a password. 

69 

70 The `db` key value of the given config specifies the default connection 

71 string, such that if no additional connection string parameters are 

72 provided or retrieved, the `db` key is returned unmodified. 

73 

74 Parameters 

75 ---------- 

76 registryConfig : `RegistryConfig` 

77 Registry configuration. 

78 

79 Returns 

80 ------- 

81 connectionString : `sqlalchemy.engine.url.URL` 

82 URL object representing the connection string. 

83 

84 Raises 

85 ------ 

86 DbAuthPermissionsError 

87 If the credentials file has incorrect permissions. 

88 DbAuthError 

89 A problem occurred when retrieving DB authentication. 

90 

91 Notes 

92 ----- 

93 Matching requires dialect, host and database keys. If dialect is not 

94 specified in the db string and host and database keys are not found in 

95 the db string, or as explicit keys in the config, the matcher returns 

96 an unchanged connection string. 

97 Insufficiently specified connection strings are interpreted as an 

98 indication that a 3rd party authentication mechanism, such as Oracle 

99 Wallet, is being used and therefore are left unmodified. 

100 """ 

101 # this import can not live on the top because of circular import issue 

102 from ._config import RegistryConfig 

103 

104 regConf = RegistryConfig(registryConfig) 

105 conStr = url.make_url(regConf["db"]) 

106 

107 for key in cls.keys: 

108 if getattr(conStr, key) is None: 

109 # check for SQLAlchemy >= 1.4 

110 if hasattr(conStr, "set"): 

111 conStr = conStr.set(**{key: regConf.get(key)}) 

112 else: 

113 # SQLAlchemy 1.3 or earlier, mutate in place 

114 setattr(conStr, key, regConf.get(key)) 

115 

116 # Allow 3rd party authentication mechanisms by assuming connection 

117 # string is correct when we can not recognize (dialect, host, database) 

118 # matching keys. 

119 if any((conStr.drivername is None, conStr.host is None, conStr.database is None)): 

120 return conStr 

121 

122 # Ignore when credentials are not set up, or when no matches are found 

123 try: 

124 dbAuth = DbAuth(DB_AUTH_PATH, DB_AUTH_ENVVAR) 

125 auth = dbAuth.getAuth( 

126 conStr.drivername, conStr.username, conStr.host, conStr.port, conStr.database 

127 ) 

128 except DbAuthNotFoundError: 

129 # credentials file doesn't exist or no matches were found 

130 pass 

131 else: 

132 # only assign auth when *no* errors were raised 

133 conStr = conStr.set(username=auth[0], password=auth[1]) 

134 

135 return conStr