import peewee from contextvars import ContextVar db_state_default = {"closed": None, "conn": None, "ctx": None, "transactions": None} db_state = ContextVar("db_state", default=db_state_default.copy()) class PeeweeConnectionState(peewee._ConnectionState): def __init__(self, **kwargs): super().__setattr__("_state", db_state) super().__init__(**kwargs) def __setattr__(self, name, value): self._state.get()[name] = value def __getattr__(self, name): return self._state.get()[name] db = peewee.SqliteDatabase('database.db', check_same_thread=False) db._state = PeeweeConnectionState()
def trace_stack_top(trace_stack_var: ContextVar) -> Any | None: """Return the element at the top of a trace stack.""" trace_stack = trace_stack_var.get() return trace_stack[-1] if trace_stack else None
import logging from contextvars import ContextVar, Token from typing import Optional from uuid import UUID, uuid4 from aiohttp.web_log import AccessLogger logger = logging.getLogger(__name__) _request_id_ctx_var: ContextVar[str] = ContextVar("belvo_request_id", default="") def set_request_id(request_id: Optional[str] = None) -> Token: if request_id: try: UUID(request_id) except (ValueError, AttributeError): logger.exception("Received invalid request id. Using a new one.") request_id = None request_id = request_id or uuid4().hex return _request_id_ctx_var.set(request_id) def get_request_id() -> str: return _request_id_ctx_var.get() def reset_request_id(request_id: Token): _request_id_ctx_var.reset(request_id)
def __init__(self, name): self._context_var = ContextVar(name, default=None)
def trace_stack_push(trace_stack_var: ContextVar, node: Any) -> None: """Push an element to the top of a trace stack.""" if (trace_stack := trace_stack_var.get()) is None: trace_stack = [] trace_stack_var.set(trace_stack)
logger = logging.getLogger(__name__) __all__ = [ 'run', 'run_trio_task', 'run_trio', 'run_future', 'run_coroutine', 'run_asyncio', 'wrap_generator', 'run_iterator', 'TrioChildWatcher', 'TrioPolicy', ] current_loop = ContextVar('trio_aio_loop', default=None) current_policy = ContextVar('trio_aio_policy', default=None) _faked_policy = threading.local() # We can monkey-patch asyncio's get_event_loop_policy but if asyncio is # imported before Trio, the asyncio acceleration C code in 3.7+ caches # get_event_loop_policy. # Thus we always set our policy. After that, our monkeypatched # setter stores the policy in a thread-local variable to which our policy # will forward all requests when Trio is not running. class _TrioPolicy(asyncio.events.BaseDefaultEventLoopPolicy): _loop_factory = TrioEventLoop
# http://www.apache.org/licenses/LICENSE-2.0 # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. import random import asyncio import requests from contextlib import contextmanager from contextvars import ContextVar trace_stack_var = ContextVar('trace_stack', default=None) def _load_http_headers(headers): from py_zipkin.zipkin import ZipkinAttrs # pylint: disable=E0401 if not headers or "X-B3-TraceId" not in headers: return None return ZipkinAttrs( headers.get("X-B3-TraceId"), headers.get("X-B3-SpanId"), headers.get("X-B3-ParentSpanId"), headers.get("X-B3-Flags") or '0', False if headers.get("X-B3-Sampled") == '0' else True, )
import aiocontextvars from inspect import signature from contextlib import contextmanager from contextvars import ContextVar import aiohttp from aiohttp.helpers import sentinel import asyncio from funcy import compose, decorator, project, merge, cut_prefix from parsechain import Response __all__ = ['settings', 'run', 'fetch', 'fetchall', 'save'] # Used to store dynamically scoped settings, part of them are session keyword params SETTINGS = ContextVar('settings', default={}) SESSION = ContextVar('session') SESSION_PARAMS = [ p.name for p in signature(aiohttp.ClientSession).parameters.values() if p.kind == p.KEYWORD_ONLY ] def run(coro): loop = asyncio.get_event_loop() return loop.run_until_complete(with_session(coro)) @contextmanager def settings(**values): old_values = SETTINGS.get()
from datetime import datetime from contextvars import ContextVar from collections import defaultdict from nonebot.rule import Rule from nonebot.permission import Permission, USER from nonebot.typing import Type, List, Dict, Union, Callable, Optional, NoReturn from nonebot.typing import Bot, Event, Handler, Message, ArgsParser, MessageSegment from nonebot.exception import PausedException, RejectedException, FinishedException matchers: Dict[int, List[Type["Matcher"]]] = defaultdict(list) """ :类型: ``Dict[int, List[Type[Matcher]]]`` :说明: 用于存储当前所有的事件响应器 """ current_bot: ContextVar = ContextVar("current_bot") current_event: ContextVar = ContextVar("current_event") class MatcherMeta(type): def __repr__(self) -> str: return (f"<Matcher from {self.module or 'unknow'}, " # type: ignore f"type={self.type}, priority={self.priority}, " # type: ignore f"temp={self.temp}>") # type: ignore def __str__(self) -> str: return repr(self) class Matcher(metaclass=MatcherMeta):
class ProxyServer: def __init__(self, port: int = 8080, block_images: bool = False, cfg=None): self.connection = ContextVar("connection") self.block_images = block_images self.port = port self._spent_data = {} if cfg is not None: if isinstance(cfg, dict): self._cfg = cfg else: raise ValueError(f"Config should be {dict.__name__} object") for rsc in chain(cfg["limited"], cfg["black-list"]): self._spent_data[rsc] = 0 self.context_token = None async def run(self): """ Launch async proxy-server at specified host and port. """ srv = await asyncio.start_server(self._handle_connection, LOCALHOST, self.port) addr = srv.sockets[0].getsockname() LOGGER.info(START_SERVER_MSG.format(app_address=addr)) async with srv: await srv.serve_forever() async def _handle_connection(self, client_reader: StreamReader, client_writer: StreamWriter) -> None: """ Handle every client response. Called whenever a new connection is established. """ try: raw_request = await client_reader.read(CHUNK_SIZE) print(raw_request) await client_writer.drain() if not raw_request: return pr = ProxyRequest(raw_request, self._cfg) LOGGER.info(f"{pr.method:<{len('CONNECT')}} " f"{pr.abs_url}") try: server_reader, server_writer = await asyncio.open_connection( pr.hostname, pr.port) except OSError: LOGGER.info( CONNECTION_REFUSED_MSG.format(method=pr.method, url=pr.abs_url)) return client_endpoint = Endpoint(client_reader, client_writer) server_endpoint = Endpoint(server_reader, server_writer) conn = Connection(client_endpoint, server_endpoint, pr, self.block_images) self.context_token = self.connection.set(conn) if self.block_images and pr.is_image_request: await self.connection.get().reset() return if pr.scheme is HTTPScheme.HTTPS: await self._handle_https() else: await self._handle_http() except Exception as e: if isinstance(e, ConnectionResetError): LOGGER.info(CONNECTION_CLOSED_MSG.format(url=pr.abs_url)) else: LOGGER.exception(e) asyncio.get_event_loop().stop() if self.context_token is not None: self.connection.reset(self.context_token) async def _handle_http(self) -> None: """ Send HTTP request and then forwards the following HTTP requests. """ conn = self.connection.get() LOGGER.debug( HANDLING_HTTP_REQUEST_MSG.format(method=conn.pr.method, url=conn.pr.abs_url)) await conn.server.write_and_drain(conn.pr.raw) await asyncio.gather(conn.forward_to_client(self._spent_data), conn.forward_to_server()) async def _handle_https(self) -> None: """ Handles https connection by making HTTP tunnel. """ conn = self.connection.get() hostname = conn.pr.hostname LOGGER.debug(HANDLING_HTTPS_CONNECTION_MSG.format(url=hostname)) rsc = conn.pr.restriction if rsc: if self._spent_data[rsc.initiator] >= rsc.data_limit: await conn.reset() return await conn.client.write_and_drain(CONNECTION_ESTABLISHED_HTTP_MSG) LOGGER.debug(CONNECTION_ESTABLISHED_MSG.format(url=conn.pr.abs_url)) await asyncio.gather(conn.forward_to_server(), conn.forward_to_client(self._spent_data))
Ret = Union[None, Awaitable[None]] OptAwait = TypeVar('OptAwait', None, Awaitable[None]) ctxCensor = Callable[[QQbot, Context], bool] ctxFunc = Callable[[QQbot, Context], Ret] ctxFuncGen = Callable[[QQbot, Context], OptAwait] ctxFuncWrap = Callable[[ctxFuncGen], ctxFuncGen] eventFunc = Callable[[QQbot, Event], Ret] eventFuncGen = Callable[[QQbot, Event], OptAwait] eventFuncWrap = Callable[[eventFuncGen], eventFuncGen] logger = logging.getLogger(__name__) contextStore: ContextVar[Context] = ContextVar('context') class SolveUnit(BotBase): _ctxLst: list[ctxFunc] _eventLst: dict[Type[Event], list[eventFunc]] def __new__(cls, *args, **kwargs) -> Any: obj = super().__new__(cls) obj._ctxLst = [] obj._eventLst = {} return obj def addFunction(self, check: Optional[ctxCensor] = None) -> ctxFuncWrap: def wrapper(func: ctxFuncGen) -> ctxFuncGen: def inner(bot: QQbot, context: Context) -> Ret:
from pymongo.database import Database from pymongo.cursor import Cursor from pymongo.errors import DuplicateKeyError import marshmallow as ma from ..builder import BaseBuilder from ..instance import Instance from ..document import DocumentImplementation from ..data_objects import Reference from ..exceptions import NotCreatedError, UpdateError, DeleteError, NoneReferenceError from ..fields import ReferenceField, ListField, EmbeddedField from ..query_mapper import map_query from .tools import cook_find_filter SESSION = ContextVar("session", default=None) # pymongo.Cursor defines __del__ method, hence mongomock's WrappedCursor should # not inherit from this class otherwise garbage collection will crash... class BaseWrappedCursor: __slots__ = ('raw_cursor', 'document_cls') def __init__(self, document_cls, cursor, *args, **kwargs): # Such a cunning plan my lord ! # We inherit from Cursor but don't call its __init__ because # we act as a proxy to the underlying raw_cursor WrappedCursor.raw_cursor.__set__(self, cursor) WrappedCursor.document_cls.__set__(self, document_cls)
resource = request.match_info.route.resource # available only in aiohttp >= 3.3.1 if getattr(resource, 'canonical', None) is not None: route = request.match_info.route.resource.canonical span.tag(HTTP_ROUTE, route) _set_remote_endpoint(span, request) PY37 = sys.version_info >= (3, 7) if PY37: from contextvars import ContextVar OptTraceVar = ContextVar[Optional[TraceContext]] zipkin_context = ContextVar('zipkin_context', default=None) # type: OptTraceVar @contextmanager def set_context_value( context_var: OptTraceVar, value: TraceContext) -> Generator[OptTraceVar, None, None]: token = context_var.set(value) try: yield context_var finally: context_var.reset(token) def middleware_maker(skip_routes: Optional[AbstractRoute] = None, tracer_key: str = APP_AIOZIPKIN_KEY, request_key: str = REQUEST_AIOZIPKIN_KEY) -> Middleware:
import threading from functools import wraps from typing import Callable, Optional, Union, List from sentry_sdk import start_span, capture_exception as sentry_capture_exception, set_tag from sentry_sdk.tracing import Span from contextvars import ContextVar span: ContextVar[Span] = ContextVar('span') def instrument_span(op: str, description: Optional[Union[str, Callable]] = None, force_new_span: bool = False, **instrument_kwargs): def wrapper(wrapped): @wraps(wrapped) def with_instrumentation(*args, **kwargs): try: parent_span = span.get() except LookupError: parent_span = None if parent_span and not force_new_span: _span = parent_span.start_child( description=description(*args, **kwargs) if callable(description) else description, **instrument_kwargs, ) else:
return datetime def process_result_value(self, value, _): if value is not None: value = timezone.make_aware(value, timezone=pytz.utc) return value def process_bind_param(self, value, _): if value is not None and timezone.is_aware(value): value = timezone.make_naive(value, timezone=pytz.utc) return value context_auto_commit = ContextVar('context_auto_commit', default=True) def get_base_model(db): class BaseModel(db.Model): __abstract__ = True __asdict_include_hybrid_properties__ = False created_time = Column(DateTime, default=datetime.utcnow) updated_time = Column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow) is_active = Column(Boolean, default=True) @classmethod def exists(cls, **attrs):
class I18nMiddleware(BaseMiddleware): """ I18n middleware based on gettext util >>> dp = Dispatcher(bot) >>> i18n = I18nMiddleware(DOMAIN, LOCALES_DIR) >>> dp.middleware.setup(i18n) and then >>> _ = i18n.gettext or >>> _ = i18n = I18nMiddleware(DOMAIN_NAME, LOCALES_DIR) """ ctx_locale = ContextVar('ctx_user_locale', default=None) def __init__(self, domain, path=None, default='en'): """ :param domain: domain :param path: path where located all *.mo files :param default: default locale name """ super(I18nMiddleware, self).__init__() if path is None: path = os.path.join(os.getcwd(), 'locales') self.domain = domain self.path = path self.default = default self.locales = self.find_locales() def find_locales(self) -> Dict[str, gettext.GNUTranslations]: """ Load all compiled locales from path :return: dict with locales """ translations = {} for name in os.listdir(self.path): if not os.path.isdir(os.path.join(self.path, name)): continue mo_path = os.path.join(self.path, name, 'LC_MESSAGES', self.domain + '.mo') if os.path.exists(mo_path): with open(mo_path, 'rb') as fp: translations[name] = gettext.GNUTranslations(fp) elif os.path.exists(mo_path[:-2] + 'po'): raise RuntimeError( f"Found locale '{name} but this language is not compiled!") return translations def reload(self): """ Hot reload locles """ self.locales = self.find_locales() @property def available_locales(self) -> Tuple[str]: """ list of loaded locales :return: """ return tuple(self.locales.keys()) def __call__(self, singular, plural=None, n=1, locale=None) -> str: return self.gettext(singular, plural, n, locale) def gettext(self, singular, plural=None, n=1, locale=None) -> str: """ Get text :param singular: :param plural: :param n: :param locale: :return: """ if locale is None: locale = self.ctx_locale.get() if locale not in self.locales: if n is 1: return singular else: return plural translator = self.locales[locale] if plural is None: return translator.gettext(singular) else: return translator.ngettext(singular, plural, n) def lazy_gettext(self, singular, plural=None, n=1, locale=None, enable_cache=True) -> LazyProxy: """ Lazy get text :param singular: :param plural: :param n: :param locale: :param enable_cache: :return: """ return LazyProxy(self.gettext, singular, plural, n, locale, enable_cache=enable_cache) # noinspection PyMethodMayBeStatic,PyUnusedLocal async def get_user_locale(self, action: str, args: Tuple[Any]) -> str: """ User locale getter You can override the method if you want to use different way of getting user language. :param action: event name :param args: event arguments :return: locale name """ user: types.User = types.User.get_current() locale: Locale = user.locale if locale: *_, data = args language = data['locale'] = locale.language return language async def trigger(self, action, args): """ Event trigger :param action: event name :param args: event arguments :return: """ if 'update' not in action \ and 'error' not in action \ and action.startswith('pre_process'): locale = await self.get_user_locale(action, args) self.ctx_locale.set(locale) return True
from contextvars import ContextVar from opentelemetry.trace import get_tracer, SpanKind from opentelemetry.propagators import inject from grpclib.client import Channel from grpclib.events import listen, SendRequest, RecvTrailingMetadata from harness import grpc_pb2 from harness.wires.base import Wire _client_span = ContextVar("client_span") async def _send_request(event: SendRequest) -> None: tracer = get_tracer(__name__) span = tracer.start_span( event.method_name, kind=SpanKind.CLIENT, attributes={ "component": "grpc", "grpc.method": event.method_name }, ) _client_span.set(span) inject(type(event.metadata).__setitem__, event.metadata) async def _recv_trailing_metadata(event: RecvTrailingMetadata) -> None: _client_span.get().end()
H.div("Thread ", H.strong(word), reason), type="info", ) thread = KillableThread(target=run, daemon=True) if not self.words: self.words.append(f"t{next(self.count)}") word = self.words.pop() thread.start() return word, thread threads = NamedThreads() _current_session = ContextVar("current_session", default=None) _current_print_session = ContextVar("current_print_session", default=None) _current_evalid = ContextVar("current_evalid", default=None) def current_session(): return _current_session.get() def current_print_session(): return _current_print_session.get() @contextmanager def new_evalid(): token = _current_evalid.set(next(_c))
def __init__(self) -> None: self._current_context = ContextVar(self._CONTEXT_KEY, default=Context())
from warnings import warn from weakref import WeakValueDictionary from pydantic import BaseModel as PydanticBaseModel from pydantic.error_wrappers import ErrorWrapper, ValidationError from pydantic.fields import PrivateAttr from hydrolib.core.io.base import DummmyParser, DummySerializer from hydrolib.core.utils import to_key logger = logging.getLogger(__name__) # We use ContextVars to keep a reference to the folder # we're currently parsing files in. In the future # we could move to https://github.com/samuelcolvin/pydantic/issues/1549 context_file_loading: ContextVar["FileLoadContext"] = ContextVar( "file_loading") class BaseModel(PydanticBaseModel): class Config: arbitrary_types_allowed = True validate_assignment = True use_enum_values = True extra = "forbid" # will throw errors so we can fix our models allow_population_by_field_name = True alias_generator = to_key def __init__(self, **data: Any) -> None: """Initializes a BaseModel with the provided data. Raises:
) from uuid import ( UUID, ) from minos.common import ( AvroDataEncoder, ) from ..exceptions import ( MinosException, NotHasContentException, NotHasParamsException, ) REQUEST_USER_CONTEXT_VAR: Final[ContextVar[Optional[UUID]]] = ContextVar("user", default=None) class Request(ABC): """Request interface.""" @property @abstractmethod def user(self) -> Optional[UUID]: """ Returns the UUID of the user making the Request. """ raise NotImplementedError async def content(self, **kwargs) -> Any: """Get the request content.
# Compatibility with Python 2.6 from distutils.sysconfig import get_python_lib import warnings from bugsnag.sessiontracker import SessionMiddleware from bugsnag.middleware import DefaultMiddleware, MiddlewareStack from bugsnag.utils import (fully_qualified_class_name, validate_str_setter, validate_bool_setter, validate_iterable_setter, validate_required_str_setter) from bugsnag.delivery import (create_default_delivery, DEFAULT_ENDPOINT, DEFAULT_SESSIONS_ENDPOINT) from bugsnag.uwsgi import warn_if_running_uwsgi_without_threads try: from contextvars import ContextVar _request_info = ContextVar('bugsnag-request', default=None) # type: ignore except ImportError: from bugsnag.utils import ThreadContextVar _request_info = ThreadContextVar( 'bugsnag-request', default=None) # type: ignore # noqa: E501 __all__ = ('Configuration', 'RequestConfiguration') class _BaseConfiguration(object): def get(self, name, overrides=None): """ Get a single configuration option, using values from overrides first if they exist. """ if overrides:
"item_id": item_id, "run_id": str(self._child_run_id), } if self._variables: result["changed_variables"] = self._variables if self._error is not None: result["error"] = str(self._error) if self._result is not None: result["result"] = self._result return result # Context variables for tracing # Current trace trace_cv: ContextVar[dict[str, deque[TraceElement]] | None] = ContextVar( "trace_cv", default=None ) # Stack of TraceElements trace_stack_cv: ContextVar[list[TraceElement] | None] = ContextVar( "trace_stack_cv", default=None ) # Current location in config tree trace_path_stack_cv: ContextVar[list[str] | None] = ContextVar( "trace_path_stack_cv", default=None ) # Copy of last variables variables_cv: ContextVar[Any | None] = ContextVar("variables_cv", default=None) # (domain.item_id, Run ID) trace_id_cv: ContextVar[tuple[str, str] | None] = ContextVar( "trace_id_cv", default=None )
self.scan_interval, ) return async with self._process_updates: tasks = [] for entity in self.entities.values(): if not entity.should_poll: continue tasks.append(entity.async_update_ha_state(True)) if tasks: await asyncio.gather(*tasks) current_platform: ContextVar[EntityPlatform | None] = ContextVar( "current_platform", default=None) @callback def async_get_platforms(hass: HomeAssistant, integration_name: str) -> list[EntityPlatform]: """Find existing platforms.""" if (DATA_ENTITY_PLATFORM not in hass.data or integration_name not in hass.data[DATA_ENTITY_PLATFORM]): return [] platforms: list[EntityPlatform] = hass.data[DATA_ENTITY_PLATFORM][ integration_name] return platforms
def trace_stack_pop(trace_stack_var: ContextVar) -> None: """Remove the top element from a trace stack.""" trace_stack = trace_stack_var.get() trace_stack.pop()
def test_context_assignment_while_running(self): id_var = ContextVar("id", default=None) def target(): self.assertIsNone(id_var.get()) self.assertIsNone(gr.gr_context) # Context is created on first use id_var.set(1) self.assertIsInstance(gr.gr_context, Context) self.assertEqual(id_var.get(), 1) self.assertEqual(gr.gr_context[id_var], 1) # Clearing the context makes it get re-created as another # empty context when next used old_context = gr.gr_context gr.gr_context = None # assign None while running self.assertIsNone(id_var.get()) self.assertIsNone(gr.gr_context) id_var.set(2) self.assertIsInstance(gr.gr_context, Context) self.assertEqual(id_var.get(), 2) self.assertEqual(gr.gr_context[id_var], 2) new_context = gr.gr_context getcurrent().parent.switch((old_context, new_context)) # parent switches us back to old_context self.assertEqual(id_var.get(), 1) gr.gr_context = new_context # assign non-None while running self.assertEqual(id_var.get(), 2) getcurrent().parent.switch() # parent switches us back to no context self.assertIsNone(id_var.get()) self.assertIsNone(gr.gr_context) gr.gr_context = old_context self.assertEqual(id_var.get(), 1) getcurrent().parent.switch() # parent switches us back to no context self.assertIsNone(id_var.get()) self.assertIsNone(gr.gr_context) gr = greenlet(target) with self.assertRaisesRegex(AttributeError, "can't delete attr"): del gr.gr_context self.assertIsNone(gr.gr_context) old_context, new_context = gr.switch() self.assertIs(new_context, gr.gr_context) self.assertEqual(old_context[id_var], 1) self.assertEqual(new_context[id_var], 2) self.assertEqual(new_context.run(id_var.get), 2) gr.gr_context = old_context # assign non-None while suspended gr.switch() self.assertIs(gr.gr_context, new_context) gr.gr_context = None # assign None while suspended gr.switch() self.assertIs(gr.gr_context, old_context) gr.gr_context = None gr.switch() self.assertIsNone(gr.gr_context) # Make sure there are no reference leaks gr = None gc.collect() self.assertEqual(sys.getrefcount(old_context), 2) self.assertEqual(sys.getrefcount(new_context), 2)
SkippedException, FinishedException, RejectedException, ) if TYPE_CHECKING: from nonebot.plugin import Plugin T = TypeVar("T") matchers: Dict[int, List[Type["Matcher"]]] = defaultdict(list) """ :类型: ``Dict[int, List[Type[Matcher]]]`` :说明: 用于存储当前所有的事件响应器 """ current_bot: ContextVar[Bot] = ContextVar("current_bot") current_event: ContextVar[Event] = ContextVar("current_event") current_matcher: ContextVar["Matcher"] = ContextVar("current_matcher") current_handler: ContextVar[Dependent] = ContextVar("current_handler") class MatcherMeta(type): if TYPE_CHECKING: module: Optional[str] plugin_name: Optional[str] module_name: Optional[str] module_prefix: Optional[str] type: str rule: Rule permission: Permission handlers: List[T_Handler]
warnings.warn( f"{name} is deprecated and will be removed in a future release. " f"Please use {use_instead} instead.", category=FutureWarning, stacklevel=2, ) return import_term(use_instead) else: raise AttributeError(f"module {__name__} has no attribute {name}") # Used internally by recursive_to_dict to stop infinite recursion. If an object has # already been encountered, a string representation will be returned instead. This is # necessary since we have multiple cyclic referencing data structures. _recursive_to_dict_seen: ContextVar[set[int]] = ContextVar("_recursive_to_dict_seen") def recursive_to_dict(obj: AnyType, *, exclude: Container[str] = ()) -> AnyType: """Recursively convert arbitrary Python objects to a JSON-serializable representation. This is intended for debugging purposes only and calls ``_to_dict`` methods on encountered objects, if available. Parameters ---------- exclude: A list of attribute names to be excluded from the dump. This will be forwarded to the objects ``_to_dict`` methods and these methods are required to accept this parameter. """ if isinstance(obj, (int, float, bool, str)) or obj is None:
class SentryReporter: """SentryReporter designed for sending reports to the Sentry server from a Tribler Client. """ scrubber = None last_event = None ignored_exceptions = [KeyboardInterrupt, SystemExit] # more info about how SentryReporter choose a strategy see in # SentryReporter.get_actual_strategy() global_strategy = SentryStrategy.SEND_ALLOWED_WITH_CONFIRMATION thread_strategy = ContextVar('context_strategy', default=None) _sentry_logger_name = 'SentryReporter' _logger = logging.getLogger(_sentry_logger_name) @staticmethod def init(sentry_url='', release_version='', scrubber=None, strategy=SentryStrategy.SEND_ALLOWED_WITH_CONFIRMATION): """Initialization. This method should be called in each process that uses SentryReporter. Args: sentry_url: URL for Sentry server. If it is empty then Sentry's sending mechanism will not be initialized. scrubber: a class that will be used for scrubbing sending events. Only a single method should be implemented in the class: ``` def scrub_event(self, event): pass ``` release_version: string that represents a release version. See Also: https://docs.sentry.io/platforms/python/configuration/releases/ strategy: a Sentry strategy for sending events (see class Strategy for more information) Returns: Sentry Guard. """ SentryReporter._logger.debug(f"Init: {sentry_url}") SentryReporter.scrubber = scrubber SentryReporter.global_strategy = strategy rv = sentry_sdk.init( sentry_url, release=release_version, # https://docs.sentry.io/platforms/python/configuration/integrations/ integrations=[ LoggingIntegration( level=logging. INFO, # Capture info and above as breadcrumbs event_level=None, # Send no errors as events ), ThreadingIntegration(propagate_hub=True), ], before_send=SentryReporter._before_send, ) ignore_logger(SentryReporter._sentry_logger_name) return rv @staticmethod def ignore_logger(logger_name): SentryReporter._logger.debug(f"Ignore logger: {logger_name}") ignore_logger(logger_name) @staticmethod def add_breadcrumb(message='', category='', level='info', **kwargs): """Adds a breadcrumb for current Sentry client. It is necessary to specify a message, a category and a level to make this breadcrumb visible in Sentry server. Args: **kwargs: named arguments that will be added to Sentry event as well """ crumb = {'message': message, 'category': category, 'level': level} SentryReporter._logger.debug(f"Add the breadcrumb: {crumb}") return sentry_sdk.add_breadcrumb(crumb, **kwargs) @staticmethod def send_event(event=None, post_data=None, sys_info=None, additional_tags=None): """Send the event to the Sentry server This method 1. Enable Sentry's sending mechanism. 2. Extend sending event by the information from post_data. 3. Send the event. 4. Disables Sentry's sending mechanism. Scrubbing the information will be performed in the `_before_send` method. During the execution of this method, all unhandled exceptions that will be raised, will be sent to Sentry automatically. Args: event: event to send. It should be taken from SentryReporter at post_data: dictionary made by the feedbackdialog.py previous stages of executing. sys_info: dictionary made by the feedbackdialog.py additional_tags: tags that will be added to the event Returns: Event that was sent to Sentry server """ SentryReporter._logger.info(f"Send: {post_data}, {event}") if event is None: return event post_data = post_data or dict() sys_info = sys_info or dict() additional_tags = additional_tags or dict() if CONTEXTS not in event: event[CONTEXTS] = {} if TAGS not in event: event[TAGS] = {} event[CONTEXTS][REPORTER] = {} # tags tags = event[TAGS] tags['version'] = get_value(post_data, 'version') tags['machine'] = get_value(post_data, 'machine') tags['os'] = get_value(post_data, 'os') tags['platform'] = get_first_item(get_value(sys_info, 'platform')) tags[f'{PLATFORM_DETAILS}'] = get_first_item( get_value(sys_info, PLATFORM_DETAILS)) tags.update(additional_tags) # context context = event[CONTEXTS] reporter = context[REPORTER] version = get_value(post_data, 'version') context['browser'] = {'version': version, 'name': 'Tribler'} stacktrace_parts = parse_stacktrace(get_value(post_data, 'stack')) reporter[STACKTRACE] = next(stacktrace_parts, []) reporter[f'{STACKTRACE}_extra'] = next(stacktrace_parts, []) reporter[f'{STACKTRACE}_context'] = next(stacktrace_parts, []) reporter['comments'] = get_value(post_data, 'comments') reporter[OS_ENVIRON] = parse_os_environ(get_value( sys_info, OS_ENVIRON)) delete_item(sys_info, OS_ENVIRON) reporter['events'] = extract_dict(sys_info, r'^(event|request)') reporter[SYSINFO] = { key: sys_info[key] for key in sys_info if key not in reporter['events'] } with this_sentry_strategy(SentryStrategy.SEND_ALLOWED): sentry_sdk.capture_event(event) return event @staticmethod def get_confirmation(exception): """Get confirmation on sending exception to the Team. There are two message boxes, that will be triggered: 1. Message box with the error_text 2. Message box with confirmation about sending this report to the Tribler team. Args: exception: exception to be sent. """ # Prevent importing PyQt globally in tribler-common module. # pylint: disable=import-outside-toplevel try: from PyQt5.QtWidgets import QApplication, QMessageBox except ImportError: SentryReporter._logger.debug( "PyQt5 is not available. User confirmation is not possible.") return False SentryReporter._logger.debug(f"Get confirmation: {exception}") _ = QApplication(sys.argv) messagebox = QMessageBox(icon=QMessageBox.Critical, text=f'{exception}.') messagebox.setWindowTitle("Error") messagebox.exec() messagebox = QMessageBox( icon=QMessageBox.Question, text='Do you want to send this crash report to the Tribler team? ' 'We anonymize all your data, who you are and what you downloaded.', ) messagebox.setWindowTitle("Error") messagebox.setStandardButtons(QMessageBox.Yes | QMessageBox.No) return messagebox.exec() == QMessageBox.Yes @staticmethod def capture_exception(exception): SentryReporter._logger.info(f"Capture exception: {exception}") sentry_sdk.capture_exception(exception) @staticmethod def event_from_exception(exception): """This function format the exception by passing it through sentry Args: exception: an exception that will be passed to `sentry_sdk.capture_exception(exception)` Returns: the event that has been saved in `_before_send` method """ SentryReporter._logger.info(f"Event from exception: {exception}") if not exception: return exception with this_sentry_strategy(SentryStrategy.SEND_SUPPRESSED): sentry_sdk.capture_exception(exception) return SentryReporter.last_event @staticmethod def set_user(user_id): """Set the user to identify the event on a Sentry server The algorithm is the following: 1. Calculate hash from `user_id`. 2. Generate fake user, based on the hash. No real `user_id` will be used in Sentry. Args: user_id: Real user id. Returns: Generated user (dictionary: {id, username}). """ # calculate hash to keep real `user_id` in secret user_id_hash = md5(user_id).hexdigest() SentryReporter._logger.debug(f"Set user: {user_id_hash}") Faker.seed(user_id_hash) user_name = Faker().name() user = {'id': user_id_hash, 'username': user_name} sentry_sdk.set_user(user) return user @staticmethod def get_actual_strategy(): """This method is used to determine actual strategy. Strategy can be global: SentryReporter.strategy and local: SentryReporter._context_strategy. Returns: the local strategy if it is defined, the global strategy otherwise """ strategy = SentryReporter.thread_strategy.get() return strategy if strategy else SentryReporter.global_strategy @staticmethod def _before_send(event, hint): """The method that is called before each send. Both allowed and disallowed. The algorithm: 1. If sending is allowed, then scrub the event and send. 2. If sending is disallowed, then store the event in `SentryReporter.last_event` Args: event: event that generated by Sentry hint: root exception (can be used in some cases) Returns: The event, prepared for sending, or `None`, if sending is suppressed. """ if not event: return event # trying to get context-depending strategy first strategy = SentryReporter.get_actual_strategy() SentryReporter._logger.info(f"Before send strategy: {strategy}") exc_info = get_value(hint, 'exc_info') error_type = get_first_item(exc_info) if error_type in SentryReporter.ignored_exceptions: SentryReporter._logger.debug( f"Exception is in ignored: {hint}. Skipped.") return None if strategy == SentryStrategy.SEND_SUPPRESSED: SentryReporter._logger.debug( "Suppress sending. Storing the event.") SentryReporter.last_event = event return None if strategy == SentryStrategy.SEND_ALLOWED_WITH_CONFIRMATION: SentryReporter._logger.debug("Request confirmation.") if not SentryReporter.get_confirmation(hint): return None # clean up the event SentryReporter._logger.debug( f"Clean up the event with scrubber: {SentryReporter.scrubber}") if SentryReporter.scrubber: event = SentryReporter.scrubber.scrub_event(event) return event
""" context var for jinja2 templates path """ from contextvars import ContextVar env_var: ContextVar[str] = ContextVar("var")