_conf.py 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448
  1. # Copyright (c) "Neo4j"
  2. # Neo4j Sweden AB [https://neo4j.com]
  3. #
  4. # Licensed under the Apache License, Version 2.0 (the "License");
  5. # you may not use this file except in compliance with the License.
  6. # You may obtain a copy of the License at
  7. #
  8. # https://www.apache.org/licenses/LICENSE-2.0
  9. #
  10. # Unless required by applicable law or agreed to in writing, software
  11. # distributed under the License is distributed on an "AS IS" BASIS,
  12. # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13. # See the License for the specific language governing permissions and
  14. # limitations under the License.
  15. from __future__ import annotations
  16. from abc import ABCMeta
  17. from collections.abc import Mapping
  18. from ._meta import (
  19. deprecation_warn,
  20. experimental_warn,
  21. )
  22. from .api import (
  23. DEFAULT_DATABASE,
  24. TRUST_ALL_CERTIFICATES,
  25. TRUST_SYSTEM_CA_SIGNED_CERTIFICATES,
  26. WRITE_ACCESS,
  27. )
  28. from .exceptions import ConfigurationError
  29. def iter_items(iterable):
  30. """
  31. Iterate through key-value pairs of a dict-like object.
  32. If the object has a `keys` method, this is used along with `__getitem__`
  33. to yield each pair in turn. If no `keys` method exists, each iterable
  34. element is assumed to be a 2-tuple of key and value.
  35. """
  36. if hasattr(iterable, "keys"):
  37. for key in iterable:
  38. yield key, iterable[key]
  39. else:
  40. for key, value in iterable:
  41. yield key, value
  42. class TrustStore:
  43. # Base class for trust stores. For internal type-checking only.
  44. pass
  45. class TrustSystemCAs(TrustStore):
  46. """
  47. Used to configure the driver to trust system CAs (default).
  48. Trust server certificates that can be verified against the system
  49. certificate authority. This option is primarily intended for use with
  50. full certificates.
  51. For example::
  52. import neo4j
  53. driver = neo4j.GraphDatabase.driver(
  54. url, auth=auth, trusted_certificates=neo4j.TrustSystemCAs()
  55. )
  56. """
  57. class TrustAll(TrustStore):
  58. """
  59. Used to configure the driver to trust all certificates.
  60. Trust any server certificate. This ensures that communication
  61. is encrypted but does not verify the server certificate against a
  62. certificate authority. This option is primarily intended for use with
  63. the default auto-generated server certificate.
  64. .. warning::
  65. This still leaves you vulnerable to man-in-the-middle attacks. It will
  66. just prevent eavesdropping "from the side-line" (i.e., without
  67. intercepting the connection).
  68. For example::
  69. import neo4j
  70. driver = neo4j.GraphDatabase.driver(
  71. url, auth=auth, trusted_certificates=neo4j.TrustAll()
  72. )
  73. """
  74. class TrustCustomCAs(TrustStore):
  75. """
  76. Used to configure the driver to trust custom CAs.
  77. Trust server certificates that can be verified against the certificate
  78. authority at the specified paths. This option is primarily intended for
  79. self-signed and custom certificates.
  80. :param certificates: paths to the certificates to trust.
  81. Those are not the certificates you expect to see from the server but
  82. the CA certificates you expect to be used to sign the server's
  83. certificate.
  84. For example::
  85. import neo4j
  86. driver = neo4j.GraphDatabase.driver(
  87. url, auth=auth,
  88. trusted_certificates=neo4j.TrustCustomCAs(
  89. "/path/to/ca1.crt", "/path/to/ca2.crt",
  90. )
  91. )
  92. """
  93. def __init__(self, *certificates: str):
  94. self.certs = certificates
  95. class DeprecatedAlias:
  96. """Used when a config option has been renamed."""
  97. def __init__(self, new):
  98. self.new = new
  99. class DeprecatedAlternative:
  100. """Used for deprecated config options that have a similar alternative."""
  101. def __init__(self, new, converter=None):
  102. self.new = new
  103. self.converter = converter
  104. class DeprecatedOption:
  105. """Used for deprecated config options without alternative."""
  106. def __init__(self, value):
  107. self.value = value
  108. class ExperimentalOption:
  109. """Used for experimental config options."""
  110. def __init__(self, value):
  111. self.value = value
  112. class ConfigType(ABCMeta):
  113. def __new__(mcs, name, bases, attributes):
  114. fields = []
  115. deprecated_aliases = {}
  116. deprecated_alternatives = {}
  117. deprecated_options = {}
  118. experimental_options = {}
  119. for base in bases:
  120. if type(base) is mcs:
  121. fields += base.keys()
  122. deprecated_aliases.update(base._deprecated_aliases())
  123. deprecated_alternatives.update(base._deprecated_alternatives())
  124. deprecated_options.update(base._deprecated_options())
  125. experimental_options.update(base._experimental_options())
  126. for k, v in attributes.items():
  127. if (
  128. k.startswith("_")
  129. or callable(v)
  130. or isinstance(v, (staticmethod, classmethod))
  131. ):
  132. continue
  133. if isinstance(v, DeprecatedAlias):
  134. deprecated_aliases[k] = v.new
  135. continue
  136. if isinstance(v, DeprecatedAlternative):
  137. deprecated_alternatives[k] = v.new, v.converter
  138. continue
  139. fields.append(k)
  140. if isinstance(v, DeprecatedOption):
  141. deprecated_options[k] = v.value
  142. attributes[k] = v.value
  143. continue
  144. if isinstance(v, ExperimentalOption):
  145. experimental_options[k] = v.value
  146. attributes[k] = v.value
  147. continue
  148. def keys(_):
  149. return set(fields)
  150. def _deprecated_keys(_):
  151. aliases = set(deprecated_aliases.keys())
  152. alternatives = set(deprecated_alternatives.keys())
  153. return aliases | alternatives
  154. def _get_new(_, key):
  155. return deprecated_aliases.get(
  156. key, deprecated_alternatives.get(key, (None,))[0]
  157. )
  158. def _deprecated_aliases(_):
  159. return deprecated_aliases
  160. def _deprecated_alternatives(_):
  161. return deprecated_alternatives
  162. def _deprecated_options(_):
  163. return deprecated_options
  164. def _experimental_options(_):
  165. return experimental_options
  166. for func in (
  167. keys,
  168. _get_new,
  169. _deprecated_keys,
  170. _deprecated_aliases,
  171. _deprecated_alternatives,
  172. _deprecated_options,
  173. _experimental_options,
  174. ):
  175. attributes.setdefault(func.__name__, classmethod(func))
  176. return super().__new__(
  177. mcs,
  178. name,
  179. bases,
  180. {
  181. k: v
  182. for k, v in attributes.items()
  183. if k not in _deprecated_keys(None)
  184. },
  185. )
  186. class Config(Mapping, metaclass=ConfigType):
  187. """Base class for all configuration containers."""
  188. @staticmethod
  189. def consume_chain(data, *config_classes):
  190. values = []
  191. for config_class in config_classes:
  192. if not issubclass(config_class, Config):
  193. raise TypeError(f"{config_class!r} is not a Config subclass")
  194. values.append(config_class._consume(data))
  195. if data:
  196. raise ConfigurationError(
  197. f"Unexpected config keys: {', '.join(data.keys())}"
  198. )
  199. return values
  200. @classmethod
  201. def consume(cls, data):
  202. (config,) = cls.consume_chain(data, cls)
  203. return config
  204. @classmethod
  205. def _consume(cls, data):
  206. config = {}
  207. if data:
  208. for key in cls.keys() | cls._deprecated_keys():
  209. try:
  210. value = data.pop(key)
  211. except KeyError:
  212. pass
  213. else:
  214. config[key] = value
  215. return cls(config)
  216. def __update(self, data, warn=True):
  217. data_dict = dict(iter_items(data))
  218. def set_attr(k, v):
  219. if k in self.keys():
  220. if warn and k in self._deprecated_options():
  221. deprecation_warn(f"The '{k}' config key is deprecated.")
  222. if warn and k in self._experimental_options():
  223. experimental_warn(
  224. f"The '{k}' config key is experimental. "
  225. "It might be changed or removed any time even without "
  226. "prior notice."
  227. )
  228. setattr(self, k, v)
  229. elif k in self._deprecated_keys():
  230. k0 = self._get_new(k)
  231. if k0 in data_dict:
  232. raise ConfigurationError(
  233. f"Cannot specify both '{k0}' and '{k}' in config"
  234. )
  235. if warn:
  236. deprecation_warn(
  237. f"The '{k}' config key is deprecated, please use "
  238. f"'{k0}' instead"
  239. )
  240. if k in self._deprecated_aliases():
  241. set_attr(k0, v)
  242. else: # k in self._deprecated_alternatives:
  243. _, converter = self._deprecated_alternatives()[k]
  244. converter(self, v)
  245. else:
  246. raise AttributeError(k)
  247. rejected_keys = []
  248. for key, value in data_dict.items():
  249. if value is not None:
  250. try:
  251. set_attr(key, value)
  252. except AttributeError as exc:
  253. if not exc.args == (key,):
  254. raise
  255. rejected_keys.append(key)
  256. if rejected_keys:
  257. raise ConfigurationError(
  258. "Unexpected config keys: " + ", ".join(rejected_keys)
  259. )
  260. def __init__(self, *args, **kwargs):
  261. for arg in args:
  262. if isinstance(arg, Config):
  263. self.__update(arg, warn=False)
  264. else:
  265. self.__update(arg)
  266. self.__update(kwargs)
  267. def __repr__(self):
  268. attrs = [f" {key}={getattr(self, key)!r}" for key in self]
  269. return f"<{self.__class__.__name__}{''.join(attrs)}>"
  270. def __len__(self):
  271. return len(self.keys())
  272. def __getitem__(self, key):
  273. return getattr(self, key)
  274. def __iter__(self):
  275. return iter(self.keys())
  276. def _trust_to_trusted_certificates(pool_config, trust):
  277. if trust == TRUST_SYSTEM_CA_SIGNED_CERTIFICATES:
  278. pool_config.trusted_certificates = TrustSystemCAs()
  279. elif trust == TRUST_ALL_CERTIFICATES:
  280. pool_config.trusted_certificates = TrustAll()
  281. class WorkspaceConfig(Config):
  282. """WorkSpace configuration."""
  283. #: Connection Acquisition Timeout
  284. connection_acquisition_timeout = 60.0 # seconds
  285. # The maximum amount of time a session will wait when requesting a
  286. # connection from the connection pool.
  287. # Since the process of acquiring a connection may involve creating a new
  288. # connection, ensure that the value
  289. # of this configuration is higher than the configured Connection Timeout.
  290. #: Max Transaction Retry Time
  291. max_transaction_retry_time = 30.0 # seconds
  292. # The maximum amount of time that a managed transaction will retry before
  293. # failing.
  294. #: Initial Retry Delay
  295. initial_retry_delay = 1.0 # seconds
  296. #: Retry Delay Multiplier
  297. retry_delay_multiplier = 2.0 # seconds
  298. #: Retry Delay Jitter Factor
  299. retry_delay_jitter_factor = 0.2 # seconds
  300. #: Database Name
  301. database = DEFAULT_DATABASE
  302. # Name of the database to query.
  303. # Note: The default database can be set on the Neo4j instance settings.
  304. #: Fetch Size
  305. fetch_size = 1000
  306. #: User to impersonate
  307. impersonated_user = None
  308. # Note that you need appropriate permissions to do so.
  309. #: Bookmark Manager
  310. bookmark_manager = None
  311. # Specify the bookmark manager to be used for sessions by default.
  312. #: Turn warning received by the server into native Python warnings
  313. warn_notification_severity = None
  314. class SessionConfig(WorkspaceConfig):
  315. """Session configuration."""
  316. #: Bookmarks
  317. bookmarks = None
  318. #: Default AccessMode
  319. default_access_mode = WRITE_ACCESS
  320. #: Auth token to temporarily switch the user
  321. auth = None
  322. #: Lowest notification severity for the server to return
  323. notifications_min_severity = None
  324. #: List of notification classifications/categories for the server to ignore
  325. notifications_disabled_classifications = None
  326. class TransactionConfig(Config):
  327. """
  328. Transaction configuration. This is internal for now.
  329. neo4j.session.begin_transaction
  330. neo4j.Query
  331. neo4j.unit_of_work
  332. are both using the same settings.
  333. """
  334. #: Metadata
  335. metadata = None # dictionary
  336. #: Timeout
  337. timeout = None # seconds
  338. class RoutingConfig(Config):
  339. """Neo4jDriver routing settings. This is internal for now."""
  340. #: Routing Table Purge_Delay
  341. routing_table_purge_delay = 30.0 # seconds
  342. # The TTL + routing_table_purge_delay should be used to check if the
  343. #: database routing table should be removed.
  344. #: Max Routing Failures
  345. # max_routing_failures = 1
  346. #: Retry Timeout Delay
  347. # retry_timeout_delay = 5.0 # seconds