_meta.py 6.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256
  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 platform
  18. import sys
  19. import typing as t
  20. from functools import wraps
  21. from inspect import isclass
  22. from warnings import warn
  23. from ._codec.packstream import RUST_AVAILABLE
  24. if t.TYPE_CHECKING:
  25. _FuncT = t.TypeVar("_FuncT", bound=t.Callable)
  26. # Can be automatically overridden in builds
  27. package = "neo4j"
  28. version = "5.28.1"
  29. deprecated_package = False
  30. def _compute_bolt_agent() -> dict[str, str]:
  31. def format_version_info(version_info):
  32. return "{}.{}.{}-{}-{}".format(*version_info)
  33. language = "Python"
  34. if RUST_AVAILABLE:
  35. language += "-Rust"
  36. return {
  37. "product": f"neo4j-python/{version}",
  38. "platform": (
  39. f"{platform.system() or 'Unknown'} "
  40. f"{platform.release() or 'unknown'}; "
  41. f"{platform.machine() or 'unknown'}"
  42. ),
  43. "language": f"{language}/{format_version_info(sys.version_info)}",
  44. "language_details": (
  45. f"{platform.python_implementation()}; "
  46. f"{format_version_info(sys.implementation.version)} "
  47. f"({', '.join(platform.python_build())}) "
  48. f"[{platform.python_compiler()}]"
  49. ),
  50. }
  51. BOLT_AGENT_DICT = _compute_bolt_agent()
  52. def _compute_user_agent() -> str:
  53. return (
  54. f'{BOLT_AGENT_DICT["product"]} '
  55. f'{BOLT_AGENT_DICT["language"]} '
  56. f'({sys.platform})'
  57. )
  58. USER_AGENT = _compute_user_agent()
  59. # Undocumented but exposed.
  60. # Other official drivers also provide means to access the default user agent.
  61. # Hence, we'll leave this here for now.
  62. def get_user_agent():
  63. """
  64. Obtain the driver's default user agent string.
  65. The user agent is sent to the server after a successful handshake.
  66. """
  67. return USER_AGENT
  68. def _id(x):
  69. return x
  70. def copy_signature(_: _FuncT) -> t.Callable[[t.Callable], _FuncT]:
  71. return _id
  72. # Copy globals as function locals to make sure that they are available
  73. # during Python shutdown when the Pool is destroyed.
  74. def deprecation_warn(message, stack_level=1, _warn=warn):
  75. _warn(message, category=DeprecationWarning, stacklevel=stack_level + 1)
  76. def deprecated(message: str) -> t.Callable[[_FuncT], _FuncT]:
  77. """
  78. Decorate deprecated functions and methods.
  79. ::
  80. @deprecated("'foo' has been deprecated in favour of 'bar'")
  81. def foo(x):
  82. pass
  83. @property
  84. @deprecated("'bar' will be internal without a replacement")
  85. def bar(self):
  86. return "bar"
  87. @property
  88. def baz(self):
  89. return self._baz
  90. @baz.setter
  91. @deprecated("'baz' will be read-only in the future")
  92. def baz(self, value):
  93. self._baz = value
  94. """
  95. return _make_warning_decorator(message, deprecation_warn)
  96. # TODO: 6.0 - remove this class, replace usage with PreviewWarning
  97. class ExperimentalWarning(Warning):
  98. """
  99. Base class for warnings about experimental features.
  100. .. deprecated:: 5.8
  101. we now use "preview" instead of "experimental":
  102. :class:`.PreviewWarning`.
  103. """
  104. def experimental_warn(message, stack_level=1):
  105. warn(message, category=ExperimentalWarning, stacklevel=stack_level + 1)
  106. def experimental(message) -> t.Callable[[_FuncT], _FuncT]:
  107. """
  108. Decorate functions and methods as experimental.
  109. ::
  110. @experimental("'foo' is an experimental function and may be "
  111. "removed in a future release")
  112. def foo(x):
  113. pass
  114. .. deprecated:: 5.8
  115. we now use "preview" instead of "experimental".
  116. """
  117. return _make_warning_decorator(message, experimental_warn)
  118. # TODO: 6.0 - consider moving this to the `warnings` module
  119. # and not to re-export it from the top-level package `neo4j`
  120. class PreviewWarning(Warning):
  121. """
  122. A driver feature in preview has been used.
  123. It might be changed without following the deprecation policy.
  124. See also https://github.com/neo4j/neo4j-python-driver/wiki/preview-features
  125. """
  126. def preview_warn(message, stack_level=1):
  127. message += (
  128. " It might be changed without following the deprecation policy. "
  129. "See also "
  130. "https://github.com/neo4j/neo4j-python-driver/wiki/preview-features."
  131. )
  132. warn(message, category=PreviewWarning, stacklevel=stack_level + 1)
  133. def preview(message) -> t.Callable[[_FuncT], _FuncT]:
  134. """
  135. Decorate functions and methods as preview.
  136. ::
  137. @preview("foo is a preview.")
  138. def foo(x):
  139. pass
  140. """
  141. return _make_warning_decorator(message, preview_warn)
  142. if t.TYPE_CHECKING:
  143. class _WarningFunc(t.Protocol):
  144. def __call__(self, message: str, stack_level: int = 1) -> None: ...
  145. else:
  146. _WarningFunc = object
  147. def _make_warning_decorator(
  148. message: str,
  149. warning_func: _WarningFunc,
  150. ) -> t.Callable[[_FuncT], _FuncT]:
  151. def decorator(f):
  152. if asyncio.iscoroutinefunction(f):
  153. @wraps(f)
  154. async def inner(*args, **kwargs):
  155. warning_func(message, stack_level=2)
  156. return await f(*args, **kwargs)
  157. inner._without_warning = f
  158. return inner
  159. if isclass(f):
  160. if hasattr(f, "__init__"):
  161. original_init = f.__init__
  162. @wraps(original_init)
  163. def inner(self, *args, **kwargs):
  164. warning_func(message, stack_level=2)
  165. return original_init(self, *args, **kwargs)
  166. def _without_warning(cls, *args, **kwargs):
  167. obj = cls.__new__(cls, *args, **kwargs)
  168. original_init(obj, *args, **kwargs)
  169. return obj
  170. f.__init__ = inner
  171. f._without_warning = classmethod(_without_warning)
  172. return f
  173. raise TypeError("Cannot decorate class without __init__")
  174. else:
  175. @wraps(f)
  176. def inner(*args, **kwargs):
  177. warning_func(message, stack_level=2)
  178. return f(*args, **kwargs)
  179. inner._without_warning = f
  180. return inner
  181. return decorator
  182. # Copy globals as function locals to make sure that they are available
  183. # during Python shutdown when the Pool is destroyed.
  184. def unclosed_resource_warn(obj, _warn=warn):
  185. cls_name = obj.__class__.__name__
  186. msg = f"unclosed {cls_name}: {obj!r}."
  187. _warn(msg, ResourceWarning, stacklevel=2, source=obj)