diff --git a/.buildinfo b/.buildinfo index c470b951f3..d9063147b9 100644 --- a/.buildinfo +++ b/.buildinfo @@ -1,4 +1,4 @@ # Sphinx build info version 1 # This file hashes the configuration used when building these files. When it is not found, a full rebuild will be done. -config: 3b487acafaaa48e4419e9ce22fa98b11 +config: 2e6cd5c82d10449c01cac1b11fe6f6d8 tags: 645f666f9bcd5a90fca523b33c5a78b7 diff --git a/.doctrees/apidocs.doctree b/.doctrees/apidocs.doctree index e600cf6e9f..a134aa1b27 100644 Binary files a/.doctrees/apidocs.doctree and b/.doctrees/apidocs.doctree differ diff --git a/.doctrees/environment.pickle b/.doctrees/environment.pickle index 456be32215..b20d34e904 100644 Binary files a/.doctrees/environment.pickle and b/.doctrees/environment.pickle differ diff --git a/_modules/index.html b/_modules/index.html index c37ae93de6..8a797171ea 100644 --- a/_modules/index.html +++ b/_modules/index.html @@ -2,7 +2,7 @@ - Overview: module code - sentry-python 2.14.0 documentation @@ -143,7 +143,7 @@

All modules for which code is available

- + diff --git a/_modules/sentry_sdk/api.html b/_modules/sentry_sdk/api.html index 9a1eabea8f..0dca9d0cb4 100644 --- a/_modules/sentry_sdk/api.html +++ b/_modules/sentry_sdk/api.html @@ -2,7 +2,7 @@ - sentry_sdk.api - sentry-python 2.14.0 documentation @@ -624,7 +624,7 @@

Source code for sentry_sdk.api

     
- + diff --git a/_modules/sentry_sdk/attachments.html b/_modules/sentry_sdk/attachments.html index 9a87c60ec9..6b618d2108 100644 --- a/_modules/sentry_sdk/attachments.html +++ b/_modules/sentry_sdk/attachments.html @@ -2,7 +2,7 @@ - sentry_sdk.attachments - sentry-python 2.14.0 documentation @@ -215,7 +215,7 @@

Source code for sentry_sdk.attachments

     
- + diff --git a/_modules/sentry_sdk/client.html b/_modules/sentry_sdk/client.html index 2ff904e839..46b1f1933f 100644 --- a/_modules/sentry_sdk/client.html +++ b/_modules/sentry_sdk/client.html @@ -2,7 +2,7 @@ - sentry_sdk.client - sentry-python 2.14.0 documentation @@ -111,7 +111,7 @@

Source code for sentry_sdk.client

 from collections.abc import Mapping
 from datetime import datetime, timezone
 from importlib import import_module
-from typing import cast
+from typing import cast, overload
 
 from sentry_sdk._compat import PY37, check_uwsgi_thread_support
 from sentry_sdk.utils import (
@@ -160,889 +160,915 @@ 

Source code for sentry_sdk.client

     from typing import Sequence
     from typing import Type
     from typing import Union
-
-    from sentry_sdk._types import Event, Hint, SDKInfo
-    from sentry_sdk.integrations import Integration
-    from sentry_sdk.metrics import MetricsAggregator
-    from sentry_sdk.scope import Scope
-    from sentry_sdk.session import Session
-    from sentry_sdk.transport import Transport
-
+    from typing import TypeVar
+
+    from sentry_sdk._types import Event, Hint, SDKInfo
+    from sentry_sdk.integrations import Integration
+    from sentry_sdk.metrics import MetricsAggregator
+    from sentry_sdk.scope import Scope
+    from sentry_sdk.session import Session
+    from sentry_sdk.transport import Transport
 
-_client_init_debug = ContextVar("client_init_debug")
+    I = TypeVar("I", bound=Integration)  # noqa: E741
 
-
-SDK_INFO = {
-    "name": "sentry.python",  # SDK name will be overridden after integrations have been loaded with sentry_sdk.integrations.setup_integrations()
-    "version": VERSION,
-    "packages": [{"name": "pypi:sentry-sdk", "version": VERSION}],
-}  # type: SDKInfo
-
-
-def _get_options(*args, **kwargs):
-    # type: (*Optional[str], **Any) -> Dict[str, Any]
-    if args and (isinstance(args[0], (bytes, str)) or args[0] is None):
-        dsn = args[0]  # type: Optional[str]
-        args = args[1:]
-    else:
-        dsn = None
-
-    if len(args) > 1:
-        raise TypeError("Only single positional argument is expected")
-
-    rv = dict(DEFAULT_OPTIONS)
-    options = dict(*args, **kwargs)
-    if dsn is not None and options.get("dsn") is None:
-        options["dsn"] = dsn
-
-    for key, value in options.items():
-        if key not in rv:
-            raise TypeError("Unknown option %r" % (key,))
-
-        rv[key] = value
+_client_init_debug = ContextVar("client_init_debug")
+
+
+SDK_INFO = {
+    "name": "sentry.python",  # SDK name will be overridden after integrations have been loaded with sentry_sdk.integrations.setup_integrations()
+    "version": VERSION,
+    "packages": [{"name": "pypi:sentry-sdk", "version": VERSION}],
+}  # type: SDKInfo
+
+
+def _get_options(*args, **kwargs):
+    # type: (*Optional[str], **Any) -> Dict[str, Any]
+    if args and (isinstance(args[0], (bytes, str)) or args[0] is None):
+        dsn = args[0]  # type: Optional[str]
+        args = args[1:]
+    else:
+        dsn = None
+
+    if len(args) > 1:
+        raise TypeError("Only single positional argument is expected")
+
+    rv = dict(DEFAULT_OPTIONS)
+    options = dict(*args, **kwargs)
+    if dsn is not None and options.get("dsn") is None:
+        options["dsn"] = dsn
+
+    for key, value in options.items():
+        if key not in rv:
+            raise TypeError("Unknown option %r" % (key,))
 
-    if rv["dsn"] is None:
-        rv["dsn"] = os.environ.get("SENTRY_DSN")
-
-    if rv["release"] is None:
-        rv["release"] = get_default_release()
-
-    if rv["environment"] is None:
-        rv["environment"] = os.environ.get("SENTRY_ENVIRONMENT") or "production"
-
-    if rv["debug"] is None:
-        rv["debug"] = env_to_bool(os.environ.get("SENTRY_DEBUG", "False"), strict=True)
-
-    if rv["server_name"] is None and hasattr(socket, "gethostname"):
-        rv["server_name"] = socket.gethostname()
-
-    if rv["instrumenter"] is None:
-        rv["instrumenter"] = INSTRUMENTER.SENTRY
-
-    if rv["project_root"] is None:
-        try:
-            project_root = os.getcwd()
-        except Exception:
-            project_root = None
-
-        rv["project_root"] = project_root
+        rv[key] = value
+
+    if rv["dsn"] is None:
+        rv["dsn"] = os.environ.get("SENTRY_DSN")
+
+    if rv["release"] is None:
+        rv["release"] = get_default_release()
+
+    if rv["environment"] is None:
+        rv["environment"] = os.environ.get("SENTRY_ENVIRONMENT") or "production"
+
+    if rv["debug"] is None:
+        rv["debug"] = env_to_bool(os.environ.get("SENTRY_DEBUG", "False"), strict=True)
+
+    if rv["server_name"] is None and hasattr(socket, "gethostname"):
+        rv["server_name"] = socket.gethostname()
+
+    if rv["instrumenter"] is None:
+        rv["instrumenter"] = INSTRUMENTER.SENTRY
+
+    if rv["project_root"] is None:
+        try:
+            project_root = os.getcwd()
+        except Exception:
+            project_root = None
 
-    if rv["enable_tracing"] is True and rv["traces_sample_rate"] is None:
-        rv["traces_sample_rate"] = 1.0
-
-    if rv["event_scrubber"] is None:
-        rv["event_scrubber"] = EventScrubber(send_default_pii=rv["send_default_pii"])
-
-    if rv["socket_options"] and not isinstance(rv["socket_options"], list):
-        logger.warning(
-            "Ignoring socket_options because of unexpected format. See urllib3.HTTPConnection.socket_options for the expected format."
-        )
-        rv["socket_options"] = None
-
-    return rv
+        rv["project_root"] = project_root
+
+    if rv["enable_tracing"] is True and rv["traces_sample_rate"] is None:
+        rv["traces_sample_rate"] = 1.0
+
+    if rv["event_scrubber"] is None:
+        rv["event_scrubber"] = EventScrubber(send_default_pii=rv["send_default_pii"])
+
+    if rv["socket_options"] and not isinstance(rv["socket_options"], list):
+        logger.warning(
+            "Ignoring socket_options because of unexpected format. See urllib3.HTTPConnection.socket_options for the expected format."
+        )
+        rv["socket_options"] = None
 
-
-try:
-    # Python 3.6+
-    module_not_found_error = ModuleNotFoundError
-except Exception:
-    # Older Python versions
-    module_not_found_error = ImportError  # type: ignore
-
-
+    return rv
+
+
+try:
+    # Python 3.6+
+    module_not_found_error = ModuleNotFoundError
+except Exception:
+    # Older Python versions
+    module_not_found_error = ImportError  # type: ignore
+
+
 
[docs] -class BaseClient: - """ - .. versionadded:: 2.0.0 - - The basic definition of a client that is used for sending data to Sentry. - """ - - def __init__(self, options=None): - # type: (Optional[Dict[str, Any]]) -> None - self.options = ( - options if options is not None else DEFAULT_OPTIONS - ) # type: Dict[str, Any] - - self.transport = None # type: Optional[Transport] - self.monitor = None # type: Optional[Monitor] - self.metrics_aggregator = None # type: Optional[MetricsAggregator] - - def __getstate__(self, *args, **kwargs): - # type: (*Any, **Any) -> Any - return {"options": {}} - - def __setstate__(self, *args, **kwargs): - # type: (*Any, **Any) -> None - pass - - @property - def dsn(self): - # type: () -> Optional[str] - return None - - def should_send_default_pii(self): - # type: () -> bool - return False - +class BaseClient: + """ + .. versionadded:: 2.0.0 + + The basic definition of a client that is used for sending data to Sentry. + """ + + def __init__(self, options=None): + # type: (Optional[Dict[str, Any]]) -> None + self.options = ( + options if options is not None else DEFAULT_OPTIONS + ) # type: Dict[str, Any] + + self.transport = None # type: Optional[Transport] + self.monitor = None # type: Optional[Monitor] + self.metrics_aggregator = None # type: Optional[MetricsAggregator] + + def __getstate__(self, *args, **kwargs): + # type: (*Any, **Any) -> Any + return {"options": {}} + + def __setstate__(self, *args, **kwargs): + # type: (*Any, **Any) -> None + pass + + @property + def dsn(self): + # type: () -> Optional[str] + return None + + def should_send_default_pii(self): + # type: () -> bool + return False +
[docs] - def is_active(self): - # type: () -> bool - """ - .. versionadded:: 2.0.0 - - Returns whether the client is active (able to send data to Sentry) - """ - return False
+
def is_active(self): + # type: () -> bool + """ + .. versionadded:: 2.0.0 + + Returns whether the client is active (able to send data to Sentry) + """ + return False
-
- def capture_event(self, *args, **kwargs): - # type: (*Any, **Any) -> Optional[str] - return None - - def capture_session(self, *args, **kwargs): - # type: (*Any, **Any) -> None - return None - - def get_integration(self, *args, **kwargs): - # type: (*Any, **Any) -> Any - return None + + def capture_event(self, *args, **kwargs): + # type: (*Any, **Any) -> Optional[str] + return None + + def capture_session(self, *args, **kwargs): + # type: (*Any, **Any) -> None + return None + + if TYPE_CHECKING: - def close(self, *args, **kwargs): - # type: (*Any, **Any) -> None - return None - - def flush(self, *args, **kwargs): - # type: (*Any, **Any) -> None - return None - - def __enter__(self): - # type: () -> BaseClient - return self - - def __exit__(self, exc_type, exc_value, tb): - # type: (Any, Any, Any) -> None - return None
+
@overload + def get_integration(self, name_or_class): + # type: (str) -> Optional[Integration] + ... + + @overload + def get_integration(self, name_or_class): + # type: (type[I]) -> Optional[I] + ... + + def get_integration(self, name_or_class): + # type: (Union[str, type[Integration]]) -> Optional[Integration] + return None + + def close(self, *args, **kwargs): + # type: (*Any, **Any) -> None + return None + + def flush(self, *args, **kwargs): + # type: (*Any, **Any) -> None + return None + + def __enter__(self): + # type: () -> BaseClient + return self + + def __exit__(self, exc_type, exc_value, tb): + # type: (Any, Any, Any) -> None + return None
- - + +
[docs] -class NonRecordingClient(BaseClient): - """ - .. versionadded:: 2.0.0 - - A client that does not send any events to Sentry. This is used as a fallback when the Sentry SDK is not yet initialized. - """ - - pass
+
class NonRecordingClient(BaseClient): + """ + .. versionadded:: 2.0.0 + + A client that does not send any events to Sentry. This is used as a fallback when the Sentry SDK is not yet initialized. + """ + + pass - - + +
[docs] -class _Client(BaseClient): - """ - The client is internally responsible for capturing the events and - forwarding them to sentry through the configured transport. It takes - the client options as keyword arguments and optionally the DSN as first - argument. - - Alias of :py:class:`sentry_sdk.Client`. (Was created for better intelisense support) - """ - - def __init__(self, *args, **kwargs): - # type: (*Any, **Any) -> None - super(_Client, self).__init__(options=get_options(*args, **kwargs)) - self._init_impl() - - def __getstate__(self): - # type: () -> Any - return {"options": self.options} - - def __setstate__(self, state): - # type: (Any) -> None - self.options = state["options"] - self._init_impl() +class _Client(BaseClient): + """ + The client is internally responsible for capturing the events and + forwarding them to sentry through the configured transport. It takes + the client options as keyword arguments and optionally the DSN as first + argument. + + Alias of :py:class:`sentry_sdk.Client`. (Was created for better intelisense support) + """ - def _setup_instrumentation(self, functions_to_trace): - # type: (Sequence[Dict[str, str]]) -> None - """ - Instruments the functions given in the list `functions_to_trace` with the `@sentry_sdk.tracing.trace` decorator. - """ - for function in functions_to_trace: - class_name = None - function_qualname = function["qualified_name"] - module_name, function_name = function_qualname.rsplit(".", 1) - - try: - # Try to import module and function - # ex: "mymodule.submodule.funcname" + def __init__(self, *args, **kwargs): + # type: (*Any, **Any) -> None + super(_Client, self).__init__(options=get_options(*args, **kwargs)) + self._init_impl() + + def __getstate__(self): + # type: () -> Any + return {"options": self.options} + + def __setstate__(self, state): + # type: (Any) -> None + self.options = state["options"] + self._init_impl() - module_obj = import_module(module_name) - function_obj = getattr(module_obj, function_name) - setattr(module_obj, function_name, trace(function_obj)) - logger.debug("Enabled tracing for %s", function_qualname) - except module_not_found_error: - try: - # Try to import a class - # ex: "mymodule.submodule.MyClassName.member_function" - - module_name, class_name = module_name.rsplit(".", 1) - module_obj = import_module(module_name) - class_obj = getattr(module_obj, class_name) - function_obj = getattr(class_obj, function_name) - function_type = type(class_obj.__dict__[function_name]) - traced_function = trace(function_obj) - - if function_type in (staticmethod, classmethod): - traced_function = staticmethod(traced_function) - - setattr(class_obj, function_name, traced_function) - setattr(module_obj, class_name, class_obj) - logger.debug("Enabled tracing for %s", function_qualname) + def _setup_instrumentation(self, functions_to_trace): + # type: (Sequence[Dict[str, str]]) -> None + """ + Instruments the functions given in the list `functions_to_trace` with the `@sentry_sdk.tracing.trace` decorator. + """ + for function in functions_to_trace: + class_name = None + function_qualname = function["qualified_name"] + module_name, function_name = function_qualname.rsplit(".", 1) + + try: + # Try to import module and function + # ex: "mymodule.submodule.funcname" + + module_obj = import_module(module_name) + function_obj = getattr(module_obj, function_name) + setattr(module_obj, function_name, trace(function_obj)) + logger.debug("Enabled tracing for %s", function_qualname) + except module_not_found_error: + try: + # Try to import a class + # ex: "mymodule.submodule.MyClassName.member_function" - except Exception as e: - logger.warning( - "Can not enable tracing for '%s'. (%s) Please check your `functions_to_trace` parameter.", - function_qualname, - e, - ) + module_name, class_name = module_name.rsplit(".", 1) + module_obj = import_module(module_name) + class_obj = getattr(module_obj, class_name) + function_obj = getattr(class_obj, function_name) + function_type = type(class_obj.__dict__[function_name]) + traced_function = trace(function_obj) - except Exception as e: - logger.warning( - "Can not enable tracing for '%s'. (%s) Please check your `functions_to_trace` parameter.", - function_qualname, - e, - ) + if function_type in (staticmethod, classmethod): + traced_function = staticmethod(traced_function) + + setattr(class_obj, function_name, traced_function) + setattr(module_obj, class_name, class_obj) + logger.debug("Enabled tracing for %s", function_qualname) - def _init_impl(self): - # type: () -> None - old_debug = _client_init_debug.get(False) - - def _capture_envelope(envelope): - # type: (Envelope) -> None - if self.transport is not None: - self.transport.capture_envelope(envelope) - - try: - _client_init_debug.set(self.options["debug"]) - self.transport = make_transport(self.options) - - self.monitor = None - if self.transport: - if self.options["enable_backpressure_handling"]: - self.monitor = Monitor(self.transport) + except Exception as e: + logger.warning( + "Can not enable tracing for '%s'. (%s) Please check your `functions_to_trace` parameter.", + function_qualname, + e, + ) + + except Exception as e: + logger.warning( + "Can not enable tracing for '%s'. (%s) Please check your `functions_to_trace` parameter.", + function_qualname, + e, + ) + + def _init_impl(self): + # type: () -> None + old_debug = _client_init_debug.get(False) - self.session_flusher = SessionFlusher(capture_func=_capture_envelope) - - self.metrics_aggregator = None # type: Optional[MetricsAggregator] - experiments = self.options.get("_experiments", {}) - if experiments.get("enable_metrics", True): - # Context vars are not working correctly on Python <=3.6 - # with gevent. - metrics_supported = not is_gevent() or PY37 - if metrics_supported: - from sentry_sdk.metrics import MetricsAggregator - - self.metrics_aggregator = MetricsAggregator( - capture_func=_capture_envelope, - enable_code_locations=bool( - experiments.get("metric_code_locations", True) - ), - ) - else: - logger.info( - "Metrics not supported on Python 3.6 and lower with gevent." - ) - - max_request_body_size = ("always", "never", "small", "medium") - if self.options["max_request_body_size"] not in max_request_body_size: - raise ValueError( - "Invalid value for max_request_body_size. Must be one of {}".format( - max_request_body_size - ) - ) - - if self.options["_experiments"].get("otel_powered_performance", False): - logger.debug( - "[OTel] Enabling experimental OTel-powered performance monitoring." - ) - self.options["instrumenter"] = INSTRUMENTER.OTEL - if ( - "sentry_sdk.integrations.opentelemetry.integration.OpenTelemetryIntegration" - not in _DEFAULT_INTEGRATIONS - ): - _DEFAULT_INTEGRATIONS.append( - "sentry_sdk.integrations.opentelemetry.integration.OpenTelemetryIntegration", + def _capture_envelope(envelope): + # type: (Envelope) -> None + if self.transport is not None: + self.transport.capture_envelope(envelope) + + try: + _client_init_debug.set(self.options["debug"]) + self.transport = make_transport(self.options) + + self.monitor = None + if self.transport: + if self.options["enable_backpressure_handling"]: + self.monitor = Monitor(self.transport) + + self.session_flusher = SessionFlusher(capture_func=_capture_envelope) + + self.metrics_aggregator = None # type: Optional[MetricsAggregator] + experiments = self.options.get("_experiments", {}) + if experiments.get("enable_metrics", True): + # Context vars are not working correctly on Python <=3.6 + # with gevent. + metrics_supported = not is_gevent() or PY37 + if metrics_supported: + from sentry_sdk.metrics import MetricsAggregator + + self.metrics_aggregator = MetricsAggregator( + capture_func=_capture_envelope, + enable_code_locations=bool( + experiments.get("metric_code_locations", True) + ), + ) + else: + logger.info( + "Metrics not supported on Python 3.6 and lower with gevent." + ) + + max_request_body_size = ("always", "never", "small", "medium") + if self.options["max_request_body_size"] not in max_request_body_size: + raise ValueError( + "Invalid value for max_request_body_size. Must be one of {}".format( + max_request_body_size ) - - self.integrations = setup_integrations( - self.options["integrations"], - with_defaults=self.options["default_integrations"], - with_auto_enabling_integrations=self.options[ - "auto_enabling_integrations" - ], - disabled_integrations=self.options["disabled_integrations"], - ) - - self.spotlight = None - spotlight_config = self.options.get("spotlight") - if spotlight_config is None and "SENTRY_SPOTLIGHT" in os.environ: - spotlight_env_value = os.environ["SENTRY_SPOTLIGHT"] - spotlight_config = env_to_bool(spotlight_env_value, strict=True) - self.options["spotlight"] = ( - spotlight_config - if spotlight_config is not None - else spotlight_env_value - ) - - if self.options.get("spotlight"): - self.spotlight = setup_spotlight(self.options) + ) + + if self.options["_experiments"].get("otel_powered_performance", False): + logger.debug( + "[OTel] Enabling experimental OTel-powered performance monitoring." + ) + self.options["instrumenter"] = INSTRUMENTER.OTEL + if ( + "sentry_sdk.integrations.opentelemetry.integration.OpenTelemetryIntegration" + not in _DEFAULT_INTEGRATIONS + ): + _DEFAULT_INTEGRATIONS.append( + "sentry_sdk.integrations.opentelemetry.integration.OpenTelemetryIntegration", + ) + + self.integrations = setup_integrations( + self.options["integrations"], + with_defaults=self.options["default_integrations"], + with_auto_enabling_integrations=self.options[ + "auto_enabling_integrations" + ], + disabled_integrations=self.options["disabled_integrations"], + ) - sdk_name = get_sdk_name(list(self.integrations.keys())) - SDK_INFO["name"] = sdk_name - logger.debug("Setting SDK name to '%s'", sdk_name) - - if has_profiling_enabled(self.options): - try: - setup_profiler(self.options) - except Exception as e: - logger.debug("Can not set up profiler. (%s)", e) - else: - try: - setup_continuous_profiler( - self.options, - sdk_info=SDK_INFO, - capture_func=_capture_envelope, - ) - except Exception as e: - logger.debug("Can not set up continuous profiler. (%s)", e) - - finally: - _client_init_debug.set(old_debug) - - self._setup_instrumentation(self.options.get("functions_to_trace", [])) - - if ( - self.monitor - or self.metrics_aggregator - or has_profiling_enabled(self.options) - or isinstance(self.transport, HttpTransport) - ): - # If we have anything on that could spawn a background thread, we - # need to check if it's safe to use them. - check_uwsgi_thread_support() - + self.spotlight = None + spotlight_config = self.options.get("spotlight") + if spotlight_config is None and "SENTRY_SPOTLIGHT" in os.environ: + spotlight_env_value = os.environ["SENTRY_SPOTLIGHT"] + spotlight_config = env_to_bool(spotlight_env_value, strict=True) + self.options["spotlight"] = ( + spotlight_config + if spotlight_config is not None + else spotlight_env_value + ) + + if self.options.get("spotlight"): + self.spotlight = setup_spotlight(self.options) + + sdk_name = get_sdk_name(list(self.integrations.keys())) + SDK_INFO["name"] = sdk_name + logger.debug("Setting SDK name to '%s'", sdk_name) + + if has_profiling_enabled(self.options): + try: + setup_profiler(self.options) + except Exception as e: + logger.debug("Can not set up profiler. (%s)", e) + else: + try: + setup_continuous_profiler( + self.options, + sdk_info=SDK_INFO, + capture_func=_capture_envelope, + ) + except Exception as e: + logger.debug("Can not set up continuous profiler. (%s)", e) + + finally: + _client_init_debug.set(old_debug) + + self._setup_instrumentation(self.options.get("functions_to_trace", [])) + + if ( + self.monitor + or self.metrics_aggregator + or has_profiling_enabled(self.options) + or isinstance(self.transport, HttpTransport) + ): + # If we have anything on that could spawn a background thread, we + # need to check if it's safe to use them. + check_uwsgi_thread_support() +
[docs] - def is_active(self): - # type: () -> bool - """ - .. versionadded:: 2.0.0 - - Returns whether the client is active (able to send data to Sentry) - """ - return True
+
def is_active(self): + # type: () -> bool + """ + .. versionadded:: 2.0.0 + + Returns whether the client is active (able to send data to Sentry) + """ + return True
-
+
[docs] - def should_send_default_pii(self): - # type: () -> bool - """ - .. versionadded:: 2.0.0 - - Returns whether the client should send default PII (Personally Identifiable Information) data to Sentry. - """ - return self.options.get("send_default_pii", False)
+
def should_send_default_pii(self): + # type: () -> bool + """ + .. versionadded:: 2.0.0 + + Returns whether the client should send default PII (Personally Identifiable Information) data to Sentry. + """ + return self.options.get("send_default_pii", False) - - @property - def dsn(self): - # type: () -> Optional[str] - """Returns the configured DSN as string.""" - return self.options["dsn"] - - def _prepare_event( - self, - event, # type: Event - hint, # type: Hint - scope, # type: Optional[Scope] - ): - # type: (...) -> Optional[Event] - if event.get("timestamp") is None: - event["timestamp"] = datetime.now(timezone.utc) - - if scope is not None: - is_transaction = event.get("type") == "transaction" - spans_before = len(event.get("spans", [])) - event_ = scope.apply_to_event(event, hint, self.options) - - # one of the event/error processors returned None - if event_ is None: - if self.transport: - self.transport.record_lost_event( - "event_processor", - data_category=("transaction" if is_transaction else "error"), - ) - if is_transaction: - self.transport.record_lost_event( - "event_processor", - data_category="span", - quantity=spans_before + 1, # +1 for the transaction itself - ) - return None - - event = event_ - - spans_delta = spans_before - len(event.get("spans", [])) - if is_transaction and spans_delta > 0 and self.transport is not None: - self.transport.record_lost_event( - "event_processor", data_category="span", quantity=spans_delta - ) - - if ( - self.options["attach_stacktrace"] - and "exception" not in event - and "stacktrace" not in event - and "threads" not in event - ): - with capture_internal_exceptions(): - event["threads"] = { - "values": [ - { - "stacktrace": current_stacktrace( - include_local_variables=self.options.get( - "include_local_variables", True - ), - max_value_length=self.options.get( - "max_value_length", DEFAULT_MAX_VALUE_LENGTH - ), - ), - "crashed": False, - "current": True, - } - ] - } - - for key in "release", "environment", "server_name", "dist": - if event.get(key) is None and self.options[key] is not None: - event[key] = str(self.options[key]).strip() # type: ignore[literal-required] - if event.get("sdk") is None: - sdk_info = dict(SDK_INFO) - sdk_info["integrations"] = sorted(self.integrations.keys()) - event["sdk"] = sdk_info - - if event.get("platform") is None: - event["platform"] = "python" - - event = handle_in_app( - event, - self.options["in_app_exclude"], - self.options["in_app_include"], - self.options["project_root"], - ) - - if event is not None: - event_scrubber = self.options["event_scrubber"] - if event_scrubber: - event_scrubber.scrub_event(event) - - # Postprocess the event here so that annotated types do - # generally not surface in before_send - if event is not None: - event = cast( - "Event", - serialize( - cast("Dict[str, Any]", event), - max_request_body_size=self.options.get("max_request_body_size"), - max_value_length=self.options.get("max_value_length"), - custom_repr=self.options.get("custom_repr"), - ), - ) - - before_send = self.options["before_send"] - if ( - before_send is not None - and event is not None - and event.get("type") != "transaction" - ): - new_event = None - with capture_internal_exceptions(): - new_event = before_send(event, hint or {}) - if new_event is None: - logger.info("before send dropped event") - if self.transport: - self.transport.record_lost_event( - "before_send", data_category="error" - ) - event = new_event # type: ignore - - before_send_transaction = self.options["before_send_transaction"] - if ( - before_send_transaction is not None - and event is not None - and event.get("type") == "transaction" - ): - new_event = None - spans_before = len(event.get("spans", [])) - with capture_internal_exceptions(): - new_event = before_send_transaction(event, hint or {}) - if new_event is None: - logger.info("before send transaction dropped event") - if self.transport: - self.transport.record_lost_event( - reason="before_send", data_category="transaction" - ) - self.transport.record_lost_event( - reason="before_send", - data_category="span", - quantity=spans_before + 1, # +1 for the transaction itself - ) - else: - spans_delta = spans_before - len(new_event.get("spans", [])) - if spans_delta > 0 and self.transport is not None: - self.transport.record_lost_event( - reason="before_send", data_category="span", quantity=spans_delta - ) - - event = new_event # type: ignore - - return event - - def _is_ignored_error(self, event, hint): - # type: (Event, Hint) -> bool - exc_info = hint.get("exc_info") - if exc_info is None: - return False - - error = exc_info[0] - error_type_name = get_type_name(exc_info[0]) - error_full_name = "%s.%s" % (exc_info[0].__module__, error_type_name) + @property + def dsn(self): + # type: () -> Optional[str] + """Returns the configured DSN as string.""" + return self.options["dsn"] + + def _prepare_event( + self, + event, # type: Event + hint, # type: Hint + scope, # type: Optional[Scope] + ): + # type: (...) -> Optional[Event] + + if event.get("timestamp") is None: + event["timestamp"] = datetime.now(timezone.utc) + + if scope is not None: + is_transaction = event.get("type") == "transaction" + spans_before = len(event.get("spans", [])) + event_ = scope.apply_to_event(event, hint, self.options) + + # one of the event/error processors returned None + if event_ is None: + if self.transport: + self.transport.record_lost_event( + "event_processor", + data_category=("transaction" if is_transaction else "error"), + ) + if is_transaction: + self.transport.record_lost_event( + "event_processor", + data_category="span", + quantity=spans_before + 1, # +1 for the transaction itself + ) + return None + + event = event_ + + spans_delta = spans_before - len(event.get("spans", [])) + if is_transaction and spans_delta > 0 and self.transport is not None: + self.transport.record_lost_event( + "event_processor", data_category="span", quantity=spans_delta + ) + + if ( + self.options["attach_stacktrace"] + and "exception" not in event + and "stacktrace" not in event + and "threads" not in event + ): + with capture_internal_exceptions(): + event["threads"] = { + "values": [ + { + "stacktrace": current_stacktrace( + include_local_variables=self.options.get( + "include_local_variables", True + ), + max_value_length=self.options.get( + "max_value_length", DEFAULT_MAX_VALUE_LENGTH + ), + ), + "crashed": False, + "current": True, + } + ] + } + + for key in "release", "environment", "server_name", "dist": + if event.get(key) is None and self.options[key] is not None: + event[key] = str(self.options[key]).strip() # type: ignore[literal-required] + if event.get("sdk") is None: + sdk_info = dict(SDK_INFO) + sdk_info["integrations"] = sorted(self.integrations.keys()) + event["sdk"] = sdk_info + + if event.get("platform") is None: + event["platform"] = "python" + + event = handle_in_app( + event, + self.options["in_app_exclude"], + self.options["in_app_include"], + self.options["project_root"], + ) + + if event is not None: + event_scrubber = self.options["event_scrubber"] + if event_scrubber: + event_scrubber.scrub_event(event) + + # Postprocess the event here so that annotated types do + # generally not surface in before_send + if event is not None: + event = cast( + "Event", + serialize( + cast("Dict[str, Any]", event), + max_request_body_size=self.options.get("max_request_body_size"), + max_value_length=self.options.get("max_value_length"), + custom_repr=self.options.get("custom_repr"), + ), + ) + + before_send = self.options["before_send"] + if ( + before_send is not None + and event is not None + and event.get("type") != "transaction" + ): + new_event = None + with capture_internal_exceptions(): + new_event = before_send(event, hint or {}) + if new_event is None: + logger.info("before send dropped event") + if self.transport: + self.transport.record_lost_event( + "before_send", data_category="error" + ) + event = new_event # type: ignore + + before_send_transaction = self.options["before_send_transaction"] + if ( + before_send_transaction is not None + and event is not None + and event.get("type") == "transaction" + ): + new_event = None + spans_before = len(event.get("spans", [])) + with capture_internal_exceptions(): + new_event = before_send_transaction(event, hint or {}) + if new_event is None: + logger.info("before send transaction dropped event") + if self.transport: + self.transport.record_lost_event( + reason="before_send", data_category="transaction" + ) + self.transport.record_lost_event( + reason="before_send", + data_category="span", + quantity=spans_before + 1, # +1 for the transaction itself + ) + else: + spans_delta = spans_before - len(new_event.get("spans", [])) + if spans_delta > 0 and self.transport is not None: + self.transport.record_lost_event( + reason="before_send", data_category="span", quantity=spans_delta + ) - for ignored_error in self.options["ignore_errors"]: - # String types are matched against the type name in the - # exception only - if isinstance(ignored_error, str): - if ignored_error == error_full_name or ignored_error == error_type_name: - return True - else: - if issubclass(error, ignored_error): - return True + event = new_event # type: ignore + + return event + + def _is_ignored_error(self, event, hint): + # type: (Event, Hint) -> bool + exc_info = hint.get("exc_info") + if exc_info is None: + return False - return False - - def _should_capture( - self, - event, # type: Event - hint, # type: Hint - scope=None, # type: Optional[Scope] - ): - # type: (...) -> bool - # Transactions are sampled independent of error events. - is_transaction = event.get("type") == "transaction" - if is_transaction: - return True + error = exc_info[0] + error_type_name = get_type_name(exc_info[0]) + error_full_name = "%s.%s" % (exc_info[0].__module__, error_type_name) + + for ignored_error in self.options["ignore_errors"]: + # String types are matched against the type name in the + # exception only + if isinstance(ignored_error, str): + if ignored_error == error_full_name or ignored_error == error_type_name: + return True + else: + if issubclass(error, ignored_error): + return True - ignoring_prevents_recursion = scope is not None and not scope._should_capture - if ignoring_prevents_recursion: - return False - - ignored_by_config_option = self._is_ignored_error(event, hint) - if ignored_by_config_option: - return False - - return True - - def _should_sample_error( - self, - event, # type: Event - hint, # type: Hint - ): - # type: (...) -> bool - error_sampler = self.options.get("error_sampler", None) + return False + + def _should_capture( + self, + event, # type: Event + hint, # type: Hint + scope=None, # type: Optional[Scope] + ): + # type: (...) -> bool + # Transactions are sampled independent of error events. + is_transaction = event.get("type") == "transaction" + if is_transaction: + return True + + ignoring_prevents_recursion = scope is not None and not scope._should_capture + if ignoring_prevents_recursion: + return False - if callable(error_sampler): - with capture_internal_exceptions(): - sample_rate = error_sampler(event, hint) - else: - sample_rate = self.options["sample_rate"] + ignored_by_config_option = self._is_ignored_error(event, hint) + if ignored_by_config_option: + return False + + return True - try: - not_in_sample_rate = sample_rate < 1.0 and random.random() >= sample_rate - except NameError: - logger.warning( - "The provided error_sampler raised an error. Defaulting to sampling the event." - ) - - # If the error_sampler raised an error, we should sample the event, since the default behavior - # (when no sample_rate or error_sampler is provided) is to sample all events. - not_in_sample_rate = False - except TypeError: - parameter, verb = ( - ("error_sampler", "returned") - if callable(error_sampler) - else ("sample_rate", "contains") - ) - logger.warning( - "The provided %s %s an invalid value of %s. The value should be a float or a bool. Defaulting to sampling the event." - % (parameter, verb, repr(sample_rate)) + def _should_sample_error( + self, + event, # type: Event + hint, # type: Hint + ): + # type: (...) -> bool + error_sampler = self.options.get("error_sampler", None) + + if callable(error_sampler): + with capture_internal_exceptions(): + sample_rate = error_sampler(event, hint) + else: + sample_rate = self.options["sample_rate"] + + try: + not_in_sample_rate = sample_rate < 1.0 and random.random() >= sample_rate + except NameError: + logger.warning( + "The provided error_sampler raised an error. Defaulting to sampling the event." ) - # If the sample_rate has an invalid value, we should sample the event, since the default behavior + # If the error_sampler raised an error, we should sample the event, since the default behavior # (when no sample_rate or error_sampler is provided) is to sample all events. not_in_sample_rate = False - - if not_in_sample_rate: - # because we will not sample this event, record a "lost event". - if self.transport: - self.transport.record_lost_event("sample_rate", data_category="error") - - return False - - return True - - def _update_session_from_event( - self, - session, # type: Session - event, # type: Event - ): - # type: (...) -> None - - crashed = False - errored = False - user_agent = None - - exceptions = (event.get("exception") or {}).get("values") - if exceptions: - errored = True - for error in exceptions: - mechanism = error.get("mechanism") - if isinstance(mechanism, Mapping) and mechanism.get("handled") is False: - crashed = True - break - - user = event.get("user") - - if session.user_agent is None: - headers = (event.get("request") or {}).get("headers") - headers_dict = headers if isinstance(headers, dict) else {} - for k, v in headers_dict.items(): - if k.lower() == "user-agent": - user_agent = v - break - - session.update( - status="crashed" if crashed else None, - user=user, - user_agent=user_agent, - errors=session.errors + (errored or crashed), - ) - + except TypeError: + parameter, verb = ( + ("error_sampler", "returned") + if callable(error_sampler) + else ("sample_rate", "contains") + ) + logger.warning( + "The provided %s %s an invalid value of %s. The value should be a float or a bool. Defaulting to sampling the event." + % (parameter, verb, repr(sample_rate)) + ) + + # If the sample_rate has an invalid value, we should sample the event, since the default behavior + # (when no sample_rate or error_sampler is provided) is to sample all events. + not_in_sample_rate = False + + if not_in_sample_rate: + # because we will not sample this event, record a "lost event". + if self.transport: + self.transport.record_lost_event("sample_rate", data_category="error") + + return False + + return True + + def _update_session_from_event( + self, + session, # type: Session + event, # type: Event + ): + # type: (...) -> None + + crashed = False + errored = False + user_agent = None + + exceptions = (event.get("exception") or {}).get("values") + if exceptions: + errored = True + for error in exceptions: + mechanism = error.get("mechanism") + if isinstance(mechanism, Mapping) and mechanism.get("handled") is False: + crashed = True + break + + user = event.get("user") + + if session.user_agent is None: + headers = (event.get("request") or {}).get("headers") + headers_dict = headers if isinstance(headers, dict) else {} + for k, v in headers_dict.items(): + if k.lower() == "user-agent": + user_agent = v + break + + session.update( + status="crashed" if crashed else None, + user=user, + user_agent=user_agent, + errors=session.errors + (errored or crashed), + ) +
[docs] - def capture_event( - self, - event, # type: Event - hint=None, # type: Optional[Hint] - scope=None, # type: Optional[Scope] - ): - # type: (...) -> Optional[str] - """Captures an event. - - :param event: A ready-made event that can be directly sent to Sentry. - - :param hint: Contains metadata about the event that can be read from `before_send`, such as the original exception object or a HTTP request object. - - :param scope: An optional :py:class:`sentry_sdk.Scope` to apply to events. - - :returns: An event ID. May be `None` if there is no DSN set or of if the SDK decided to discard the event for other reasons. In such situations setting `debug=True` on `init()` may help. - """ - if hint is None: - hint = {} - event_id = event.get("event_id") - hint = dict(hint or ()) # type: Hint - - if event_id is None: - event["event_id"] = event_id = uuid.uuid4().hex - if not self._should_capture(event, hint, scope): - return None + def capture_event( + self, + event, # type: Event + hint=None, # type: Optional[Hint] + scope=None, # type: Optional[Scope] + ): + # type: (...) -> Optional[str] + """Captures an event. + + :param event: A ready-made event that can be directly sent to Sentry. + + :param hint: Contains metadata about the event that can be read from `before_send`, such as the original exception object or a HTTP request object. - profile = event.pop("profile", None) + :param scope: An optional :py:class:`sentry_sdk.Scope` to apply to events. - event_opt = self._prepare_event(event, hint, scope) - if event_opt is None: - return None - - # whenever we capture an event we also check if the session needs - # to be updated based on that information. - session = scope._session if scope else None - if session: - self._update_session_from_event(session, event) - - is_transaction = event_opt.get("type") == "transaction" - is_checkin = event_opt.get("type") == "check_in" - - if ( - not is_transaction - and not is_checkin - and not self._should_sample_error(event, hint) - ): - return None - - attachments = hint.get("attachments") - - trace_context = event_opt.get("contexts", {}).get("trace") or {} - dynamic_sampling_context = trace_context.pop("dynamic_sampling_context", {}) - - headers = { - "event_id": event_opt["event_id"], - "sent_at": format_timestamp(datetime.now(timezone.utc)), - } # type: dict[str, object] - - if dynamic_sampling_context: - headers["trace"] = dynamic_sampling_context - - envelope = Envelope(headers=headers) - - if is_transaction: - if isinstance(profile, Profile): - envelope.add_profile(profile.to_json(event_opt, self.options)) - envelope.add_transaction(event_opt) - elif is_checkin: - envelope.add_checkin(event_opt) - else: - envelope.add_event(event_opt) + :returns: An event ID. May be `None` if there is no DSN set or of if the SDK decided to discard the event for other reasons. In such situations setting `debug=True` on `init()` may help. + """ + if hint is None: + hint = {} + event_id = event.get("event_id") + hint = dict(hint or ()) # type: Hint + + if event_id is None: + event["event_id"] = event_id = uuid.uuid4().hex + if not self._should_capture(event, hint, scope): + return None + + profile = event.pop("profile", None) + + event_opt = self._prepare_event(event, hint, scope) + if event_opt is None: + return None + + # whenever we capture an event we also check if the session needs + # to be updated based on that information. + session = scope._session if scope else None + if session: + self._update_session_from_event(session, event) + + is_transaction = event_opt.get("type") == "transaction" + is_checkin = event_opt.get("type") == "check_in" + + if ( + not is_transaction + and not is_checkin + and not self._should_sample_error(event, hint) + ): + return None + + attachments = hint.get("attachments") + + trace_context = event_opt.get("contexts", {}).get("trace") or {} + dynamic_sampling_context = trace_context.pop("dynamic_sampling_context", {}) + + headers = { + "event_id": event_opt["event_id"], + "sent_at": format_timestamp(datetime.now(timezone.utc)), + } # type: dict[str, object] - for attachment in attachments or (): - envelope.add_item(attachment.to_envelope_item()) + if dynamic_sampling_context: + headers["trace"] = dynamic_sampling_context - if self.spotlight: - self.spotlight.capture_envelope(envelope) - - if self.transport is None: - return None - - self.transport.capture_envelope(envelope) - - return event_id
+
envelope = Envelope(headers=headers) + + if is_transaction: + if isinstance(profile, Profile): + envelope.add_profile(profile.to_json(event_opt, self.options)) + envelope.add_transaction(event_opt) + elif is_checkin: + envelope.add_checkin(event_opt) + else: + envelope.add_event(event_opt) + + for attachment in attachments or (): + envelope.add_item(attachment.to_envelope_item()) + + if self.spotlight: + self.spotlight.capture_envelope(envelope) + + if self.transport is None: + return None + + self.transport.capture_envelope(envelope) + + return event_id - - def capture_session( - self, session # type: Session - ): - # type: (...) -> None - if not session.release: - logger.info("Discarded session update because of missing release") - else: - self.session_flusher.add_session(session) - -
-[docs] - def get_integration( - self, name_or_class # type: Union[str, Type[Integration]] - ): - # type: (...) -> Any - """Returns the integration for this client by name or class. - If the client does not have that integration then `None` is returned. - """ - if isinstance(name_or_class, str): - integration_name = name_or_class - elif name_or_class.identifier is not None: - integration_name = name_or_class.identifier + + def capture_session( + self, session # type: Session + ): + # type: (...) -> None + if not session.release: + logger.info("Discarded session update because of missing release") else: - raise ValueError("Integration has no name") + self.session_flusher.add_session(session) - return self.integrations.get(integration_name)
- +
if TYPE_CHECKING: + @overload + def get_integration(self, name_or_class): + # type: (str) -> Optional[Integration] + ... + + @overload + def get_integration(self, name_or_class): + # type: (type[I]) -> Optional[I] + ... + +
+[docs] + def get_integration( + self, name_or_class # type: Union[str, Type[Integration]] + ): + # type: (...) -> Optional[Integration] + """Returns the integration for this client by name or class. + If the client does not have that integration then `None` is returned. + """ + if isinstance(name_or_class, str): + integration_name = name_or_class + elif name_or_class.identifier is not None: + integration_name = name_or_class.identifier + else: + raise ValueError("Integration has no name") + + return self.integrations.get(integration_name)
+ +
[docs] - def close( - self, - timeout=None, # type: Optional[float] - callback=None, # type: Optional[Callable[[int, float], None]] - ): - # type: (...) -> None - """ - Close the client and shut down the transport. Arguments have the same - semantics as :py:meth:`Client.flush`. - """ - if self.transport is not None: - self.flush(timeout=timeout, callback=callback) - self.session_flusher.kill() - if self.metrics_aggregator is not None: - self.metrics_aggregator.kill() - if self.monitor: - self.monitor.kill() - self.transport.kill() - self.transport = None
+
def close( + self, + timeout=None, # type: Optional[float] + callback=None, # type: Optional[Callable[[int, float], None]] + ): + # type: (...) -> None + """ + Close the client and shut down the transport. Arguments have the same + semantics as :py:meth:`Client.flush`. + """ + if self.transport is not None: + self.flush(timeout=timeout, callback=callback) + self.session_flusher.kill() + if self.metrics_aggregator is not None: + self.metrics_aggregator.kill() + if self.monitor: + self.monitor.kill() + self.transport.kill() + self.transport = None - +
[docs] - def flush( - self, - timeout=None, # type: Optional[float] - callback=None, # type: Optional[Callable[[int, float], None]] - ): - # type: (...) -> None - """ - Wait for the current events to be sent. - - :param timeout: Wait for at most `timeout` seconds. If no `timeout` is provided, the `shutdown_timeout` option value is used. - - :param callback: Is invoked with the number of pending events and the configured timeout. - """ - if self.transport is not None: - if timeout is None: - timeout = self.options["shutdown_timeout"] - self.session_flusher.flush() - if self.metrics_aggregator is not None: - self.metrics_aggregator.flush() - self.transport.flush(timeout=timeout, callback=callback)
+
def flush( + self, + timeout=None, # type: Optional[float] + callback=None, # type: Optional[Callable[[int, float], None]] + ): + # type: (...) -> None + """ + Wait for the current events to be sent. + + :param timeout: Wait for at most `timeout` seconds. If no `timeout` is provided, the `shutdown_timeout` option value is used. + + :param callback: Is invoked with the number of pending events and the configured timeout. + """ + if self.transport is not None: + if timeout is None: + timeout = self.options["shutdown_timeout"] + self.session_flusher.flush() + if self.metrics_aggregator is not None: + self.metrics_aggregator.flush() + self.transport.flush(timeout=timeout, callback=callback) - - def __enter__(self): - # type: () -> _Client - return self - - def __exit__(self, exc_type, exc_value, tb): - # type: (Any, Any, Any) -> None - self.close() + + def __enter__(self): + # type: () -> _Client + return self + + def __exit__(self, exc_type, exc_value, tb): + # type: (Any, Any, Any) -> None + self.close() - - -from typing import TYPE_CHECKING - -if TYPE_CHECKING: - # Make mypy, PyCharm and other static analyzers think `get_options` is a - # type to have nicer autocompletion for params. - # - # Use `ClientConstructor` to define the argument types of `init` and - # `Dict[str, Any]` to tell static analyzers about the return type. - - class get_options(ClientConstructor, Dict[str, Any]): # noqa: N801 - pass - + + +from typing import TYPE_CHECKING + +if TYPE_CHECKING: + # Make mypy, PyCharm and other static analyzers think `get_options` is a + # type to have nicer autocompletion for params. + # + # Use `ClientConstructor` to define the argument types of `init` and + # `Dict[str, Any]` to tell static analyzers about the return type. + + class get_options(ClientConstructor, Dict[str, Any]): # noqa: N801 + pass +
[docs] - class Client(ClientConstructor, _Client): - pass
+
class Client(ClientConstructor, _Client): + pass - -else: - # Alias `get_options` for actual usage. Go through the lambda indirection - # to throw PyCharm off of the weakly typed signature (it would otherwise - # discover both the weakly typed signature of `_init` and our faked `init` - # type). - - get_options = (lambda: _get_options)() - Client = (lambda: _Client)() + +else: + # Alias `get_options` for actual usage. Go through the lambda indirection + # to throw PyCharm off of the weakly typed signature (it would otherwise + # discover both the weakly typed signature of `_init` and our faked `init` + # type). + + get_options = (lambda: _get_options)() + Client = (lambda: _Client)()