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

Shortcuts on this page

r m x p   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

33 statements  

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 program is free software: you can redistribute it and/or modify 

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

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

12# (at your option) any later version. 

13# 

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

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

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

17# GNU General Public License for more details. 

18# 

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

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

21 

22from __future__ import annotations 

23 

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

25 

26from typing import TYPE_CHECKING 

27 

28from sqlalchemy.engine import url 

29from ._dbAuth import DbAuth, DbAuthNotFoundError 

30 

31if TYPE_CHECKING: 31 ↛ 32line 31 didn't jump to line 32, because the condition on line 31 was never true

32 from ._config import RegistryConfig 

33 

34DB_AUTH_ENVVAR = "LSST_DB_AUTH" 

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

36credentials configuration file. """ 

37 

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

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

40 

41 

42class ConnectionStringFactory: 

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

44 

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

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

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

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

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

50 `DB_AUTH_PATH` values. 

51 """ 

52 

53 keys = ('username', 'password', 'host', 'port', 'database') 

54 

55 @classmethod 

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

57 """Parses the `db`, and, if they exist, username, password, host, port 

58 and database keys from the given config. 

59 

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

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

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

63 

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

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

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

67 

68 Parameters 

69 ---------- 

70 config : `ButlerConfig`, `RegistryConfig`, `Config` or `str` 

71 Registry configuration 

72 

73 Returns 

74 ------- 

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

76 URL object representing the connection string. 

77 

78 Raises 

79 ------ 

80 DbAuthPermissionsError 

81 If the credentials file has incorrect permissions. 

82 DbAuthError 

83 A problem occurred when retrieving DB authentication. 

84 

85 Notes 

86 ----- 

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

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

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

90 an unchanged connection string. 

91 Insufficiently specified connection strings are interpreted as an 

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

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

94 """ 

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

96 from ._config import RegistryConfig 

97 regConf = RegistryConfig(registryConfig) 

98 conStr = url.make_url(regConf['db']) 

99 

100 for key in cls.keys: 

101 if getattr(conStr, key) is None: 

102 # check for SQLAlchemy >= 1.4 

103 if hasattr(conStr, "set"): 

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

105 else: 

106 # SQLAlchemy 1.3 or earlier, mutate in place 

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

108 

109 # Allow 3rd party authentication mechanisms by assuming connection 

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

111 # matching keys. 

112 if any((conStr.drivername is None, 

113 conStr.host is None, 

114 conStr.database is None)): 

115 return conStr 

116 

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

118 try: 

119 dbAuth = DbAuth(DB_AUTH_PATH, DB_AUTH_ENVVAR) 

120 auth = dbAuth.getAuth(conStr.drivername, conStr.username, conStr.host, 

121 conStr.port, conStr.database) 

122 except DbAuthNotFoundError: 

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

124 pass 

125 else: 

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

127 if hasattr(conStr, "set"): 

128 # for SQLAlchemy >= 1.4 

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

130 else: 

131 # SQLAlchemy 1.3 or earlier, mutate in place 

132 conStr.username = auth[0] 

133 conStr.password = auth[1] 

134 

135 return conStr