Coverage for python/lsst/daf/butler/registry/connectionString.py: 35%
35 statements
« prev ^ index » next coverage.py v6.4.2, created at 2022-08-04 02:20 -0700
« prev ^ index » next coverage.py v6.4.2, created at 2022-08-04 02:20 -0700
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/>.
22from __future__ import annotations
24__all__ = ("DB_AUTH_ENVVAR", "DB_AUTH_PATH", "ConnectionStringFactory")
26from typing import TYPE_CHECKING
28from sqlalchemy.engine import url
30from ._dbAuth import DbAuth, DbAuthNotFoundError
32if TYPE_CHECKING: 32 ↛ 33line 32 didn't jump to line 33, because the condition on line 32 was never true
33 from ._config import RegistryConfig
35DB_AUTH_ENVVAR = "LSST_DB_AUTH"
36"""Default name of the environmental variable that will be used to locate DB
37credentials configuration file. """
39DB_AUTH_PATH = "~/.lsst/db-auth.yaml"
40"""Default path at which it is expected that DB credentials are found."""
43class ConnectionStringFactory:
44 """Factory for `sqlalchemy.engine.url.URL` instances.
46 The factory constructs a connection string URL object by parsing the
47 connection string, the 'db' key in the registry configuration.
48 Username, password, host, port or database can be specified as keys in the
49 config explicitly. If username or password are missing a matching DB is
50 found in the credentials file pointed to by `DB_AUTH_ENVVAR` or
51 `DB_AUTH_PATH` values.
52 """
54 keys = ("username", "password", "host", "port", "database")
56 @classmethod
57 def fromConfig(cls, registryConfig: RegistryConfig) -> url.URL:
58 """Parses the `db`, and, if they exist, username, password, host, port
59 and database keys from the given config.
61 If no username and password are found in the connection string, or in
62 the config, they are retrieved from a file at `DB_AUTH_PATH` or
63 `DB_AUTH_ENVVAR`. Sqlite dialect does not require a password.
65 The `db` key value of the given config specifies the default connection
66 string, such that if no additional connection string parameters are
67 provided or retrieved, the `db` key is returned unmodified.
69 Parameters
70 ----------
71 config : `ButlerConfig`, `RegistryConfig`, `Config` or `str`
72 Registry configuration
74 Returns
75 -------
76 connectionString : `sqlalchemy.engine.url.URL`
77 URL object representing the connection string.
79 Raises
80 ------
81 DbAuthPermissionsError
82 If the credentials file has incorrect permissions.
83 DbAuthError
84 A problem occurred when retrieving DB authentication.
86 Notes
87 -----
88 Matching requires dialect, host and database keys. If dialect is not
89 specified in the db string and host and database keys are not found in
90 the db string, or as explicit keys in the config, the matcher returns
91 an unchanged connection string.
92 Insufficiently specified connection strings are interpreted as an
93 indication that a 3rd party authentication mechanism, such as Oracle
94 Wallet, is being used and therefore are left unmodified.
95 """
96 # this import can not live on the top because of circular import issue
97 from ._config import RegistryConfig
99 regConf = RegistryConfig(registryConfig)
100 conStr = url.make_url(regConf["db"])
102 for key in cls.keys:
103 if getattr(conStr, key) is None:
104 # check for SQLAlchemy >= 1.4
105 if hasattr(conStr, "set"):
106 conStr = conStr.set(**{key: regConf.get(key)})
107 else:
108 # SQLAlchemy 1.3 or earlier, mutate in place
109 setattr(conStr, key, regConf.get(key))
111 # Allow 3rd party authentication mechanisms by assuming connection
112 # string is correct when we can not recognize (dialect, host, database)
113 # matching keys.
114 if any((conStr.drivername is None, conStr.host is None, conStr.database is None)):
115 return conStr
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(
121 conStr.drivername, conStr.username, conStr.host, conStr.port, conStr.database
122 )
123 except DbAuthNotFoundError:
124 # credentials file doesn't exist or no matches were found
125 pass
126 else:
127 # only assign auth when *no* errors were raised
128 if hasattr(conStr, "set"):
129 # for SQLAlchemy >= 1.4
130 conStr = conStr.set(username=auth[0], password=auth[1])
131 else:
132 # SQLAlchemy 1.3 or earlier, mutate in place
133 conStr.username = auth[0]
134 conStr.password = auth[1]
136 return conStr