driver.py 52 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430
  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. import asyncio
  17. import typing as t
  18. if t.TYPE_CHECKING:
  19. import ssl
  20. import typing_extensions as te
  21. from .._api import (
  22. T_NotificationDisabledCategory,
  23. T_NotificationMinimumSeverity,
  24. )
  25. from .._api import (
  26. NotificationMinimumSeverity,
  27. RoutingControl,
  28. TelemetryAPI,
  29. )
  30. from .._async_compat.util import Util
  31. from .._conf import (
  32. Config,
  33. ConfigurationError,
  34. SessionConfig,
  35. TrustAll,
  36. TrustStore,
  37. WorkspaceConfig,
  38. )
  39. from .._debug import ENABLED as DEBUG_ENABLED
  40. from .._meta import (
  41. deprecation_warn,
  42. experimental_warn,
  43. preview_warn,
  44. unclosed_resource_warn,
  45. )
  46. from .._work import (
  47. EagerResult,
  48. Query,
  49. unit_of_work,
  50. )
  51. from ..addressing import Address
  52. from ..api import (
  53. Auth,
  54. BookmarkManager,
  55. Bookmarks,
  56. DRIVER_BOLT,
  57. DRIVER_NEO4J,
  58. parse_neo4j_uri,
  59. parse_routing_context,
  60. READ_ACCESS,
  61. SECURITY_TYPE_SECURE,
  62. SECURITY_TYPE_SELF_SIGNED_CERTIFICATE,
  63. ServerInfo,
  64. TRUST_ALL_CERTIFICATES,
  65. TRUST_SYSTEM_CA_SIGNED_CERTIFICATES,
  66. URI_SCHEME_BOLT,
  67. URI_SCHEME_BOLT_SECURE,
  68. URI_SCHEME_BOLT_SELF_SIGNED_CERTIFICATE,
  69. URI_SCHEME_NEO4J,
  70. URI_SCHEME_NEO4J_SECURE,
  71. URI_SCHEME_NEO4J_SELF_SIGNED_CERTIFICATE,
  72. WRITE_ACCESS,
  73. )
  74. from ..auth_management import (
  75. AuthManager,
  76. AuthManagers,
  77. ClientCertificate,
  78. ClientCertificateProvider,
  79. )
  80. from ..exceptions import Neo4jError
  81. from .auth_management import _StaticClientCertificateProvider
  82. from .bookmark_manager import (
  83. Neo4jBookmarkManager,
  84. TBmConsumer as _TBmConsumer,
  85. TBmSupplier as _TBmSupplier,
  86. )
  87. from .config import PoolConfig
  88. from .work import (
  89. ManagedTransaction,
  90. Result,
  91. Session,
  92. )
  93. if t.TYPE_CHECKING:
  94. import ssl
  95. from enum import Enum
  96. import typing_extensions as te
  97. from .._api import T_RoutingControl
  98. from ..api import _TAuth
  99. class _DefaultEnum(Enum):
  100. default = "default"
  101. _default = _DefaultEnum.default
  102. else:
  103. _default = object()
  104. _T = t.TypeVar("_T")
  105. class GraphDatabase:
  106. """Accessor for :class:`neo4j.Driver` construction."""
  107. if t.TYPE_CHECKING:
  108. @classmethod
  109. def driver(
  110. cls,
  111. uri: str,
  112. *,
  113. auth: _TAuth | AuthManager = ...,
  114. max_connection_lifetime: float = ...,
  115. liveness_check_timeout: float | None = ...,
  116. max_connection_pool_size: int = ...,
  117. connection_timeout: float = ...,
  118. trust: (
  119. te.Literal["TRUST_ALL_CERTIFICATES"]
  120. | te.Literal["TRUST_SYSTEM_CA_SIGNED_CERTIFICATES"]
  121. ) = ...,
  122. resolver: (
  123. t.Callable[[Address], t.Iterable[Address]]
  124. | t.Callable[[Address], t.Union[t.Iterable[Address]]]
  125. ) = ...,
  126. encrypted: bool = ...,
  127. trusted_certificates: TrustStore = ...,
  128. client_certificate: (
  129. ClientCertificate | ClientCertificateProvider | None
  130. ) = ...,
  131. ssl_context: ssl.SSLContext | None = ...,
  132. user_agent: str = ...,
  133. keep_alive: bool = ...,
  134. notifications_min_severity: (
  135. T_NotificationMinimumSeverity | None
  136. ) = ...,
  137. notifications_disabled_categories: (
  138. t.Iterable[T_NotificationDisabledCategory] | None
  139. ) = ...,
  140. notifications_disabled_classifications: (
  141. t.Iterable[T_NotificationDisabledCategory] | None
  142. ) = ...,
  143. warn_notification_severity: (
  144. T_NotificationMinimumSeverity | None
  145. ) = ...,
  146. telemetry_disabled: bool = ...,
  147. # undocumented/unsupported options
  148. # they may be changed or removed any time without prior notice
  149. connection_acquisition_timeout: float = ...,
  150. max_transaction_retry_time: float = ...,
  151. initial_retry_delay: float = ...,
  152. retry_delay_multiplier: float = ...,
  153. retry_delay_jitter_factor: float = ...,
  154. database: str | None = ...,
  155. fetch_size: int = ...,
  156. impersonated_user: str | None = ...,
  157. bookmark_manager: (
  158. BookmarkManager | BookmarkManager | None
  159. ) = ...,
  160. ) -> Driver: ...
  161. else:
  162. @classmethod
  163. def driver(
  164. cls,
  165. uri: str,
  166. *,
  167. auth: _TAuth | AuthManager = None,
  168. **config,
  169. ) -> Driver:
  170. """
  171. Create a driver.
  172. :param uri: the connection URI for the driver,
  173. see :ref:`uri-ref` for available URIs.
  174. :param auth: the authentication details,
  175. see :ref:`auth-ref` for available authentication details.
  176. :param config: driver configuration key-word arguments,
  177. see :ref:`driver-configuration-ref` for available
  178. key-word arguments.
  179. """
  180. driver_type, security_type, parsed = parse_neo4j_uri(uri)
  181. if not isinstance(auth, AuthManager):
  182. auth = AuthManagers.static(auth)
  183. config["auth"] = auth
  184. client_certificate = config.get("client_certificate")
  185. if isinstance(client_certificate, ClientCertificate):
  186. # using internal class until public factory is GA:
  187. # AsyncClientCertificateProviders.static
  188. config["client_certificate"] = (
  189. _StaticClientCertificateProvider(client_certificate)
  190. )
  191. # TODO: 6.0 - remove "trust" config option
  192. if "trust" in config and config["trust"] not in {
  193. TRUST_ALL_CERTIFICATES,
  194. TRUST_SYSTEM_CA_SIGNED_CERTIFICATES,
  195. }:
  196. raise ConfigurationError(
  197. "The config setting `trust` values are {!r}".format(
  198. [
  199. TRUST_ALL_CERTIFICATES,
  200. TRUST_SYSTEM_CA_SIGNED_CERTIFICATES,
  201. ]
  202. )
  203. )
  204. if "trusted_certificates" in config and not isinstance(
  205. config["trusted_certificates"], TrustStore
  206. ):
  207. raise ConfigurationError(
  208. 'The config setting "trusted_certificates" must be of '
  209. "type neo4j.TrustAll, neo4j.TrustCustomCAs, or"
  210. "neo4j.TrustSystemCAs but was {}".format(
  211. type(config["trusted_certificates"])
  212. )
  213. )
  214. if security_type in {
  215. SECURITY_TYPE_SELF_SIGNED_CERTIFICATE,
  216. SECURITY_TYPE_SECURE,
  217. } and (
  218. "encrypted" in config
  219. or "trust" in config
  220. or "trusted_certificates" in config
  221. or "ssl_context" in config
  222. ):
  223. # TODO: 6.0 - remove "trust" from error message
  224. raise ConfigurationError(
  225. 'The config settings "encrypted", "trust", '
  226. '"trusted_certificates", and "ssl_context" can only be '
  227. "used with the URI schemes {!r}. Use the other URI "
  228. "schemes {!r} for setting encryption settings.".format(
  229. [
  230. URI_SCHEME_BOLT,
  231. URI_SCHEME_NEO4J,
  232. ],
  233. [
  234. URI_SCHEME_BOLT_SELF_SIGNED_CERTIFICATE,
  235. URI_SCHEME_BOLT_SECURE,
  236. URI_SCHEME_NEO4J_SELF_SIGNED_CERTIFICATE,
  237. URI_SCHEME_NEO4J_SECURE,
  238. ],
  239. )
  240. )
  241. if security_type == SECURITY_TYPE_SECURE:
  242. config["encrypted"] = True
  243. elif security_type == SECURITY_TYPE_SELF_SIGNED_CERTIFICATE:
  244. config["encrypted"] = True
  245. config["trusted_certificates"] = TrustAll()
  246. if "notifications_disabled_classifications" in config:
  247. preview_warn(
  248. "notifications_disabled_classifications "
  249. "is a preview feature.",
  250. stack_level=2,
  251. )
  252. if "warn_notification_severity" in config:
  253. preview_warn(
  254. "notification warnings are a preview feature.",
  255. stack_level=2,
  256. )
  257. _normalize_notifications_config(config, driver_level=True)
  258. liveness_check_timeout = config.get("liveness_check_timeout")
  259. if (
  260. liveness_check_timeout is not None
  261. and liveness_check_timeout < 0
  262. ):
  263. raise ConfigurationError(
  264. 'The config setting "liveness_check_timeout" must be '
  265. "greater than or equal to 0 but was "
  266. f"{liveness_check_timeout}."
  267. )
  268. assert driver_type in {DRIVER_BOLT, DRIVER_NEO4J}
  269. if driver_type == DRIVER_BOLT:
  270. if parse_routing_context(parsed.query):
  271. deprecation_warn(
  272. 'Creating a direct driver ("bolt://" scheme) with '
  273. "routing context (URI parameters) is deprecated. They "
  274. "will be ignored. This will raise an error in a "
  275. f'future release. Given URI "{uri}"',
  276. stack_level=2,
  277. )
  278. # TODO: 6.0 - raise instead of warning
  279. # raise ValueError(
  280. # 'Routing parameters are not supported with scheme '
  281. # '"bolt". Given URI "{}".'.format(uri)
  282. # )
  283. return cls.bolt_driver(parsed.netloc, **config)
  284. # else driver_type == DRIVER_NEO4J
  285. routing_context = parse_routing_context(parsed.query)
  286. return cls.neo4j_driver(
  287. parsed.netloc, routing_context=routing_context, **config
  288. )
  289. @classmethod
  290. def bookmark_manager(
  291. cls,
  292. initial_bookmarks: Bookmarks | t.Iterable[str] | None = None,
  293. bookmarks_supplier: _TBmSupplier | None = None,
  294. bookmarks_consumer: _TBmConsumer | None = None,
  295. ) -> BookmarkManager:
  296. """
  297. Create a :class:`.BookmarkManager` with default implementation.
  298. Basic usage example to configure sessions with the built-in bookmark
  299. manager implementation so that all work is automatically causally
  300. chained (i.e., all reads can observe all previous writes even in a
  301. clustered setup)::
  302. import neo4j
  303. # omitting closing the driver for brevity
  304. driver = neo4j.GraphDatabase.driver(...)
  305. bookmark_manager = neo4j.GraphDatabase.bookmark_manager(...)
  306. with driver.session(
  307. bookmark_manager=bookmark_manager
  308. ) as session1:
  309. with driver.session(
  310. bookmark_manager=bookmark_manager,
  311. access_mode=neo4j.READ_ACCESS
  312. ) as session2:
  313. result1 = session1.run("<WRITE_QUERY>")
  314. result1.consume()
  315. # READ_QUERY is guaranteed to see what WRITE_QUERY wrote.
  316. result2 = session2.run("<READ_QUERY>")
  317. result2.consume()
  318. This is a very contrived example, and in this particular case, having
  319. both queries in the same session has the exact same effect and might
  320. even be more performant. However, when dealing with sessions spanning
  321. multiple threads, Tasks, processes, or even hosts, the bookmark
  322. manager can come in handy as sessions are not safe to be used
  323. concurrently.
  324. :param initial_bookmarks:
  325. The initial set of bookmarks. The returned bookmark manager will
  326. use this to initialize its internal bookmarks.
  327. :param bookmarks_supplier:
  328. Function which will be called every time the default bookmark
  329. manager's method :meth:`.BookmarkManager.get_bookmarks`
  330. gets called.
  331. The function takes no arguments and must return a
  332. :class:`.Bookmarks` object. The result of ``bookmarks_supplier``
  333. will then be concatenated with the internal set of bookmarks and
  334. used to configure the session in creation. It will, however, not
  335. update the internal set of bookmarks.
  336. :param bookmarks_consumer:
  337. Function which will be called whenever the set of bookmarks
  338. handled by the bookmark manager gets updated with the new
  339. internal bookmark set. It will receive the new set of bookmarks
  340. as a :class:`.Bookmarks` object and return :data:`None`.
  341. :returns: A default implementation of :class:`.BookmarkManager`.
  342. .. versionadded:: 5.0
  343. .. versionchanged:: 5.3
  344. The bookmark manager no longer tracks bookmarks per database.
  345. This effectively changes the signature of almost all bookmark
  346. manager related methods:
  347. * ``initial_bookmarks`` is no longer a mapping from database name
  348. to bookmarks but plain bookmarks.
  349. * ``bookmarks_supplier`` no longer receives the database name as
  350. an argument.
  351. * ``bookmarks_consumer`` no longer receives the database name as
  352. an argument.
  353. .. versionchanged:: 5.8 Stabilized from experimental.
  354. """
  355. return Neo4jBookmarkManager(
  356. initial_bookmarks=initial_bookmarks,
  357. bookmarks_supplier=bookmarks_supplier,
  358. bookmarks_consumer=bookmarks_consumer,
  359. )
  360. @classmethod
  361. def bolt_driver(cls, target, **config):
  362. """
  363. Create a direct driver.
  364. Create a driver for direct Bolt server access that uses
  365. socket I/O and thread-based concurrency.
  366. """
  367. from .._exceptions import (
  368. BoltHandshakeError,
  369. BoltSecurityError,
  370. )
  371. try:
  372. return BoltDriver.open(target, **config)
  373. except (BoltHandshakeError, BoltSecurityError) as error:
  374. from ..exceptions import ServiceUnavailable
  375. raise ServiceUnavailable(str(error)) from error
  376. @classmethod
  377. def neo4j_driver(cls, *targets, routing_context=None, **config):
  378. """
  379. Create a routing driver.
  380. Create a driver for routing-capable Neo4j service access
  381. that uses socket I/O and thread-based concurrency.
  382. """
  383. # TODO: 6.0 - adjust signature to only take one target
  384. if len(targets) > 1:
  385. deprecation_warn(
  386. "Creating a routing driver with multiple targets is "
  387. "deprecated. The driver only uses the first target anyway. "
  388. "The method signature will change in a future release.",
  389. )
  390. from .._exceptions import (
  391. BoltHandshakeError,
  392. BoltSecurityError,
  393. )
  394. try:
  395. return Neo4jDriver.open(
  396. *targets, routing_context=routing_context, **config
  397. )
  398. except (BoltHandshakeError, BoltSecurityError) as error:
  399. from ..exceptions import ServiceUnavailable
  400. raise ServiceUnavailable(str(error)) from error
  401. class _Direct:
  402. # TODO: 6.0 - those attributes should be private
  403. default_host = "localhost"
  404. default_port = 7687
  405. default_target = ":"
  406. def __init__(self, address):
  407. self._address = address
  408. @property
  409. def address(self):
  410. return self._address
  411. @classmethod
  412. def parse_target(cls, target):
  413. """Parse a target string to produce an address."""
  414. if not target:
  415. target = cls.default_target
  416. return Address.parse(
  417. target,
  418. default_host=cls.default_host,
  419. default_port=cls.default_port,
  420. )
  421. class _Routing:
  422. # TODO: 6.0 - those attributes should be private
  423. default_host = "localhost"
  424. default_port = 7687
  425. default_targets = ": :17601 :17687"
  426. def __init__(self, initial_addresses):
  427. self._initial_addresses = initial_addresses
  428. @property
  429. def initial_addresses(self):
  430. return self._initial_addresses
  431. @classmethod
  432. def parse_targets(cls, *targets):
  433. """Parse a sequence of target strings to produce an address list."""
  434. targets = " ".join(targets)
  435. if not targets:
  436. targets = cls.default_targets
  437. return Address.parse_list(
  438. targets,
  439. default_host=cls.default_host,
  440. default_port=cls.default_port,
  441. )
  442. class Driver:
  443. """
  444. Base class for all driver types.
  445. Drivers are used as the primary access point to Neo4j.
  446. """
  447. #: Connection pool
  448. _pool: t.Any = None
  449. #: Flag if the driver has been closed
  450. _closed = False
  451. def __init__(self, pool, default_workspace_config):
  452. assert pool is not None
  453. assert default_workspace_config is not None
  454. self._pool = pool
  455. self._default_workspace_config = default_workspace_config
  456. self._query_bookmark_manager = GraphDatabase.bookmark_manager()
  457. def __enter__(self) -> Driver:
  458. return self
  459. def __exit__(self, exc_type, exc_value, traceback):
  460. self.close()
  461. # Copy globals as function locals to make sure that they are available
  462. # during Python shutdown when the Pool is destroyed.
  463. def __del__(
  464. self,
  465. _unclosed_resource_warn=unclosed_resource_warn,
  466. _is_async_code=Util.is_async_code,
  467. _deprecation_warn=deprecation_warn,
  468. ):
  469. if not self._closed:
  470. _unclosed_resource_warn(self)
  471. # TODO: 6.0 - remove this
  472. if _is_async_code:
  473. return
  474. if not self._closed:
  475. _deprecation_warn(
  476. "Relying on Driver's destructor to close the session "
  477. "is deprecated. Please make sure to close the session. "
  478. "Use it as a context (`with` statement) or make sure to "
  479. "call `.close()` explicitly. Future versions of the "
  480. "driver will not close drivers automatically."
  481. )
  482. self.close()
  483. def _check_state(self):
  484. if self._closed:
  485. # TODO: 6.0 - raise the error
  486. # raise DriverError("Driver closed")
  487. deprecation_warn(
  488. "Using a driver after it has been closed is deprecated. "
  489. "Future versions of the driver will raise an error.",
  490. stack_level=3,
  491. )
  492. @property
  493. def encrypted(self) -> bool:
  494. """Indicate whether the driver was configured to use encryption."""
  495. return bool(self._pool.pool_config.encrypted)
  496. if t.TYPE_CHECKING:
  497. def session(
  498. self,
  499. *,
  500. connection_acquisition_timeout: float = ...,
  501. max_transaction_retry_time: float = ...,
  502. database: str | None = ...,
  503. fetch_size: int = ...,
  504. impersonated_user: str | None = ...,
  505. bookmarks: t.Iterable[str] | Bookmarks | None = ...,
  506. default_access_mode: str = ...,
  507. bookmark_manager: (
  508. BookmarkManager | BookmarkManager | None
  509. ) = ...,
  510. auth: _TAuth = ...,
  511. notifications_min_severity: (
  512. T_NotificationMinimumSeverity | None
  513. ) = ...,
  514. notifications_disabled_categories: (
  515. t.Iterable[T_NotificationDisabledCategory] | None
  516. ) = ...,
  517. notifications_disabled_classifications: (
  518. t.Iterable[T_NotificationDisabledCategory] | None
  519. ) = ...,
  520. # undocumented/unsupported options
  521. # they may be change or removed any time without prior notice
  522. initial_retry_delay: float = ...,
  523. retry_delay_multiplier: float = ...,
  524. retry_delay_jitter_factor: float = ...,
  525. ) -> Session: ...
  526. else:
  527. def session(self, **config) -> Session:
  528. """
  529. Create a session.
  530. See :ref:`session-construction-ref` for details.
  531. :param config: session configuration key-word arguments,
  532. see :ref:`session-configuration-ref` for available
  533. key-word arguments.
  534. :returns: new :class:`neo4j.Session` object
  535. """
  536. if "warn_notification_severity" in config:
  537. # Would work just fine, but we don't want to introduce yet
  538. # another undocumented/unsupported config option.
  539. del config["warn_notification_severity"]
  540. self._check_state()
  541. if "notifications_disabled_classifications" in config:
  542. preview_warn(
  543. "notifications_disabled_classifications "
  544. "is a preview feature.",
  545. stack_level=2,
  546. )
  547. session_config = self._read_session_config(config)
  548. return self._session(session_config)
  549. def _session(self, session_config) -> Session:
  550. return Session(self._pool, session_config)
  551. def _read_session_config(self, config_kwargs):
  552. config = self._prepare_session_config(config_kwargs)
  553. return SessionConfig(self._default_workspace_config, config)
  554. @classmethod
  555. def _prepare_session_config(cls, config_kwargs):
  556. _normalize_notifications_config(config_kwargs)
  557. return config_kwargs
  558. def close(self) -> None:
  559. """Shut down, closing any open connections in the pool."""
  560. # TODO: 6.0 - NOOP if already closed
  561. # if self._closed:
  562. # return
  563. try:
  564. self._pool.close()
  565. except asyncio.CancelledError:
  566. self._closed = True
  567. raise
  568. self._closed = True
  569. # overloads to work around https://github.com/python/mypy/issues/3737
  570. @t.overload
  571. def execute_query(
  572. self,
  573. query_: te.LiteralString | Query,
  574. parameters_: dict[str, t.Any] | None = None,
  575. routing_: T_RoutingControl = RoutingControl.WRITE,
  576. database_: str | None = None,
  577. impersonated_user_: str | None = None,
  578. bookmark_manager_: (
  579. BookmarkManager | BookmarkManager | None
  580. ) = ...,
  581. auth_: _TAuth = None,
  582. result_transformer_: t.Callable[
  583. [Result], t.Union[EagerResult]
  584. ] = ...,
  585. **kwargs: t.Any,
  586. ) -> EagerResult: ...
  587. @t.overload
  588. def execute_query(
  589. self,
  590. query_: te.LiteralString | Query,
  591. parameters_: dict[str, t.Any] | None = None,
  592. routing_: T_RoutingControl = RoutingControl.WRITE,
  593. database_: str | None = None,
  594. impersonated_user_: str | None = None,
  595. bookmark_manager_: (
  596. BookmarkManager | BookmarkManager | None
  597. ) = ...,
  598. auth_: _TAuth = None,
  599. result_transformer_: t.Callable[[Result], t.Union[_T]] = ...,
  600. **kwargs: t.Any,
  601. ) -> _T: ...
  602. def execute_query(
  603. self,
  604. query_: te.LiteralString | Query,
  605. parameters_: dict[str, t.Any] | None = None,
  606. routing_: T_RoutingControl = RoutingControl.WRITE,
  607. database_: str | None = None,
  608. impersonated_user_: str | None = None,
  609. bookmark_manager_: (
  610. BookmarkManager
  611. | BookmarkManager
  612. | None
  613. | te.Literal[_DefaultEnum.default]
  614. ) = _default,
  615. auth_: _TAuth = None,
  616. result_transformer_: t.Callable[
  617. [Result], t.Union[t.Any]
  618. ] = Result.to_eager_result,
  619. **kwargs: t.Any,
  620. ) -> t.Any:
  621. '''
  622. Execute a query in a transaction function and return all results.
  623. This method is a handy wrapper for lower-level driver APIs like
  624. sessions, transactions, and transaction functions. It is intended
  625. for simple use cases where there is no need for managing all possible
  626. options.
  627. The internal usage of transaction functions provides a retry-mechanism
  628. for appropriate errors. Furthermore, this means that queries using
  629. ``CALL {} IN TRANSACTIONS`` or the older ``USING PERIODIC COMMIT``
  630. will not work (use :meth:`Session.run` for these).
  631. The method is roughly equivalent to::
  632. def execute_query(
  633. query_, parameters_, routing_, database_, impersonated_user_,
  634. bookmark_manager_, auth_, result_transformer_, **kwargs
  635. ):
  636. @unit_of_work(query_.metadata, query_.timeout)
  637. def work(tx):
  638. result = tx.run(query_.text, parameters_, **kwargs)
  639. return result_transformer_(result)
  640. with driver.session(
  641. database=database_,
  642. impersonated_user=impersonated_user_,
  643. bookmark_manager=bookmark_manager_,
  644. auth=auth_,
  645. ) as session:
  646. if routing_ == RoutingControl.WRITE:
  647. return session.execute_write(work)
  648. elif routing_ == RoutingControl.READ:
  649. return session.execute_read(work)
  650. Usage example::
  651. from typing import List
  652. import neo4j
  653. def example(driver: neo4j.Driver) -> List[str]:
  654. """Get the name of all 42 year-olds."""
  655. records, summary, keys = driver.execute_query(
  656. "MATCH (p:Person {age: $age}) RETURN p.name",
  657. {"age": 42},
  658. routing_=neo4j.RoutingControl.READ, # or just "r"
  659. database_="neo4j",
  660. )
  661. assert keys == ["p.name"] # not needed, just for illustration
  662. # log_summary(summary) # log some metadata
  663. return [str(record["p.name"]) for record in records]
  664. # or: return [str(record[0]) for record in records]
  665. # or even: return list(map(lambda r: str(r[0]), records))
  666. Another example::
  667. import neo4j
  668. def example(driver: neo4j.Driver) -> int:
  669. """Call all young people "My dear" and get their count."""
  670. record = driver.execute_query(
  671. "MATCH (p:Person) WHERE p.age <= $age "
  672. "SET p.nickname = 'My dear' "
  673. "RETURN count(*)",
  674. # optional routing parameter, as write is default
  675. # routing_=neo4j.RoutingControl.WRITE, # or just "w",
  676. database_="neo4j",
  677. result_transformer_=neo4j.Result.single,
  678. age=15,
  679. )
  680. assert record is not None # for typechecking and illustration
  681. count = record[0]
  682. assert isinstance(count, int)
  683. return count
  684. :param query_:
  685. Cypher query to execute.
  686. Use a :class:`.Query` object to pass a query with additional
  687. transaction configuration.
  688. :type query_: typing.LiteralString | Query
  689. :param parameters_: parameters to use in the query
  690. :type parameters_: typing.Optional[typing.Dict[str, typing.Any]]
  691. :param routing_:
  692. Whether to route the query to a reader (follower/read replica) or
  693. a writer (leader) in the cluster. Default is to route to a writer.
  694. :type routing_: RoutingControl
  695. :param database_:
  696. Database to execute the query against.
  697. :data:`None` (default) uses the database configured on the server
  698. side.
  699. .. Note::
  700. It is recommended to always specify the database explicitly
  701. when possible. This allows the driver to work more efficiently,
  702. as it will not have to resolve the default database first.
  703. See also the Session config :ref:`database-ref`.
  704. :type database_: typing.Optional[str]
  705. :param impersonated_user_:
  706. Name of the user to impersonate.
  707. This means that all query will be executed in the security context
  708. of the impersonated user. For this, the user for which the
  709. :class:`Driver` has been created needs to have the appropriate
  710. permissions.
  711. See also the Session config :ref:`impersonated-user-ref`.
  712. :type impersonated_user_: typing.Optional[str]
  713. :param auth_:
  714. Authentication information to use for this query.
  715. By default, the driver configuration is used.
  716. See also the Session config :ref:`session-auth-ref`.
  717. :type auth_: typing.Tuple[typing.Any, typing.Any] | Auth | None
  718. :param result_transformer_:
  719. A function that gets passed the :class:`neo4j.Result` object
  720. resulting from the query and converts it to a different type. The
  721. result of the transformer function is returned by this method.
  722. .. warning::
  723. The transformer function must **not** return the
  724. :class:`neo4j.Result` itself.
  725. .. warning::
  726. N.B. the driver might retry the underlying transaction so the
  727. transformer might get invoked more than once (with different
  728. :class:`neo4j.Result` objects).
  729. Therefore, it needs to be idempotent (i.e., have the same
  730. effect, regardless if called once or many times).
  731. Example transformer that checks that exactly one record is in the
  732. result stream, then returns the record and the result summary::
  733. from typing import Tuple
  734. import neo4j
  735. def transformer(
  736. result: neo4j.Result
  737. ) -> Tuple[neo4j.Record, neo4j.ResultSummary]:
  738. record = result.single(strict=True)
  739. summary = result.consume()
  740. return record, summary
  741. Note that methods of :class:`neo4j.Result` that don't take
  742. mandatory arguments can be used directly as transformer functions.
  743. For example::
  744. import neo4j
  745. def example(driver: neo4j.Driver) -> neo4j.Record::
  746. record = driver.execute_query(
  747. "SOME QUERY",
  748. result_transformer_=neo4j.Result.single
  749. )
  750. # is equivalent to:
  751. def transformer(result: neo4j.Result) -> neo4j.Record:
  752. return result.single()
  753. def example(driver: neo4j.Driver) -> neo4j.Record::
  754. record = driver.execute_query(
  755. "SOME QUERY",
  756. result_transformer_=transformer
  757. )
  758. :type result_transformer_:
  759. typing.Callable[[Result], typing.Union[T]]
  760. :param bookmark_manager_:
  761. Specify a bookmark manager to use.
  762. If present, the bookmark manager is used to keep the query causally
  763. consistent with all work executed using the same bookmark manager.
  764. Defaults to the driver's :attr:`.execute_query_bookmark_manager`.
  765. Pass :data:`None` to disable causal consistency.
  766. :type bookmark_manager_: BookmarkManager | BookmarkManager | None
  767. :param kwargs: additional keyword parameters. None of these can end
  768. with a single underscore. This is to avoid collisions with the
  769. keyword configuration parameters of this method. If you need to
  770. pass such a parameter, use the ``parameters_`` parameter instead.
  771. Parameters passed as kwargs take precedence over those passed in
  772. ``parameters_``.
  773. :type kwargs: typing.Any
  774. :returns: the result of the ``result_transformer_``
  775. :rtype: T
  776. .. versionadded:: 5.5
  777. .. versionchanged:: 5.8
  778. * Added ``auth_`` parameter in preview.
  779. * Stabilized from experimental.
  780. .. versionchanged:: 5.14
  781. Stabilized ``auth_`` parameter from preview.
  782. .. versionchanged:: 5.15
  783. The ``query_`` parameter now also accepts a :class:`.Query` object
  784. instead of only :class:`str`.
  785. ''' # noqa: E501 example code isn't too long
  786. self._check_state()
  787. invalid_kwargs = [
  788. k for k in kwargs if k[-2:-1] != "_" and k[-1:] == "_"
  789. ]
  790. if invalid_kwargs:
  791. raise ValueError(
  792. "keyword parameters must not end with a single '_'. "
  793. f"Found: {invalid_kwargs!r}\n"
  794. "\nYou either misspelled an existing configuration parameter "
  795. "or tried to send a query parameter that is reserved. In the "
  796. "latter case, use the `parameters_` dictionary instead."
  797. )
  798. if isinstance(query_, Query):
  799. timeout = query_.timeout
  800. metadata = query_.metadata
  801. query_str = query_.text
  802. work = unit_of_work(metadata, timeout)(_work)
  803. else:
  804. query_str = query_
  805. work = _work
  806. parameters = dict(parameters_ or {}, **kwargs)
  807. if bookmark_manager_ is _default:
  808. bookmark_manager_ = self._query_bookmark_manager
  809. assert bookmark_manager_ is not _default
  810. session_config = self._read_session_config(
  811. {
  812. "database": database_,
  813. "impersonated_user": impersonated_user_,
  814. "bookmark_manager": bookmark_manager_,
  815. "auth": auth_,
  816. }
  817. )
  818. session = self._session(session_config)
  819. with session:
  820. if routing_ == RoutingControl.WRITE:
  821. access_mode = WRITE_ACCESS
  822. elif routing_ == RoutingControl.READ:
  823. access_mode = READ_ACCESS
  824. else:
  825. raise ValueError(
  826. f"Invalid routing control value: {routing_!r}"
  827. )
  828. with session._pipelined_begin:
  829. return session._run_transaction(
  830. access_mode,
  831. TelemetryAPI.DRIVER,
  832. work,
  833. (query_str, parameters, result_transformer_),
  834. {},
  835. )
  836. @property
  837. def execute_query_bookmark_manager(self) -> BookmarkManager:
  838. """
  839. The driver's default query bookmark manager.
  840. This is the default :class:`.BookmarkManager` used by
  841. :meth:`.execute_query`. This can be used to causally chain
  842. :meth:`.execute_query` calls and sessions. Example::
  843. def example(driver: neo4j.Driver) -> None:
  844. driver.execute_query("<QUERY 1>")
  845. with driver.session(
  846. bookmark_manager=driver.execute_query_bookmark_manager
  847. ) as session:
  848. # every query inside this session will be causally chained
  849. # (i.e., can read what was written by <QUERY 1>)
  850. session.run("<QUERY 2>")
  851. # subsequent execute_query calls will be causally chained
  852. # (i.e., can read what was written by <QUERY 2>)
  853. driver.execute_query("<QUERY 3>")
  854. .. versionadded:: 5.5
  855. .. versionchanged:: 5.8
  856. * Renamed from ``query_bookmark_manager`` to
  857. ``execute_query_bookmark_manager``.
  858. * Stabilized from experimental.
  859. """
  860. return self._query_bookmark_manager
  861. if t.TYPE_CHECKING:
  862. def verify_connectivity(
  863. self,
  864. *,
  865. # all arguments are experimental
  866. # they may be change or removed any time without prior notice
  867. session_connection_timeout: float = ...,
  868. connection_acquisition_timeout: float = ...,
  869. max_transaction_retry_time: float = ...,
  870. database: str | None = ...,
  871. fetch_size: int = ...,
  872. impersonated_user: str | None = ...,
  873. bookmarks: t.Iterable[str] | Bookmarks | None = ...,
  874. default_access_mode: str = ...,
  875. bookmark_manager: (
  876. BookmarkManager | BookmarkManager | None
  877. ) = ...,
  878. auth: Auth | tuple[str, str] = ...,
  879. notifications_min_severity: (
  880. T_NotificationMinimumSeverity | None
  881. ) = ...,
  882. notifications_disabled_categories: (
  883. t.Iterable[T_NotificationDisabledCategory] | None
  884. ) = ...,
  885. notifications_disabled_classifications: (
  886. t.Iterable[T_NotificationDisabledCategory] | None
  887. ) = ...,
  888. # undocumented/unsupported options
  889. initial_retry_delay: float = ...,
  890. retry_delay_multiplier: float = ...,
  891. retry_delay_jitter_factor: float = ...,
  892. ) -> None: ...
  893. else:
  894. def verify_connectivity(self, **config) -> None:
  895. """
  896. Verify that the driver can establish a connection to the server.
  897. This verifies if the driver can establish a reading connection to a
  898. remote server or a cluster. Some data will be exchanged.
  899. .. note::
  900. Even if this method raises an exception, the driver still needs
  901. to be closed via :meth:`close` to free up all resources.
  902. :param config: accepts the same configuration key-word arguments as
  903. :meth:`session`.
  904. .. warning::
  905. All configuration key-word arguments are experimental.
  906. They might be changed or removed in any future version
  907. without prior notice.
  908. :raises Exception: if the driver cannot connect to the remote.
  909. Use the exception to further understand the cause of the
  910. connectivity problem.
  911. .. versionchanged:: 5.0
  912. The undocumented return value has been removed.
  913. If you need information about the remote server, use
  914. :meth:`get_server_info` instead.
  915. """
  916. self._check_state()
  917. if config:
  918. experimental_warn(
  919. "All configuration key-word arguments to "
  920. "verify_connectivity() are experimental. They might be "
  921. "changed or removed in any future version without prior "
  922. "notice."
  923. )
  924. session_config = self._read_session_config(config)
  925. self._get_server_info(session_config)
  926. if t.TYPE_CHECKING:
  927. def get_server_info(
  928. self,
  929. *,
  930. # all arguments are experimental
  931. # they may be change or removed any time without prior notice
  932. session_connection_timeout: float = ...,
  933. connection_acquisition_timeout: float = ...,
  934. max_transaction_retry_time: float = ...,
  935. database: str | None = ...,
  936. fetch_size: int = ...,
  937. impersonated_user: str | None = ...,
  938. bookmarks: t.Iterable[str] | Bookmarks | None = ...,
  939. default_access_mode: str = ...,
  940. bookmark_manager: (
  941. BookmarkManager | BookmarkManager | None
  942. ) = ...,
  943. auth: Auth | tuple[str, str] = ...,
  944. notifications_min_severity: (
  945. T_NotificationMinimumSeverity | None
  946. ) = ...,
  947. notifications_disabled_categories: (
  948. t.Iterable[T_NotificationDisabledCategory] | None
  949. ) = ...,
  950. notifications_disabled_classifications: (
  951. t.Iterable[T_NotificationDisabledCategory] | None
  952. ) = ...,
  953. # undocumented/unsupported options
  954. initial_retry_delay: float = ...,
  955. retry_delay_multiplier: float = ...,
  956. retry_delay_jitter_factor: float = ...,
  957. ) -> ServerInfo: ...
  958. else:
  959. def get_server_info(self, **config) -> ServerInfo:
  960. """
  961. Get information about the connected Neo4j server.
  962. Try to establish a working read connection to the remote server or
  963. a member of a cluster and exchange some data. Then return the
  964. contacted server's information.
  965. In a cluster, there is no guarantee about which server will be
  966. contacted.
  967. .. note::
  968. Even if this method raises an exception, the driver still needs
  969. to be closed via :meth:`close` to free up all resources.
  970. :param config: accepts the same configuration key-word arguments as
  971. :meth:`session`.
  972. .. warning::
  973. All configuration key-word arguments are experimental.
  974. They might be changed or removed in any future version
  975. without prior notice.
  976. :raises Exception: if the driver cannot connect to the remote.
  977. Use the exception to further understand the cause of the
  978. connectivity problem.
  979. .. versionadded:: 5.0
  980. """
  981. self._check_state()
  982. if config:
  983. experimental_warn(
  984. "All configuration key-word arguments to "
  985. "get_server_info() are experimental. They might be "
  986. "changed or removed in any future version without prior "
  987. "notice."
  988. )
  989. session_config = self._read_session_config(config)
  990. return self._get_server_info(session_config)
  991. def supports_multi_db(self) -> bool:
  992. """
  993. Check if the server or cluster supports multi-databases.
  994. :returns: Returns true if the server or cluster the driver connects to
  995. supports multi-databases, otherwise false.
  996. .. note::
  997. Feature support query based solely on the Bolt protocol version.
  998. The feature might still be disabled on the server side even if this
  999. function return :data:`True`. It just guarantees that the driver
  1000. won't throw a :exc:`.ConfigurationError` when trying to use this
  1001. driver feature.
  1002. """
  1003. self._check_state()
  1004. session_config = self._read_session_config({})
  1005. with self._session(session_config) as session:
  1006. session._connect(READ_ACCESS)
  1007. assert session._connection
  1008. return session._connection.supports_multiple_databases
  1009. if t.TYPE_CHECKING:
  1010. def verify_authentication(
  1011. self,
  1012. auth: Auth | tuple[str, str] | None = None,
  1013. # all other arguments are experimental
  1014. # they may be change or removed any time without prior notice
  1015. session_connection_timeout: float = ...,
  1016. connection_acquisition_timeout: float = ...,
  1017. max_transaction_retry_time: float = ...,
  1018. database: str | None = ...,
  1019. fetch_size: int = ...,
  1020. impersonated_user: str | None = ...,
  1021. bookmarks: t.Iterable[str] | Bookmarks | None = ...,
  1022. default_access_mode: str = ...,
  1023. bookmark_manager: (
  1024. BookmarkManager | BookmarkManager | None
  1025. ) = ...,
  1026. # undocumented/unsupported options
  1027. initial_retry_delay: float = ...,
  1028. retry_delay_multiplier: float = ...,
  1029. retry_delay_jitter_factor: float = ...,
  1030. ) -> bool: ...
  1031. else:
  1032. def verify_authentication(
  1033. self,
  1034. auth: Auth | tuple[str, str] | None = None,
  1035. **config,
  1036. ) -> bool:
  1037. """
  1038. Verify that the authentication information is valid.
  1039. Like :meth:`.verify_connectivity`, but for checking authentication.
  1040. Try to establish a working read connection to the remote server or
  1041. a member of a cluster and exchange some data. In a cluster, there
  1042. is no guarantee about which server will be contacted. If the data
  1043. exchange is successful and the authentication information is valid,
  1044. :data:`True` is returned. Otherwise, the error will be matched
  1045. against a list of known authentication errors. If the error is on
  1046. that list, :data:`False` is returned indicating that the
  1047. authentication information is invalid. Otherwise, the error is
  1048. re-raised.
  1049. :param auth: authentication information to verify.
  1050. Same as the session config :ref:`auth-ref`.
  1051. :param config: accepts the same configuration key-word arguments as
  1052. :meth:`session`.
  1053. .. warning::
  1054. All configuration key-word arguments (except ``auth``) are
  1055. experimental. They might be changed or removed in any
  1056. future version without prior notice.
  1057. :raises Exception: if the driver cannot connect to the remote.
  1058. Use the exception to further understand the cause of the
  1059. connectivity problem.
  1060. .. versionadded:: 5.8
  1061. .. versionchanged:: 5.14 Stabilized from experimental.
  1062. """
  1063. self._check_state()
  1064. if config:
  1065. experimental_warn(
  1066. "All configuration key-word arguments but auth to "
  1067. "verify_authentication() are experimental. They might be "
  1068. "changed or removed in any future version without prior "
  1069. "notice."
  1070. )
  1071. if "database" not in config:
  1072. config["database"] = "system"
  1073. session_config = self._read_session_config(config)
  1074. session_config = SessionConfig(session_config, {"auth": auth})
  1075. with self._session(session_config) as session:
  1076. try:
  1077. session._verify_authentication()
  1078. except Neo4jError as exc:
  1079. if exc.code in {
  1080. "Neo.ClientError.Security.CredentialsExpired",
  1081. "Neo.ClientError.Security.Forbidden",
  1082. "Neo.ClientError.Security.TokenExpired",
  1083. "Neo.ClientError.Security.Unauthorized",
  1084. }:
  1085. return False
  1086. raise
  1087. return True
  1088. def supports_session_auth(self) -> bool:
  1089. """
  1090. Check if the remote supports connection re-authentication.
  1091. :returns: Returns true if the server or cluster the driver connects to
  1092. supports re-authentication of existing connections, otherwise
  1093. false.
  1094. .. note::
  1095. Feature support query based solely on the Bolt protocol version.
  1096. The feature might still be disabled on the server side even if this
  1097. function return :data:`True`. It just guarantees that the driver
  1098. won't throw a :exc:`.ConfigurationError` when trying to use this
  1099. driver feature.
  1100. .. versionadded:: 5.8
  1101. """
  1102. self._check_state()
  1103. session_config = self._read_session_config({})
  1104. with self._session(session_config) as session:
  1105. session._connect(READ_ACCESS)
  1106. assert session._connection
  1107. return session._connection.supports_re_auth
  1108. def _get_server_info(self, session_config) -> ServerInfo:
  1109. with self._session(session_config) as session:
  1110. return session._get_server_info()
  1111. def _work(
  1112. tx: ManagedTransaction,
  1113. query: te.LiteralString,
  1114. parameters: dict[str, t.Any],
  1115. transformer: t.Callable[[Result], t.Union[_T]],
  1116. ) -> _T:
  1117. res = tx.run(query, parameters)
  1118. return transformer(res)
  1119. class BoltDriver(_Direct, Driver):
  1120. """
  1121. :class:`.BoltDriver` is instantiated for ``bolt`` URIs.
  1122. It addresses a single database machine. This may be a standalone server or
  1123. could be a specific member of a cluster.
  1124. Connections established by a :class:`.BoltDriver` are always made to
  1125. the exact host and port detailed in the URI.
  1126. This class is not supposed to be instantiated externally. Use
  1127. :meth:`GraphDatabase.driver` instead.
  1128. """
  1129. @classmethod
  1130. def open(cls, target, **config):
  1131. from .io import BoltPool
  1132. address = cls.parse_target(target)
  1133. pool_config, default_workspace_config = Config.consume_chain(
  1134. config, PoolConfig, WorkspaceConfig
  1135. )
  1136. pool = BoltPool.open(
  1137. address,
  1138. pool_config=pool_config,
  1139. workspace_config=default_workspace_config,
  1140. )
  1141. return cls(pool, default_workspace_config)
  1142. def __init__(self, pool, default_workspace_config):
  1143. _Direct.__init__(self, pool.address)
  1144. Driver.__init__(self, pool, default_workspace_config)
  1145. self._default_workspace_config = default_workspace_config
  1146. class Neo4jDriver(_Routing, Driver):
  1147. """
  1148. :class:`.Neo4jDriver` is instantiated for ``neo4j`` URIs.
  1149. The routing behaviour works in tandem with Neo4j's `Causal Clustering
  1150. <https://neo4j.com/docs/operations-manual/current/clustering/>`_
  1151. feature by directing read and write behaviour to appropriate
  1152. cluster members.
  1153. This class is not supposed to be instantiated externally. Use
  1154. :meth:`GraphDatabase.driver` instead.
  1155. """
  1156. @classmethod
  1157. def open(cls, *targets, routing_context=None, **config):
  1158. from .io import Neo4jPool
  1159. addresses = cls.parse_targets(*targets)
  1160. pool_config, default_workspace_config = Config.consume_chain(
  1161. config, PoolConfig, WorkspaceConfig
  1162. )
  1163. pool = Neo4jPool.open(
  1164. *addresses,
  1165. routing_context=routing_context,
  1166. pool_config=pool_config,
  1167. workspace_config=default_workspace_config,
  1168. )
  1169. return cls(pool, default_workspace_config)
  1170. def __init__(self, pool, default_workspace_config):
  1171. _Routing.__init__(self, [pool.address])
  1172. Driver.__init__(self, pool, default_workspace_config)
  1173. def _normalize_notifications_config(config_kwargs, *, driver_level=False):
  1174. list_config_keys = (
  1175. "notifications_disabled_categories",
  1176. "notifications_disabled_classifications",
  1177. )
  1178. for key in list_config_keys:
  1179. value = config_kwargs.get(key)
  1180. if value is not None:
  1181. config_kwargs[key] = [getattr(e, "value", e) for e in value]
  1182. disabled_categories = config_kwargs.pop(
  1183. "notifications_disabled_categories", None
  1184. )
  1185. if disabled_categories is not None:
  1186. disabled_classifications = config_kwargs.get(
  1187. "notifications_disabled_classifications"
  1188. )
  1189. if disabled_classifications is None:
  1190. disabled_classifications = disabled_categories
  1191. else:
  1192. disabled_classifications = list(
  1193. {*disabled_categories, *disabled_classifications}
  1194. )
  1195. config_kwargs["notifications_disabled_classifications"] = (
  1196. disabled_classifications
  1197. )
  1198. single_config_keys = (
  1199. "notifications_min_severity",
  1200. "warn_notification_severity",
  1201. )
  1202. for key in single_config_keys:
  1203. value = config_kwargs.get(key)
  1204. if value is not None:
  1205. config_kwargs[key] = getattr(value, "value", value)
  1206. value = config_kwargs.get("warn_notification_severity")
  1207. if value not in {*NotificationMinimumSeverity, None}:
  1208. raise ValueError(
  1209. f"Invalid value for configuration "
  1210. f"warn_notification_severity: {value}. Should be None, a "
  1211. f"NotificationMinimumSeverity, or a string representing a "
  1212. f"NotificationMinimumSeverity."
  1213. )
  1214. if driver_level:
  1215. if value is None:
  1216. if DEBUG_ENABLED:
  1217. config_kwargs["warn_notification_severity"] = (
  1218. NotificationMinimumSeverity.INFORMATION
  1219. )
  1220. elif value == NotificationMinimumSeverity.OFF:
  1221. config_kwargs["warn_notification_severity"] = None