diff --git a/Cargo.lock b/Cargo.lock index 644cb6d9..900761b1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2535,9 +2535,9 @@ dependencies = [ [[package]] name = "sentry_protos" -version = "0.8.5" +version = "0.8.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d3c4e8bca4c556eec616dc2594e519248891ca84f8bf958016c2c416223d8ff" +checksum = "60dfb8c1b03c3f6e800a91eca7daea05205dd87f63b8d70b50b7e2211a2e0be2" dependencies = [ "prost", "prost-types", diff --git a/Cargo.toml b/Cargo.toml index 9a042406..1ad2b992 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -42,7 +42,7 @@ sentry = { version = "0.41.0", default-features = false, features = [ "tracing", "logs" ] } -sentry_protos = "0.8.5" +sentry_protos = "0.8.13" serde = "1.0.214" serde_yaml = "0.9.34" sha2 = "0.10.8" diff --git a/clients/python/pyproject.toml b/clients/python/pyproject.toml index 27b29b14..20ae13f3 100644 --- a/clients/python/pyproject.toml +++ b/clients/python/pyproject.toml @@ -6,10 +6,11 @@ readme = "README.md" dependencies = [ "sentry-arroyo>=2.38.7", "sentry-sdk[http2]>=2.43.0", - "sentry-protos>=0.8.5", + "sentry-protos>=0.8.13", "confluent_kafka>=2.3.0", "cronsim>=2.6", "grpcio>=1.67.0", + "msgpack>=1.0.0", "orjson>=3.10.10", "protobuf>=5.28.3", "redis>=3.4.1", diff --git a/clients/python/src/taskbroker_client/constants.py b/clients/python/src/taskbroker_client/constants.py index 36074a01..6655c199 100644 --- a/clients/python/src/taskbroker_client/constants.py +++ b/clients/python/src/taskbroker_client/constants.py @@ -66,3 +66,16 @@ class CompressionType(Enum): ZSTD = "zstd" PLAINTEXT = "plaintext" + + +class ParametersFormat(Enum): + """ + How the producer populates the legacy `parameters` (JSON) and new + `parameters_bytes` (msgpack) fields on TaskActivation. + + Set via env var `TASKBROKER_CLIENT_PARAMETERS_FORMAT`. Default BOTH. + """ + + BOTH = "both" + JSON = "json" + MSGPACK = "msgpack" diff --git a/clients/python/src/taskbroker_client/task.py b/clients/python/src/taskbroker_client/task.py index 5dfa9ead..2463db23 100644 --- a/clients/python/src/taskbroker_client/task.py +++ b/clients/python/src/taskbroker_client/task.py @@ -2,12 +2,14 @@ import base64 import datetime +import os import time from collections.abc import Callable, Collection, Mapping, MutableMapping from functools import update_wrapper from typing import TYPE_CHECKING, Any, Generic, ParamSpec, TypeVar from uuid import uuid4 +import msgpack import orjson import sentry_sdk import zstandard as zstd @@ -22,9 +24,22 @@ DEFAULT_PROCESSING_DEADLINE, MAX_PARAMETER_BYTES_BEFORE_COMPRESSION, CompressionType, + ParametersFormat, ) from taskbroker_client.retry import Retry + +def _get_parameters_format() -> ParametersFormat: + raw = os.environ.get("TASKBROKER_CLIENT_PARAMETERS_FORMAT", ParametersFormat.BOTH.value) + try: + return ParametersFormat(raw.lower()) + except ValueError: + raise ValueError( + f"Invalid TASKBROKER_CLIENT_PARAMETERS_FORMAT={raw!r}. " + f"Expected one of: {', '.join(f.value for f in ParametersFormat)}" + ) + + if TYPE_CHECKING: from taskbroker_client.registry import TaskNamespace @@ -197,37 +212,58 @@ def create_activation( f"The `{key}` header value is of type {type(value)}" ) - parameters_json = orjson.dumps({"args": args, "kwargs": kwargs}) - if ( - len(parameters_json) > MAX_PARAMETER_BYTES_BEFORE_COMPRESSION - or self.compression_type == CompressionType.ZSTD - ): - # Worker uses this header to determine if the parameters are decompressed + parameters_format = _get_parameters_format() + data = {"args": args, "kwargs": kwargs} + + msgpack_bytes = ( + msgpack.packb(data, use_bin_type=True) + if parameters_format in (ParametersFormat.BOTH, ParametersFormat.MSGPACK) + else b"" + ) + # JSON can't encode some values msgpack can (e.g. raw bytes). In + # JSON-only mode we surface the TypeError; in BOTH mode we silently + # skip the legacy field so msgpack-aware workers can still run. + json_bytes: bytes | None = None + if parameters_format in (ParametersFormat.BOTH, ParametersFormat.JSON): + try: + json_bytes = orjson.dumps(data) + except TypeError: + if parameters_format == ParametersFormat.JSON: + raise + + should_compress = ( + self.compression_type == CompressionType.ZSTD + or (len(msgpack_bytes) + len(json_bytes or b"")) + > MAX_PARAMETER_BYTES_BEFORE_COMPRESSION + ) + + if should_compress: headers["compression-type"] = CompressionType.ZSTD.value start_time = time.perf_counter() - parameters_str = base64.b64encode(zstd.compress(parameters_json)).decode("utf8") - end_time = time.perf_counter() + parameters_bytes_val = zstd.compress(msgpack_bytes) if msgpack_bytes else b"" + parameters_str = ( + base64.b64encode(zstd.compress(json_bytes)).decode("utf8") if json_bytes else "" + ) + elapsed = time.perf_counter() - start_time + metric_tags = { + "namespace": self._namespace.name, + "taskname": self.name, + "topic": self._namespace.topic, + } self.namespace.metrics.distribution( "taskworker.producer.compressed_parameters_size", - len(parameters_str), - tags={ - "namespace": self._namespace.name, - "taskname": self.name, - "topic": self._namespace.topic, - }, + len(parameters_bytes_val) or len(parameters_str), + tags=metric_tags, ) self.namespace.metrics.distribution( "taskworker.producer.compression_time", - end_time - start_time, - tags={ - "namespace": self._namespace.name, - "taskname": self.name, - "topic": self._namespace.topic, - }, + elapsed, + tags=metric_tags, ) else: - parameters_str = parameters_json.decode("utf8") + parameters_bytes_val = msgpack_bytes + parameters_str = json_bytes.decode("utf8") if json_bytes else "" return TaskActivation( id=uuid4().hex, @@ -236,6 +272,7 @@ def create_activation( taskname=self.name, headers=headers, parameters=parameters_str, + parameters_bytes=parameters_bytes_val, retry_state=self._create_retry_state(), received_at=received_at, processing_deadline_duration=processing_deadline, diff --git a/clients/python/src/taskbroker_client/worker/workerchild.py b/clients/python/src/taskbroker_client/worker/workerchild.py index 18e2ad68..12583c1a 100644 --- a/clients/python/src/taskbroker_client/worker/workerchild.py +++ b/clients/python/src/taskbroker_client/worker/workerchild.py @@ -12,6 +12,7 @@ from typing import Any # XXX: Don't import any modules that will import django here, do those within child_process +import msgpack import orjson import sentry_sdk import zstandard as zstd @@ -59,17 +60,28 @@ def timeout_alarm( signal.signal(signal.SIGALRM, original) -def load_parameters(data: str, headers: dict[str, str]) -> dict[str, Any]: +def load_parameters(activation: TaskActivation) -> dict[str, Any]: + headers = dict(activation.headers) compression_type = headers.get("compression-type", None) + + # Prefer new msgpack field + if activation.parameters_bytes: + data = activation.parameters_bytes + if compression_type == CompressionType.ZSTD.value: + data = zstd.decompress(data) + return msgpack.unpackb(data, raw=False) + + # Legacy JSON fallback + data_str = activation.parameters if not compression_type or compression_type == CompressionType.PLAINTEXT.value: - return orjson.loads(data) + return orjson.loads(data_str) elif compression_type == CompressionType.ZSTD.value: - return orjson.loads(zstd.decompress(base64.b64decode(data))) + return orjson.loads(zstd.decompress(base64.b64decode(data_str))) else: logger.error( "Unsupported compression type: %s. Continuing with plaintext.", compression_type ) - return orjson.loads(data) + return orjson.loads(data_str) def status_name(status: TaskActivationStatus.ValueType) -> str: @@ -314,12 +326,12 @@ def _execute_activation( context_hooks: Sequence[ContextHook] = (), ) -> None: """Invoke a task function with the activation parameters.""" - headers = {k: v for k, v in activation.headers.items()} - parameters = load_parameters(activation.parameters, headers) + parameters = load_parameters(activation) args = parameters.get("args", []) kwargs = parameters.get("kwargs", {}) + headers = dict(activation.headers) transaction = sentry_sdk.continue_trace( environ_or_headers=headers, op="queue.task.taskworker", diff --git a/clients/python/tests/test_registry.py b/clients/python/tests/test_registry.py index 050e7d8f..5d215dba 100644 --- a/clients/python/tests/test_registry.py +++ b/clients/python/tests/test_registry.py @@ -2,6 +2,7 @@ from concurrent.futures import Future from unittest.mock import Mock +import msgpack import orjson import pytest import zstandard as zstd @@ -174,12 +175,14 @@ def simple_task_with_compression(param: str) -> None: expected_params = {"args": ["test_arg"], "kwargs": {"test_key": "test_value"}} - decoded_data = base64.b64decode(activation.parameters.encode("utf-8")) - decompressed_data = zstd.decompress(decoded_data) - actual_params = orjson.loads(decompressed_data) + decompressed_data = zstd.decompress(activation.parameters_bytes) + actual_params = msgpack.unpackb(decompressed_data, raw=False) assert actual_params == expected_params + legacy_decompressed = zstd.decompress(base64.b64decode(activation.parameters.encode("utf-8"))) + assert orjson.loads(legacy_decompressed) == expected_params + def test_namespace_send_task_with_auto_compression() -> None: namespace = TaskNamespace( @@ -204,12 +207,14 @@ def simple_task_with_compression(param: str) -> None: expected_params = {"args": big_args, "kwargs": {"test_key": "test_value"}} - decoded_data = base64.b64decode(activation.parameters.encode("utf-8")) - decompressed_data = zstd.decompress(decoded_data) - actual_params = orjson.loads(decompressed_data) + decompressed_data = zstd.decompress(activation.parameters_bytes) + actual_params = msgpack.unpackb(decompressed_data, raw=False) assert actual_params == expected_params + legacy_decompressed = zstd.decompress(base64.b64decode(activation.parameters.encode("utf-8"))) + assert orjson.loads(legacy_decompressed) == expected_params + def test_namespace_send_task_with_retry() -> None: namespace = TaskNamespace( diff --git a/clients/python/tests/test_task.py b/clients/python/tests/test_task.py index 5bb9c271..7fd9785e 100644 --- a/clients/python/tests/test_task.py +++ b/clients/python/tests/test_task.py @@ -4,6 +4,7 @@ from typing import Any from unittest.mock import patch +import msgpack import orjson import pytest import sentry_sdk @@ -78,9 +79,9 @@ def test_func(*args: Any, **kwargs: Any) -> None: activation = call_params.args[0] assert activation.expires == 10 - assert activation.parameters == orjson.dumps( - {"args": ["arg2"], "kwargs": {"org_id": 2}} - ).decode("utf-8") + expected_params = {"args": ["arg2"], "kwargs": {"org_id": 2}} + assert msgpack.unpackb(activation.parameters_bytes, raw=False) == expected_params + assert orjson.loads(activation.parameters) == expected_params def test_apply_async_countdown(task_namespace: TaskNamespace) -> None: @@ -99,9 +100,9 @@ def test_func(*args: Any, **kwargs: Any) -> None: activation = call_params.args[0] assert activation.delay == 600 - assert activation.parameters == orjson.dumps( - {"args": ["arg2"], "kwargs": {"org_id": 2}} - ).decode("utf-8") + expected_params = {"args": ["arg2"], "kwargs": {"org_id": 2}} + assert msgpack.unpackb(activation.parameters_bytes, raw=False) == expected_params + assert orjson.loads(activation.parameters) == expected_params def test_delay_immediate_mode(task_namespace: TaskNamespace) -> None: @@ -268,11 +269,14 @@ def with_parameters(one: str, two: int, org_id: int) -> None: raise NotImplementedError activation = with_parameters.create_activation(["one", 22], {"org_id": 99}) - params = orjson.loads(activation.parameters) + params = msgpack.unpackb(activation.parameters_bytes, raw=False) assert params["args"] assert params["args"] == ["one", 22] assert params["kwargs"] == {"org_id": 99} + json_params = orjson.loads(activation.parameters) + assert json_params == params + def test_create_activation_tracing(task_namespace: TaskNamespace) -> None: @task_namespace.register(name="test.parameters") diff --git a/clients/python/tests/worker/test_worker.py b/clients/python/tests/worker/test_worker.py index 731eaf0b..9e9f5f33 100644 --- a/clients/python/tests/worker/test_worker.py +++ b/clients/python/tests/worker/test_worker.py @@ -8,6 +8,7 @@ from unittest import TestCase, mock import grpc +import msgpack import orjson import zstandard as zstd from redis import StrictRedis @@ -39,7 +40,7 @@ id="111", taskname="examples.simple_task", namespace="examples", - parameters='{"args": [], "kwargs": {}}', + parameters_bytes=msgpack.packb({"args": [], "kwargs": {}}, use_bin_type=True), processing_deadline_duration=2, ), ) @@ -51,7 +52,7 @@ id="222", taskname="examples.retry_task", namespace="examples", - parameters='{"args": [], "kwargs": {}}', + parameters_bytes=msgpack.packb({"args": [], "kwargs": {}}, use_bin_type=True), processing_deadline_duration=2, ), ) @@ -63,7 +64,7 @@ id="333", taskname="examples.fail_task", namespace="examples", - parameters='{"args": [], "kwargs": {}}', + parameters_bytes=msgpack.packb({"args": [], "kwargs": {}}, use_bin_type=True), processing_deadline_duration=2, ), ) @@ -75,7 +76,7 @@ id="444", taskname="total.rubbish", namespace="lolnope", - parameters='{"args": [], "kwargs": {}}', + parameters_bytes=msgpack.packb({"args": [], "kwargs": {}}, use_bin_type=True), processing_deadline_duration=2, ), ) @@ -87,7 +88,7 @@ id="555", taskname="examples.at_most_once", namespace="examples", - parameters='{"args": [], "kwargs": {}}', + parameters_bytes=msgpack.packb({"args": [], "kwargs": {}}, use_bin_type=True), processing_deadline_duration=2, ), ) @@ -99,7 +100,7 @@ id="654", taskname="examples.retry_state", namespace="examples", - parameters='{"args": [], "kwargs": {}}', + parameters_bytes=msgpack.packb({"args": [], "kwargs": {}}, use_bin_type=True), processing_deadline_duration=2, retry_state=RetryState( # no more attempts left @@ -117,7 +118,7 @@ id="111", taskname="examples.simple_task", namespace="examples", - parameters='{"args": [], "kwargs": {}}', + parameters_bytes=msgpack.packb({"args": [], "kwargs": {}}, use_bin_type=True), processing_deadline_duration=2, headers={ "sentry-monitor-slug": "simple-task", @@ -133,6 +134,43 @@ id="compressed_task_123", taskname="examples.simple_task", namespace="examples", + parameters_bytes=zstd.compress( + msgpack.packb( + { + "args": ["test_arg1", "test_arg2"], + "kwargs": {"test_key": "test_value", "number": 42}, + }, + use_bin_type=True, + ) + ), + headers={ + "compression-type": CompressionType.ZSTD.value, + }, + processing_deadline_duration=2, + ), +) + +# Legacy fixture using the old JSON parameters field for backward compat testing +LEGACY_JSON_TASK = InflightTaskActivation( + host="localhost:50051", + receive_timestamp=0, + activation=TaskActivation( + id="legacy_json_123", + taskname="examples.simple_task", + namespace="examples", + parameters='{"args": ["legacy_arg"], "kwargs": {}}', + processing_deadline_duration=2, + ), +) + +# Legacy compressed fixture using base64+zstd in the old parameters field +LEGACY_COMPRESSED_TASK = InflightTaskActivation( + host="localhost:50051", + receive_timestamp=0, + activation=TaskActivation( + id="legacy_compressed_123", + taskname="examples.simple_task", + namespace="examples", parameters=base64.b64encode( zstd.compress( orjson.dumps( @@ -765,6 +803,54 @@ def test_child_process_decompression(mock_capture_checkin: mock.MagicMock) -> No assert mock_capture_checkin.call_count == 0 +@mock.patch("sentry_sdk.crons.api.capture_checkin") +def test_child_process_legacy_json_parameters(mock_capture_checkin: mock.MagicMock) -> None: + """Test backward compat: worker can handle legacy JSON parameters field.""" + todo: queue.Queue[InflightTaskActivation] = queue.Queue() + processed: queue.Queue[ProcessingResult] = queue.Queue() + shutdown = Event() + + todo.put(LEGACY_JSON_TASK) + child_process( + "examples.app:app", + todo, + processed, + shutdown, + max_task_count=1, + processing_pool_name="test", + process_type="fork", + ) + + assert todo.empty() + result = processed.get() + assert result.task_id == LEGACY_JSON_TASK.activation.id + assert result.status == TASK_ACTIVATION_STATUS_COMPLETE + + +@mock.patch("sentry_sdk.crons.api.capture_checkin") +def test_child_process_legacy_compressed_parameters(mock_capture_checkin: mock.MagicMock) -> None: + """Test backward compat: worker can handle legacy base64+zstd compressed JSON parameters.""" + todo: queue.Queue[InflightTaskActivation] = queue.Queue() + processed: queue.Queue[ProcessingResult] = queue.Queue() + shutdown = Event() + + todo.put(LEGACY_COMPRESSED_TASK) + child_process( + "examples.app:app", + todo, + processed, + shutdown, + max_task_count=1, + processing_pool_name="test", + process_type="fork", + ) + + assert todo.empty() + result = processed.get() + assert result.task_id == LEGACY_COMPRESSED_TASK.activation.id + assert result.status == TASK_ACTIVATION_STATUS_COMPLETE + + def test_child_process_context_hooks() -> None: """Context hooks' on_execute is called with activation headers during task execution.""" executed_headers: list[dict[str, str]] = [] diff --git a/pyproject.toml b/pyproject.toml index 757c9f55..ad01b857 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -57,6 +57,7 @@ module = [ "redis.*", "rediscluster.*", "confluent_kafka.*", + "msgpack", ] ignore_missing_imports = true # end: missing 3rd party stubs diff --git a/src/kafka/deserialize_activation.rs b/src/kafka/deserialize_activation.rs index 2288d534..5bdac341 100644 --- a/src/kafka/deserialize_activation.rs +++ b/src/kafka/deserialize_activation.rs @@ -142,6 +142,7 @@ mod tests { namespace: generate_unique_namespace(), taskname: "taskname".into(), parameters: "{}".into(), + parameters_bytes: vec![], headers: HashMap::new(), // not used when the activation doesn't have expires. received_at: Some(prost_types::Timestamp { @@ -187,6 +188,7 @@ mod tests { namespace: generate_unique_namespace(), taskname: "taskname".into(), parameters: "{}".into(), + parameters_bytes: vec![], headers: HashMap::new(), // used because the activation has expires received_at: Some(prost_types::Timestamp { @@ -233,6 +235,7 @@ mod tests { namespace: generate_unique_namespace(), taskname: "taskname".into(), parameters: "{}".into(), + parameters_bytes: vec![], headers: HashMap::new(), // used because the activation has expires received_at: Some(prost_types::Timestamp { @@ -279,6 +282,7 @@ mod tests { namespace: generate_unique_namespace(), taskname: "taskname".into(), parameters: "{}".into(), + parameters_bytes: vec![], headers: HashMap::new(), // used because the activation has delay received_at: Some(prost_types::Timestamp { @@ -326,6 +330,7 @@ mod tests { namespace: generate_unique_namespace(), taskname: "taskname".into(), parameters: "{}".into(), + parameters_bytes: vec![], headers: HashMap::new(), // used because the activation has delay received_at: Some(prost_types::Timestamp { diff --git a/src/test_utils.rs b/src/test_utils.rs index 3949dbdb..8754ac05 100644 --- a/src/test_utils.rs +++ b/src/test_utils.rs @@ -31,6 +31,7 @@ pub struct TaskActivationBuilder { pub namespace: Option, pub taskname: Option, pub parameters: Option, + pub parameters_bytes: Option>, pub headers: Option>, pub received_at: Option, pub retry_state: Option, @@ -47,6 +48,7 @@ impl TaskActivationBuilder { namespace: None, taskname: None, parameters: None, + parameters_bytes: None, headers: None, received_at: None, retry_state: None, @@ -76,11 +78,17 @@ impl TaskActivationBuilder { self } + #[allow(deprecated)] pub fn parameters>(mut self, parameters: T) -> Self { self.parameters = Some(parameters.into()); self } + pub fn parameters_bytes(mut self, parameters_bytes: Vec) -> Self { + self.parameters_bytes = Some(parameters_bytes); + self + } + pub fn headers(mut self, headers: HashMap) -> Self { self.headers = Some(headers); self @@ -111,6 +119,7 @@ impl TaskActivationBuilder { self } + #[allow(deprecated)] pub fn build(self) -> v1::TaskActivation { v1::TaskActivation { id: self.id.expect("id is required"), @@ -118,6 +127,7 @@ impl TaskActivationBuilder { namespace: self.namespace.expect("namespace is required"), taskname: self.taskname.expect("taskname is required"), parameters: self.parameters.unwrap_or_else(|| "{}".to_string()), + parameters_bytes: self.parameters_bytes.unwrap_or_default(), headers: self.headers.unwrap_or_default(), processing_deadline_duration: self.processing_deadline_duration.unwrap_or(0), received_at: self.received_at, diff --git a/src/upkeep.rs b/src/upkeep.rs index 9132d3f5..40e9420b 100644 --- a/src/upkeep.rs +++ b/src/upkeep.rs @@ -662,7 +662,10 @@ mod tests { activation.received_at.unwrap().nanos as u32, ) .expect(""); - activation.parameters = r#"{"a":"b"}"#.into(); + { + #![allow(deprecated)] + activation.parameters = r#"{"a":"b"}"#.into(); + } activation.delay = Some(30); records[0].status = InflightActivationStatus::Retry; records[0].delay_until = Some(Utc::now() + Duration::from_secs(30)); @@ -699,7 +702,10 @@ mod tests { let activation_to_check = TaskActivation::decode(&records[0].activation as &[u8]).unwrap(); assert_eq!(activation.taskname, activation_to_check.taskname); assert_eq!(activation.namespace, activation_to_check.namespace); - assert_eq!(activation.parameters, activation_to_check.parameters); + { + #![allow(deprecated)] + assert_eq!(activation.parameters, activation_to_check.parameters); + } // received_at should be set be later than the original activation assert!( activation.received_at.unwrap().seconds @@ -973,6 +979,7 @@ mod tests { #[case::sqlite("sqlite")] #[case::postgres("postgres")] async fn test_remove_at_remove_failed_publish_to_kafka(#[case] adapter: &str) { + #![allow(deprecated)] let config = create_integration_config(); let runtime_config = Arc::new(RuntimeConfigManager::new(None).await); reset_topic(config.clone()).await; @@ -1019,7 +1026,10 @@ mod tests { let activation_to_check = TaskActivation::decode(&records[0].activation as &[u8]).unwrap(); assert_eq!(activation.id, activation_to_check.id); // DLQ should retain parameters of original task - assert_eq!(activation.parameters, activation_to_check.parameters); + { + #![allow(deprecated)] + assert_eq!(activation.parameters, activation_to_check.parameters); + } } #[tokio::test] diff --git a/uv.lock b/uv.lock index db494d19..040b1df9 100644 --- a/uv.lock +++ b/uv.lock @@ -1,5 +1,5 @@ version = 1 -revision = 2 +revision = 3 requires-python = ">=3.11" resolution-markers = [ "sys_platform == 'darwin' or sys_platform == 'linux'", @@ -301,6 +301,28 @@ wheels = [ { url = "https://pypi.devinfra.sentry.io/wheels/mccabe-0.7.0-py2.py3-none-any.whl", hash = "sha256:6c2d30ab6be0e4a46919781807b4f0d834ebdd6c6e3dca0bda5a15f863427b6e" }, ] +[[package]] +name = "msgpack" +version = "1.1.1" +source = { registry = "https://pypi.devinfra.sentry.io/simple" } +wheels = [ + { url = "https://pypi.devinfra.sentry.io/wheels/msgpack-1.1.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:71ef05c1726884e44f8b1d1773604ab5d4d17729d8491403a705e649116c9558" }, + { url = "https://pypi.devinfra.sentry.io/wheels/msgpack-1.1.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:36043272c6aede309d29d56851f8841ba907a1a3d04435e43e8a19928e243c1d" }, + { url = "https://pypi.devinfra.sentry.io/wheels/msgpack-1.1.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a32747b1b39c3ac27d0670122b57e6e57f28eefb725e0b625618d1b59bf9d1e0" }, + { url = "https://pypi.devinfra.sentry.io/wheels/msgpack-1.1.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8a8b10fdb84a43e50d38057b06901ec9da52baac6983d3f709d8507f3889d43f" }, + { url = "https://pypi.devinfra.sentry.io/wheels/msgpack-1.1.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:ae497b11f4c21558d95de9f64fff7053544f4d1a17731c866143ed6bb4591238" }, + { url = "https://pypi.devinfra.sentry.io/wheels/msgpack-1.1.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:33be9ab121df9b6b461ff91baac6f2731f83d9b27ed948c5b9d1978ae28bf157" }, + { url = "https://pypi.devinfra.sentry.io/wheels/msgpack-1.1.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6f64ae8fe7ffba251fecb8408540c34ee9df1c26674c50c4544d72dbf792e5ce" }, + { url = "https://pypi.devinfra.sentry.io/wheels/msgpack-1.1.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a494554874691720ba5891c9b0b39474ba43ffb1aaf32a5dac874effb1619e1a" }, + { url = "https://pypi.devinfra.sentry.io/wheels/msgpack-1.1.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:3765afa6bd4832fc11c3749be4ba4b69a0e8d7b728f78e68120a157a4c5d41f0" }, + { url = "https://pypi.devinfra.sentry.io/wheels/msgpack-1.1.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:8ddb2bcfd1a8b9e431c8d6f4f7db0773084e107730ecf3472f1dfe9ad583f3d9" }, + { url = "https://pypi.devinfra.sentry.io/wheels/msgpack-1.1.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:196a736f0526a03653d829d7d4c5500a97eea3648aebfd4b6743875f28aa2af8" }, + { url = "https://pypi.devinfra.sentry.io/wheels/msgpack-1.1.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9d592d06e3cc2f537ceeeb23d38799c6ad83255289bb84c2e5792e5a8dea268a" }, + { url = "https://pypi.devinfra.sentry.io/wheels/msgpack-1.1.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:9bef030f66d0376aba8aed8e3fe05eab09abef8b6afe27182fac4ffd87168dcf" }, + { url = "https://pypi.devinfra.sentry.io/wheels/msgpack-1.1.1-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_31_aarch64.whl", hash = "sha256:cb8908c79b04884a6a6cfdf8b239380fb485b8dd61ae25a8bffb28ca00fb4361" }, + { url = "https://pypi.devinfra.sentry.io/wheels/msgpack-1.1.1-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_31_x86_64.whl", hash = "sha256:e4b09afd7f8c72549735d183cf973890ef3b6f0dcf6148d5572a646b2f29b169" }, +] + [[package]] name = "mypy" version = "1.17.1" @@ -546,7 +568,7 @@ wheels = [ [[package]] name = "sentry-protos" -version = "0.8.6" +version = "0.8.13" source = { registry = "https://pypi.devinfra.sentry.io/simple" } dependencies = [ { name = "grpc-stubs", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, @@ -554,7 +576,7 @@ dependencies = [ { name = "protobuf", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, ] wheels = [ - { url = "https://pypi.devinfra.sentry.io/wheels/sentry_protos-0.8.6-py3-none-any.whl", hash = "sha256:bffd32fae9df928a1d4fc519c1ff02fa3ba8fac7bf8ba0ea6495b1eb353575ef" }, + { url = "https://pypi.devinfra.sentry.io/wheels/sentry_protos-0.8.13-py3-none-any.whl", hash = "sha256:8cebc86dbb20cea0157f488e0509bb854b8ec03f840ceb09445b2c4c2ee27d4f" }, ] [[package]] @@ -656,6 +678,7 @@ dependencies = [ { name = "confluent-kafka", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, { name = "cronsim", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, { name = "grpcio", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "msgpack", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, { name = "orjson", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, { name = "protobuf", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, { name = "redis", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, @@ -693,12 +716,13 @@ requires-dist = [ { name = "confluent-kafka", specifier = ">=2.3.0" }, { name = "cronsim", specifier = ">=2.6" }, { name = "grpcio", specifier = ">=1.67.0" }, + { name = "msgpack", specifier = ">=1.0.0" }, { name = "orjson", specifier = ">=3.10.10" }, { name = "protobuf", specifier = ">=5.28.3" }, { name = "redis", specifier = ">=3.4.1" }, { name = "redis-py-cluster", specifier = ">=2.1.0" }, { name = "sentry-arroyo", specifier = ">=2.38.7" }, - { name = "sentry-protos", specifier = ">=0.8.5" }, + { name = "sentry-protos", specifier = ">=0.8.13" }, { name = "sentry-sdk", extras = ["http2"], specifier = ">=2.43.0" }, { name = "setuptools", marker = "extra == 'examples'", specifier = ">=80.0" }, { name = "zstandard", specifier = ">=0.18.0" },