From 84cfff9a8f70ab23643174fc07b2326a5eaf5d9c Mon Sep 17 00:00:00 2001 From: Maxwell Mapako Date: Tue, 14 May 2024 21:07:32 +0200 Subject: [PATCH] fix: update deprecated SDK references and remove unused resources (#129) --- .env.defaults | 1 - app/settings/common.py | 18 +----- config/data/repositories.py | 10 +--- config/data/schemas.py | 2 - config/data/sources.py | 10 ++-- config/domain/usecases.py | 6 +- config/graphql/resolvers.py | 7 ++- core/middleware.py | 46 +++------------ core/mixin.py | 48 +++++++++++++--- core/models.py | 57 +++++++++++++++---- core/tests.py | 33 +++++++---- core/utils.py | 62 +++++++++++++++++--- di/containers.py | 2 +- poetry.lock | 110 ++++++++++++++++++++++++++++++------ pyproject.toml | 2 +- 15 files changed, 281 insertions(+), 133 deletions(-) diff --git a/.env.defaults b/.env.defaults index ab61eeb..9c935cd 100644 --- a/.env.defaults +++ b/.env.defaults @@ -14,7 +14,6 @@ GROWTH_BOOK_HOST=GROWTH_BOOK_HOST GROWTH_BOOK_KEY=GROWTH_BOOK_KEY GROWTH_BOOK_TTL=600 EDGE_HOST=http://localhost:8888 -ROLLBAR_TOKEN=ROLLBAR_TOKEN LOGTAIL_SOURCE_TOKEN=LOGTAIL_SOURCE_TOKEN DJANGO_Q_NAME=queue-service DJANGO_Q_ORM=default diff --git a/app/settings/common.py b/app/settings/common.py index 710e6cf..00052bd 100644 --- a/app/settings/common.py +++ b/app/settings/common.py @@ -78,7 +78,6 @@ def __get_base_dir() -> str: "corsheaders.middleware.CorsMiddleware", "core.middleware.HeaderMiddleware", "core.middleware.FeatureFlagMiddleware", - 'core.middleware.CustomRollbarNotifierMiddleware', ] ROOT_URLCONF = "app.urls" @@ -184,15 +183,9 @@ def __get_base_dir() -> str: "class": "logging.StreamHandler", "formatter": "standard", }, - "rollbar": { - "level": "WARNING", - "filters": ["require_debug_false"], - "access_token": config('ROLLBAR_TOKEN', cast=str), - "environment": "development" if DEBUG else "production", - "class": "rollbar.logger.RollbarHandler" - }, "logtail": { "level": "INFO", + "filters": ["require_debug_false"], "class": "logtail.LogtailHandler", "formatter": "simple", "source_token": config("LOGTAIL_SOURCE_TOKEN", cast=str) @@ -204,7 +197,7 @@ def __get_base_dir() -> str: "propagate": True, }, "root": { - "handlers": ["rollbar", "logtail"], + "handlers": ["logtail"], "propagate": True, }, }, @@ -216,13 +209,6 @@ def __get_base_dir() -> str: "ttl": config("GROWTH_BOOK_TTL", cast=int), } -ROLLBAR = { - 'access_token': config("ROLLBAR_TOKEN", cast=str), - 'environment': 'development' if DEBUG else 'production', - 'code_version': '1.0', - 'root': BASE_DIR, -} - ON_THE_EDGE = { "host": config("EDGE_HOST", cast=str) } diff --git a/config/data/repositories.py b/config/data/repositories.py index 5fcdea1..2cd889a 100644 --- a/config/data/repositories.py +++ b/config/data/repositories.py @@ -1,15 +1,10 @@ from json import JSONDecodeError -from typing import Optional, Any from uplink import Consumer -from core.errors import NoDataError from core.repositories import DataRepository -from .schemas import ConfigurationSchema from ..data.sources import RemoteSource -from ..domain.entities import (ConfigurationModel, SettingsModel, ImageModel, NavigationModel, - NavigationGroupModel, GenreModel) -from pyxtension.streams import stream +from ..domain.entities import (ConfigurationModel) class Repository(DataRepository): @@ -20,7 +15,8 @@ def __init__(self, remote_source: Consumer) -> None: def invoke(self, **kwargs) -> ConfigurationModel: try: - data = self._remote_source.get_config() + headers = kwargs.get('headers') + data = self._remote_source.get_config(headers) return data except JSONDecodeError as e: self._logger.error(f"Malformed response with error message `{e.doc}`", exc_info=e) diff --git a/config/data/schemas.py b/config/data/schemas.py index 0e7fd2a..ce372f7 100644 --- a/config/data/schemas.py +++ b/config/data/schemas.py @@ -1,5 +1,3 @@ -from typing import Any - from marshmallow import fields, post_load from config.domain.entities import ConfigurationModel diff --git a/config/data/sources.py b/config/data/sources.py index 08d2ab2..e08bf7e 100644 --- a/config/data/sources.py +++ b/config/data/sources.py @@ -1,16 +1,16 @@ from marshmallow import EXCLUDE -from uplink import get, timeout, retry, ratelimit, Consumer +from uplink import get, timeout, retry, ratelimit, Consumer, HeaderMap from core.decorators import raise_api_error from core import __TIME_OUT__, __MAX_ATTEMPTS__, __RATE_LIMIT_CALLS__, __RATE_LIMIT_PERIOD_CALLS__ from ..data.schemas import ConfigurationSchema -@timeout(seconds=__TIME_OUT__) +@timeout(seconds=5) @retry( - max_attempts=__MAX_ATTEMPTS__, + max_attempts=3, when=retry.when.raises(Exception), - stop=retry.stop.after_attempt(__MAX_ATTEMPTS__) | retry.stop.after_delay(__RATE_LIMIT_PERIOD_CALLS__), + stop=retry.stop.after_attempt(3) | retry.stop.after_delay(2), backoff=retry.backoff.jittered(multiplier=0.5) ) @ratelimit( @@ -21,7 +21,7 @@ class RemoteSource(Consumer): @raise_api_error @get("config") - def get_config(self) -> ConfigurationSchema(unknown=EXCLUDE): + def get_config(self, headers: HeaderMap) -> ConfigurationSchema(unknown=EXCLUDE): """ :return: ConfigurationSchema """ diff --git a/config/domain/usecases.py b/config/domain/usecases.py index 8767982..b444e63 100644 --- a/config/domain/usecases.py +++ b/config/domain/usecases.py @@ -1,4 +1,4 @@ -from typing import Any, Optional +from typing import Optional, Mapping from .entities import ConfigurationModel from core.usecases import CommonUseCase @@ -6,9 +6,9 @@ class ConfigUseCase(CommonUseCase): - def fetch_configuration(self) -> Optional[ConfigurationModel]: + def fetch_configuration(self, headers: Mapping) -> Optional[ConfigurationModel]: try: - data = self._repository.invoke() + data = self._repository.invoke(headers=headers) return data except Exception as e: self._logger.error(f"Uncaught exception", exc_info=e) diff --git a/config/graphql/resolvers.py b/config/graphql/resolvers.py index bb06326..62df1a8 100644 --- a/config/graphql/resolvers.py +++ b/config/graphql/resolvers.py @@ -1,8 +1,9 @@ -from typing import Optional +from typing import Optional, Dict # noinspection PyPackageRequirements from graphql import GraphQLResolveInfo +from core.utils import get_forwarded_headers from ..di.containers import UseCaseContainer from ..domain.entities import ConfigurationModel from ..domain.usecases import ConfigUseCase @@ -19,6 +20,6 @@ def resolve_config( :return: Instance of Config """ use_case: ConfigUseCase = use_case_provider() - enabled_analytics = info.context.feature.isOn("enable-analytics") - result = use_case.fetch_configuration() + forwarded_headers = get_forwarded_headers(info.context) + result = use_case.fetch_configuration(forwarded_headers) return result diff --git a/core/middleware.py b/core/middleware.py index a2ced50..de971e0 100644 --- a/core/middleware.py +++ b/core/middleware.py @@ -1,12 +1,12 @@ +import logging from typing import Optional, Dict from django.http import HttpRequest, JsonResponse, HttpResponse from django.utils.deprecation import MiddlewareMixin -from growthbook import GrowthBook -from rollbar.contrib.django.middleware import RollbarNotifierMiddleware from .mixin import GrowthBookMixin, LoggerMixin from .models import ContextHeader, Application +from .utils import UAParser class FeatureFlagMiddleware(MiddlewareMixin, GrowthBookMixin): @@ -32,14 +32,13 @@ def process_request( request: HttpRequest, ) -> Optional[HttpResponse]: headers = request.META + ua_parser = UAParser(user_agent=headers.get('HTTP_USER_AGENT')) request.context_header = ContextHeader( authorization=headers.get('HTTP_AUTHORIZATION'), accepts=headers.get('HTTP_ACCEPT'), - agent=headers.get('HTTP_USER_AGENT'), - contentType=headers.get('CONTENT_TYPE'), - acceptEncoding=headers.get('HTTP_ACCEPT_ENCODING'), - language=headers.get('HTTP_ACCEPT_LANGUAGE'), + content_type=headers.get('CONTENT_TYPE'), + accept_encoding=headers.get('HTTP_ACCEPT_ENCODING'), application=Application( locale=headers.get('HTTP_X_APP_LOCALE'), version=headers.get('HTTP_X_APP_VERSION'), @@ -47,48 +46,17 @@ def process_request( code=headers.get('HTTP_X_APP_CODE'), label=headers.get('HTTP_X_APP_NAME'), buildType=headers.get('HTTP_X_APP_BUILD_TYPE'), - ) + ), + user_agent_info=ua_parser.get_result(), ) enforced = [ 'HTTP_HOST', 'HTTP_ACCEPT', 'HTTP_ACCEPT_ENCODING', - 'HTTP_ACCEPT_LANGUAGE', 'HTTP_USER_AGENT', ] for header in enforced: if header not in headers: return self.__fail(header) - - -class CustomRollbarNotifierMiddleware(RollbarNotifierMiddleware): - - def get_extra_data(self, request, exc) -> Dict: - feature: GrowthBook = request.feature - context: ContextHeader = request.context_header - - # Example of adding arbitrary metadata (optional) - extra_data: Dict = { - 'user_agent': context.agent, - 'feature_flags': feature.get_features() - } - - return extra_data - - def get_payload_data(self, request, exc) -> Dict: - payload_data: Dict = dict() - - if not request.user.is_anonymous: - # Adding info about the user affected by this event (optional) - # The 'id' field is required, anything else is optional - payload_data = { - 'person': { - 'id': request.user.id, - 'username': request.user.username, - 'email': request.user.email, - }, - } - - return payload_data diff --git a/core/mixin.py b/core/mixin.py index af164f8..7bcaa74 100644 --- a/core/mixin.py +++ b/core/mixin.py @@ -2,9 +2,10 @@ from typing import NoReturn from dependency_injector.wiring import Provide, inject -from django.http import HttpRequest +from django.http import HttpRequest, HttpResponse from growthbook import GrowthBook +from core.models import ContextHeader from di import CoreContainer @@ -14,16 +15,47 @@ def __init__(self) -> None: self._logger = logging.getLogger('django') -class GrowthBookMixin: +class GrowthBookMixin(LoggerMixin): @inject def __init__(self, growth_book=Provide[CoreContainer.growth_book]) -> None: + super().__init__() self.__growth_book: GrowthBook = growth_book - def process_request( - self, - request: HttpRequest, - ) -> NoReturn: + def set_request_attributes(self, context_header: ContextHeader): + user_agent_info = context_header.user_agent_info + application = context_header.application + self.__growth_book.set_attributes({ + 'app_locale': application.locale, + 'app_version': application.version, + 'app_source': application.source, + 'app_code': application.code, + 'app_name': application.label, + 'app_build_type': application.buildType, + 'browser_name': user_agent_info.user_agent.family, + 'browser_version': user_agent_info.user_agent.version, + 'cpu_architecture': user_agent_info.cpu.architecture, + 'device_model': user_agent_info.device.model, + 'device_vendor': user_agent_info.device.brand, + 'device_type': user_agent_info.device.family, + 'engine_name': user_agent_info.engine.family, + 'engine_version': user_agent_info.engine.version, + 'os_name': user_agent_info.os.family, + 'os_version': user_agent_info.os.version, + }) + + def process_request(self, request: HttpRequest) -> NoReturn: + self._logger.info('loading growth_book features') + self.__growth_book.load_features() + if hasattr(request, 'context_header'): + self.set_request_attributes( + context_header=request.context_header + ) + else: + self._logger.warning('context_header is not defined in the current request context') request.feature = self.__growth_book - request.feature.load_features() - request.feature.destroy() + + def process_response(self, request: HttpRequest, response: HttpResponse) -> HttpResponse: + self._logger.info('destroying growth_book instance') + self.__growth_book.destroy() + return response diff --git a/core/models.py b/core/models.py index aac3256..26d93aa 100644 --- a/core/models.py +++ b/core/models.py @@ -1,4 +1,4 @@ -from dataclasses import dataclass +from dataclasses import dataclass, field from typing import Optional from django.db import models @@ -12,13 +12,51 @@ class Meta: abstract = True +@dataclass +class UserAgent: + family: Optional[str] = None + major: Optional[str] = None + minor: Optional[str] = None + patch: Optional[str] = None + + @property + def version(self): + return f'{self.major}{self.minor}{self.patch}' + + +@dataclass +class CPU: + architecture: Optional[str] = None + + @dataclass class Device: - browser: Optional[str] - cpu: Optional[str] - device: Optional[str] - engine: Optional[str] - os: Optional[str] + family: Optional[str] = None + brand: Optional[str] = None + model: Optional[str] = None + + +@dataclass +class OS: + family: Optional[str] = None + major: Optional[str] = None + minor: Optional[str] = None + patch: Optional[str] = None + patch_minor: Optional[str] = None + + @property + def version(self): + return f'{self.major}{self.minor}{self.patch}${self.patch_minor}' + + +@dataclass +class UserAgentInfo: + raw: str + user_agent: UserAgent = field(default_factory=UserAgent) + cpu: CPU = field(default_factory=CPU) + device: Device = field(default_factory=Device) + engine: UserAgent = field(default_factory=UserAgent) + os: OS = field(default_factory=OS) @dataclass @@ -35,8 +73,7 @@ class Application: class ContextHeader: authorization: Optional[str] accepts: Optional[str] - agent: str - contentType: Optional[str] - acceptEncoding: Optional[str] - language: Optional[str] + content_type: Optional[str] + accept_encoding: Optional[str] application: Application + user_agent_info: UserAgentInfo diff --git a/core/tests.py b/core/tests.py index 6874493..ec55630 100644 --- a/core/tests.py +++ b/core/tests.py @@ -68,8 +68,8 @@ def test_process_request_with_headers(self): headers = { 'HTTP_AUTHORIZATION': 'Bearer token', 'HTTP_ACCEPT': 'application/json', - 'HTTP_USER_AGENT': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 ' - '(KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.3', + 'HTTP_USER_AGENT': 'Mozilla/5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N) AppleWebKit/537.36 (KHTML, ' + 'like Gecko) Chrome/124.0.0.0 Mobile Safari/537.36', 'CONTENT_TYPE': 'application/json', 'HTTP_ACCEPT_ENCODING': 'gzip, deflate', 'HTTP_ACCEPT_LANGUAGE': 'en-US,en;q=0.9', @@ -87,16 +87,25 @@ def test_process_request_with_headers(self): self.assertEqual(context_header.authorization, 'Bearer token') self.assertEqual(context_header.accepts, 'application/json') - self.assertEqual(context_header.agent, 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36' - ' (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.3') - self.assertEqual(context_header.contentType, 'application/json') - self.assertEqual(context_header.acceptEncoding, 'gzip, deflate') - self.assertEqual(context_header.language, 'en-US,en;q=0.9') - # self.assertEqual(context_header.device.browser, 'Chrome') - # self.assertEqual(context_header.device.cpu, None) - # self.assertEqual(context_header.device.device, None) - # self.assertEqual(context_header.device.engine, 'Chrome') - # self.assertEqual(context_header.device.os, 'Windows NT 10.0') + self.assertEqual(context_header.user_agent_info.raw, 'Mozilla/5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N) ' + 'AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0' + ' Mobile Safari/537.36') + self.assertEqual(context_header.content_type, 'application/json') + self.assertEqual(context_header.accept_encoding, 'gzip, deflate') + self.assertEqual(context_header.user_agent_info.user_agent.family, 'Chrome Mobile') + self.assertEqual(context_header.user_agent_info.user_agent.major, '124') + self.assertEqual(context_header.user_agent_info.user_agent.minor, '0') + self.assertEqual(context_header.user_agent_info.user_agent.patch, '0') + self.assertIsNone(context_header.user_agent_info.cpu.architecture) + self.assertEqual(context_header.user_agent_info.device.family, 'Nexus 5') + self.assertEqual(context_header.user_agent_info.device.brand, 'LG') + self.assertEqual(context_header.user_agent_info.device.model, 'Nexus 5') + self.assertIsNone(context_header.user_agent_info.engine.family) + self.assertEqual(context_header.user_agent_info.os.family, 'Android') + self.assertEqual(context_header.user_agent_info.os.major, '6') + self.assertEqual(context_header.user_agent_info.os.minor, '0') + self.assertIsNone(context_header.user_agent_info.os.patch) + self.assertIsNone(context_header.user_agent_info.os.patch_minor) self.assertEqual(context_header.application.locale, 'en-US') self.assertEqual(context_header.application.version, '1.0') self.assertEqual(context_header.application.source, 'web') diff --git a/core/utils.py b/core/utils.py index 612a7dc..960c452 100644 --- a/core/utils.py +++ b/core/utils.py @@ -1,19 +1,63 @@ -from typing import Optional +from typing import Optional, Dict, Any, Mapping from ua_parser import user_agent_parser -from .models import Device +from .models import UserAgentInfo, UserAgent, CPU, Device, OS + + +def get_forwarded_headers(context) -> Optional[Mapping]: + keys_to_pick = [ + 'host', + 'accept', + 'accept-encoding', + 'accept-language', + 'user-agent', + 'content-type', + 'x-app-name', + 'x-app-version', + 'x-app-code', + 'x-app-source', + 'x-app-locale', + 'x-app-build-type' + ] + headers: Optional[Mapping] = None + if hasattr(context, 'headers'): + request_headers: Mapping = context.headers + headers = {key: request_headers[key] for key in keys_to_pick if key in request_headers} + return headers + + +def safe_get(dictionary: Dict[str, any], keys: str, default: Optional[any] = None) -> any: + """ + Safely get a nested value from a dictionary. + + Args: + dictionary (Dict[str, any]): The dictionary to extract the value from. + keys (str): A string representing the keys separated by dots. + default (Optional[any], optional): Default value to return if the keys are not found. Defaults to None. + + Returns: + any: The value found at the specified keys, or the default value if not found. + """ + keys_list = keys.split('.') + for key in keys_list: + if isinstance(dictionary, dict) and key in dictionary: + dictionary = dictionary[key] + else: + return default if default is not None else {} + return dictionary class UAParser: def __init__(self, user_agent: Optional[str] = None): self.ua = user_agent_parser.Parse(user_agent or '') - def get_result(self) -> Device: - return Device( - browser=self.ua.get('user_agent', {}).get('family'), - cpu=self.ua('cpu', {}).get('architecture'), - device=self.ua('device', {}).get('family'), - engine=self.ua('user_agent', {}).get('family'), - os=self.ua('os', {}).get('family'), + def get_result(self) -> UserAgentInfo: + return UserAgentInfo( + raw=safe_get(self.ua, 'string'), + user_agent=UserAgent(**safe_get(self.ua, 'user_agent')), + cpu=CPU(**safe_get(self.ua, 'cpu')), + device=Device(**safe_get(self.ua, 'device')), + engine=UserAgent(**safe_get(self.ua, 'engine')), + os=OS(**safe_get(self.ua, 'os')) ) diff --git a/di/containers.py b/di/containers.py index 86d2209..a755533 100644 --- a/di/containers.py +++ b/di/containers.py @@ -30,5 +30,5 @@ class CoreContainer(containers.DeclarativeContainer): api_host=settings.GROWTH_BOOK["host"], client_key=settings.GROWTH_BOOK["key"], cache_ttl=settings.GROWTH_BOOK["ttl"], - trackingCallback=on_experiment_viewed + on_experiment_viewed=on_experiment_viewed ) diff --git a/poetry.lock b/poetry.lock index 1412ea4..175ffe9 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,4 +1,4 @@ -# This file is automatically @generated by Poetry 1.8.2 and should not be changed by hand. +# This file is automatically @generated by Poetry 1.5.0 and should not be changed by hand. [[package]] name = "aniso8601" @@ -42,6 +42,98 @@ typing-extensions = {version = ">=4", markers = "python_version < \"3.11\""} [package.extras] tests = ["mypy (>=0.800)", "pytest", "pytest-asyncio"] +[[package]] +name = "brotli" +version = "1.1.0" +description = "Python bindings for the Brotli compression library" +optional = false +python-versions = "*" +files = [ + {file = "Brotli-1.1.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:e1140c64812cb9b06c922e77f1c26a75ec5e3f0fb2bf92cc8c58720dec276752"}, + {file = "Brotli-1.1.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:c8fd5270e906eef71d4a8d19b7c6a43760c6abcfcc10c9101d14eb2357418de9"}, + {file = "Brotli-1.1.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1ae56aca0402a0f9a3431cddda62ad71666ca9d4dc3a10a142b9dce2e3c0cda3"}, + {file = "Brotli-1.1.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:43ce1b9935bfa1ede40028054d7f48b5469cd02733a365eec8a329ffd342915d"}, + {file = "Brotli-1.1.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:7c4855522edb2e6ae7fdb58e07c3ba9111e7621a8956f481c68d5d979c93032e"}, + {file = "Brotli-1.1.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:38025d9f30cf4634f8309c6874ef871b841eb3c347e90b0851f63d1ded5212da"}, + {file = "Brotli-1.1.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:e6a904cb26bfefc2f0a6f240bdf5233be78cd2488900a2f846f3c3ac8489ab80"}, + {file = "Brotli-1.1.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:a37b8f0391212d29b3a91a799c8e4a2855e0576911cdfb2515487e30e322253d"}, + {file = "Brotli-1.1.0-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:e84799f09591700a4154154cab9787452925578841a94321d5ee8fb9a9a328f0"}, + {file = "Brotli-1.1.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:f66b5337fa213f1da0d9000bc8dc0cb5b896b726eefd9c6046f699b169c41b9e"}, + {file = "Brotli-1.1.0-cp310-cp310-win32.whl", hash = "sha256:be36e3d172dc816333f33520154d708a2657ea63762ec16b62ece02ab5e4daf2"}, + {file = "Brotli-1.1.0-cp310-cp310-win_amd64.whl", hash = "sha256:0c6244521dda65ea562d5a69b9a26120769b7a9fb3db2fe9545935ed6735b128"}, + {file = "Brotli-1.1.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:a3daabb76a78f829cafc365531c972016e4aa8d5b4bf60660ad8ecee19df7ccc"}, + {file = "Brotli-1.1.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c8146669223164fc87a7e3de9f81e9423c67a79d6b3447994dfb9c95da16e2d6"}, + {file = "Brotli-1.1.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:30924eb4c57903d5a7526b08ef4a584acc22ab1ffa085faceb521521d2de32dd"}, + {file = "Brotli-1.1.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ceb64bbc6eac5a140ca649003756940f8d6a7c444a68af170b3187623b43bebf"}, + {file = "Brotli-1.1.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a469274ad18dc0e4d316eefa616d1d0c2ff9da369af19fa6f3daa4f09671fd61"}, + {file = "Brotli-1.1.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:524f35912131cc2cabb00edfd8d573b07f2d9f21fa824bd3fb19725a9cf06327"}, + {file = "Brotli-1.1.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:5b3cc074004d968722f51e550b41a27be656ec48f8afaeeb45ebf65b561481dd"}, + {file = "Brotli-1.1.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:19c116e796420b0cee3da1ccec3b764ed2952ccfcc298b55a10e5610ad7885f9"}, + {file = "Brotli-1.1.0-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:510b5b1bfbe20e1a7b3baf5fed9e9451873559a976c1a78eebaa3b86c57b4265"}, + {file = "Brotli-1.1.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:a1fd8a29719ccce974d523580987b7f8229aeace506952fa9ce1d53a033873c8"}, + {file = "Brotli-1.1.0-cp311-cp311-win32.whl", hash = "sha256:39da8adedf6942d76dc3e46653e52df937a3c4d6d18fdc94a7c29d263b1f5b50"}, + {file = "Brotli-1.1.0-cp311-cp311-win_amd64.whl", hash = "sha256:aac0411d20e345dc0920bdec5548e438e999ff68d77564d5e9463a7ca9d3e7b1"}, + {file = "Brotli-1.1.0-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:316cc9b17edf613ac76b1f1f305d2a748f1b976b033b049a6ecdfd5612c70409"}, + {file = "Brotli-1.1.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:caf9ee9a5775f3111642d33b86237b05808dafcd6268faa492250e9b78046eb2"}, + {file = "Brotli-1.1.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:70051525001750221daa10907c77830bc889cb6d865cc0b813d9db7fefc21451"}, + {file = "Brotli-1.1.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7f4bf76817c14aa98cc6697ac02f3972cb8c3da93e9ef16b9c66573a68014f91"}, + {file = "Brotli-1.1.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d0c5516f0aed654134a2fc936325cc2e642f8a0e096d075209672eb321cff408"}, + {file = "Brotli-1.1.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6c3020404e0b5eefd7c9485ccf8393cfb75ec38ce75586e046573c9dc29967a0"}, + {file = "Brotli-1.1.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:4ed11165dd45ce798d99a136808a794a748d5dc38511303239d4e2363c0695dc"}, + {file = "Brotli-1.1.0-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:4093c631e96fdd49e0377a9c167bfd75b6d0bad2ace734c6eb20b348bc3ea180"}, + {file = "Brotli-1.1.0-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:7e4c4629ddad63006efa0ef968c8e4751c5868ff0b1c5c40f76524e894c50248"}, + {file = "Brotli-1.1.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:861bf317735688269936f755fa136a99d1ed526883859f86e41a5d43c61d8966"}, + {file = "Brotli-1.1.0-cp312-cp312-win32.whl", hash = "sha256:5f4d5ea15c9382135076d2fb28dde923352fe02951e66935a9efaac8f10e81b0"}, + {file = "Brotli-1.1.0-cp312-cp312-win_amd64.whl", hash = "sha256:906bc3a79de8c4ae5b86d3d75a8b77e44404b0f4261714306e3ad248d8ab0951"}, + {file = "Brotli-1.1.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:a090ca607cbb6a34b0391776f0cb48062081f5f60ddcce5d11838e67a01928d1"}, + {file = "Brotli-1.1.0-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2de9d02f5bda03d27ede52e8cfe7b865b066fa49258cbab568720aa5be80a47d"}, + {file = "Brotli-1.1.0-cp36-cp36m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2333e30a5e00fe0fe55903c8832e08ee9c3b1382aacf4db26664a16528d51b4b"}, + {file = "Brotli-1.1.0-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:4d4a848d1837973bf0f4b5e54e3bec977d99be36a7895c61abb659301b02c112"}, + {file = "Brotli-1.1.0-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:fdc3ff3bfccdc6b9cc7c342c03aa2400683f0cb891d46e94b64a197910dc4064"}, + {file = "Brotli-1.1.0-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:5eeb539606f18a0b232d4ba45adccde4125592f3f636a6182b4a8a436548b914"}, + {file = "Brotli-1.1.0-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:fd5f17ff8f14003595ab414e45fce13d073e0762394f957182e69035c9f3d7c2"}, + {file = "Brotli-1.1.0-cp36-cp36m-musllinux_1_1_ppc64le.whl", hash = "sha256:069a121ac97412d1fe506da790b3e69f52254b9df4eb665cd42460c837193354"}, + {file = "Brotli-1.1.0-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:e93dfc1a1165e385cc8239fab7c036fb2cd8093728cbd85097b284d7b99249a2"}, + {file = "Brotli-1.1.0-cp36-cp36m-win32.whl", hash = "sha256:a599669fd7c47233438a56936988a2478685e74854088ef5293802123b5b2460"}, + {file = "Brotli-1.1.0-cp36-cp36m-win_amd64.whl", hash = "sha256:d143fd47fad1db3d7c27a1b1d66162e855b5d50a89666af46e1679c496e8e579"}, + {file = "Brotli-1.1.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:11d00ed0a83fa22d29bc6b64ef636c4552ebafcef57154b4ddd132f5638fbd1c"}, + {file = "Brotli-1.1.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f733d788519c7e3e71f0855c96618720f5d3d60c3cb829d8bbb722dddce37985"}, + {file = "Brotli-1.1.0-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:929811df5462e182b13920da56c6e0284af407d1de637d8e536c5cd00a7daf60"}, + {file = "Brotli-1.1.0-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:0b63b949ff929fbc2d6d3ce0e924c9b93c9785d877a21a1b678877ffbbc4423a"}, + {file = "Brotli-1.1.0-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:d192f0f30804e55db0d0e0a35d83a9fead0e9a359a9ed0285dbacea60cc10a84"}, + {file = "Brotli-1.1.0-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:f296c40e23065d0d6650c4aefe7470d2a25fffda489bcc3eb66083f3ac9f6643"}, + {file = "Brotli-1.1.0-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:919e32f147ae93a09fe064d77d5ebf4e35502a8df75c29fb05788528e330fe74"}, + {file = "Brotli-1.1.0-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:23032ae55523cc7bccb4f6a0bf368cd25ad9bcdcc1990b64a647e7bbcce9cb5b"}, + {file = "Brotli-1.1.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:224e57f6eac61cc449f498cc5f0e1725ba2071a3d4f48d5d9dffba42db196438"}, + {file = "Brotli-1.1.0-cp37-cp37m-win32.whl", hash = "sha256:587ca6d3cef6e4e868102672d3bd9dc9698c309ba56d41c2b9c85bbb903cdb95"}, + {file = "Brotli-1.1.0-cp37-cp37m-win_amd64.whl", hash = "sha256:2954c1c23f81c2eaf0b0717d9380bd348578a94161a65b3a2afc62c86467dd68"}, + {file = "Brotli-1.1.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:efa8b278894b14d6da122a72fefcebc28445f2d3f880ac59d46c90f4c13be9a3"}, + {file = "Brotli-1.1.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:03d20af184290887bdea3f0f78c4f737d126c74dc2f3ccadf07e54ceca3bf208"}, + {file = "Brotli-1.1.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6172447e1b368dcbc458925e5ddaf9113477b0ed542df258d84fa28fc45ceea7"}, + {file = "Brotli-1.1.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a743e5a28af5f70f9c080380a5f908d4d21d40e8f0e0c8901604d15cfa9ba751"}, + {file = "Brotli-1.1.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:0541e747cce78e24ea12d69176f6a7ddb690e62c425e01d31cc065e69ce55b48"}, + {file = "Brotli-1.1.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:cdbc1fc1bc0bff1cef838eafe581b55bfbffaed4ed0318b724d0b71d4d377619"}, + {file = "Brotli-1.1.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:890b5a14ce214389b2cc36ce82f3093f96f4cc730c1cffdbefff77a7c71f2a97"}, + {file = "Brotli-1.1.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:1ab4fbee0b2d9098c74f3057b2bc055a8bd92ccf02f65944a241b4349229185a"}, + {file = "Brotli-1.1.0-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:141bd4d93984070e097521ed07e2575b46f817d08f9fa42b16b9b5f27b5ac088"}, + {file = "Brotli-1.1.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:fce1473f3ccc4187f75b4690cfc922628aed4d3dd013d047f95a9b3919a86596"}, + {file = "Brotli-1.1.0-cp38-cp38-win32.whl", hash = "sha256:db85ecf4e609a48f4b29055f1e144231b90edc90af7481aa731ba2d059226b1b"}, + {file = "Brotli-1.1.0-cp38-cp38-win_amd64.whl", hash = "sha256:3d7954194c36e304e1523f55d7042c59dc53ec20dd4e9ea9d151f1b62b4415c0"}, + {file = "Brotli-1.1.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:5fb2ce4b8045c78ebbc7b8f3c15062e435d47e7393cc57c25115cfd49883747a"}, + {file = "Brotli-1.1.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:7905193081db9bfa73b1219140b3d315831cbff0d8941f22da695832f0dd188f"}, + {file = "Brotli-1.1.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a77def80806c421b4b0af06f45d65a136e7ac0bdca3c09d9e2ea4e515367c7e9"}, + {file = "Brotli-1.1.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8dadd1314583ec0bf2d1379f7008ad627cd6336625d6679cf2f8e67081b83acf"}, + {file = "Brotli-1.1.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:901032ff242d479a0efa956d853d16875d42157f98951c0230f69e69f9c09bac"}, + {file = "Brotli-1.1.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:22fc2a8549ffe699bfba2256ab2ed0421a7b8fadff114a3d201794e45a9ff578"}, + {file = "Brotli-1.1.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:ae15b066e5ad21366600ebec29a7ccbc86812ed267e4b28e860b8ca16a2bc474"}, + {file = "Brotli-1.1.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:949f3b7c29912693cee0afcf09acd6ebc04c57af949d9bf77d6101ebb61e388c"}, + {file = "Brotli-1.1.0-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:89f4988c7203739d48c6f806f1e87a1d96e0806d44f0fba61dba81392c9e474d"}, + {file = "Brotli-1.1.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:de6551e370ef19f8de1807d0a9aa2cdfdce2e85ce88b122fe9f6b2b076837e59"}, + {file = "Brotli-1.1.0-cp39-cp39-win32.whl", hash = "sha256:f0d8a7a6b5983c2496e364b969f0e526647a06b075d034f3297dc66f3b360c64"}, + {file = "Brotli-1.1.0-cp39-cp39-win_amd64.whl", hash = "sha256:cdad5b9014d83ca68c25d2e9444e28e967ef16e80f6b436918c700c117a85467"}, + {file = "Brotli-1.1.0.tar.gz", hash = "sha256:81de08ac11bcb85841e440c13611c00b67d3bf82698314928d0b676362546724"}, +] + [[package]] name = "certifi" version = "2023.7.22" @@ -1239,20 +1331,6 @@ urllib3 = ">=1.21.1,<3" socks = ["PySocks (>=1.5.6,!=1.5.7)"] use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"] -[[package]] -name = "rollbar" -version = "1.0.0" -description = "Easy and powerful exception tracking with Rollbar. Send messages and exceptions with arbitrary context, get back aggregates, and debug production issues quickly." -optional = false -python-versions = "*" -files = [ - {file = "rollbar-1.0.0-py3-none-any.whl", hash = "sha256:b7831cd8f2d2384e5922cdfb18897890c4ac4609fddfd2a821efe17b92cf1e58"}, - {file = "rollbar-1.0.0.tar.gz", hash = "sha256:6347b7e49f22f0296fc287a6aea75d642cf64494d2ee127042a7a593c97df3af"}, -] - -[package.dependencies] -requests = ">=0.12.1" - [[package]] name = "serde" version = "0.9.0" @@ -1536,4 +1614,4 @@ testing = ["coverage (>=5.0.3)", "zope.event", "zope.testing"] [metadata] lock-version = "2.0" python-versions = "^3.10" -content-hash = "3a23dc9747226624c050e4ab7fe1a7f5c6291de151bba3e8c012e30f29f594bb" +content-hash = "54a135403c6136ba809f6fb8f265568488cae90abb12ea9fac9a54a0cf2ec5a6" diff --git a/pyproject.toml b/pyproject.toml index 4cfa2b8..edf5c71 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -31,8 +31,8 @@ reactivex = "4.0.4" pyxtension = "1.16.10" ua-parser = "0.18.0" growthbook = "1.1.0" -rollbar = "1.0.0" logtail-python = "0.2.10" +brotli = "1.1.0" [build-system] requires = ["poetry>=1.0.0"]