Skip to content

Commit f307432

Browse files
committed
ref(data-collection): Rework DataCollection as a TypedDict, drop accessors
Replace the class-based DataCollection/KeyValueCollectionBehavior/ GenAICollection/HttpHeadersCollection API with plain TypedDicts (DataCollection, DataCollectionUserOptions, and per-category *CollectionBehaviour/*UserOptions types) defined in _types.py. Collection mode strings move from camelCase (denyList/allowList) to snake_case (deny_list/allow_list) to match Python convention, since this is a Python-only deviation from the spec that is never serialized to Sentry. Drop the should_collect_user_info/should_collect_gen_ai_inputs/ should_collect_gen_ai_outputs accessor methods and their module-level shortcuts in favor of reading data_collection fields directly, and drop the public sentry_sdk.DataCollection/GenAICollection/ HttpHeadersCollection/KeyValueCollectionBehavior exports. Revert the Unreleased CHANGELOG entry and README example for the prior class-based API.
1 parent 98022a4 commit f307432

9 files changed

Lines changed: 476 additions & 562 deletions

File tree

CHANGELOG.md

Lines changed: 0 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,5 @@
11
# Changelog
22

3-
## Unreleased
4-
5-
### Features
6-
7-
- Add the `data_collection` option, a structured configuration that supersedes `send_default_pii` for controlling what data integrations collect automatically (user identity, cookies, HTTP headers, query params, HTTP bodies, generative AI inputs/outputs, stack frame variables, source context). See the [Data Collection spec](https://develop.sentry.dev/sdk/foundations/client/data-collection/).
8-
- Adds `sentry_sdk.DataCollection`, `KeyValueCollectionBehavior`, `HttpHeadersCollection`, and `GenAICollection`.
9-
- When `data_collection` is not set, behavior is derived from `send_default_pii` (now deprecated), so upgrading without configuring `data_collection` changes nothing.
10-
- `frame_context_lines` is now configurable (previously hardcoded to 5); AI integrations' `include_prompts` becomes a per-integration override of `data_collection.gen_ai`.
11-
123
## 2.63.0
134

145
### Bug Fixes 🐛

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ sentry_sdk.init(
5353
# To disable sending user data and HTTP request/response bodies, uncomment
5454
# the line below. For more info visit:
5555
# https://docs.sentry.io/platforms/python/configuration/options/#data_collection
56-
# data_collection=sentry_sdk.DataCollection(user_info=False, http_bodies=[]),
56+
# data_collection={"user_info": False, "http_bodies": []},
5757
)
5858
```
5959

sentry_sdk/__init__.py

Lines changed: 0 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,6 @@
22

33
from sentry_sdk.scope import Scope # isort: skip
44
from sentry_sdk.client import Client # isort: skip
5-
from sentry_sdk.data_collection import ( # isort: skip
6-
DataCollection,
7-
GenAICollection,
8-
HttpHeadersCollection,
9-
KeyValueCollectionBehavior,
10-
)
115
from sentry_sdk.consts import VERSION
126
from sentry_sdk.transport import HttpTransport, Transport
137

@@ -17,10 +11,6 @@
1711
"Hub",
1812
"Scope",
1913
"Client",
20-
"DataCollection",
21-
"GenAICollection",
22-
"HttpHeadersCollection",
23-
"KeyValueCollectionBehavior",
2414
"Transport",
2515
"HttpTransport",
2616
"VERSION",

sentry_sdk/_types.py

Lines changed: 60 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -141,7 +141,7 @@ def substituted_because_contains_sensitive_data(cls) -> "AnnotatedValue":
141141
from collections.abc import Container, MutableMapping, Sequence
142142
from datetime import datetime
143143
from types import TracebackType
144-
from typing import Any, Callable, Dict, Mapping, NotRequired, Optional, Type
144+
from typing import Any, Callable, Dict, List, Mapping, NotRequired, Optional, Type
145145

146146
from typing_extensions import Literal, TypedDict
147147

@@ -152,6 +152,65 @@ class SDKInfo(TypedDict):
152152
version: str
153153
packages: "Sequence[Mapping[str, str]]"
154154

155+
class KeyValueCollectionBehaviour(TypedDict):
156+
mode: 'Literal["off", "deny_list", "allow_list"]'
157+
terms: "NotRequired[List[str]]"
158+
159+
class GenAICollectionUserOptions(TypedDict, total=False):
160+
inputs: bool
161+
outputs: bool
162+
163+
class GenAICollectionBehaviour(TypedDict):
164+
inputs: bool
165+
outputs: bool
166+
167+
class GraphQLCollectionUserOptions(TypedDict, total=False):
168+
document: bool
169+
variables: bool
170+
171+
class GraphQLCollectionBehaviour(TypedDict):
172+
document: bool
173+
variables: bool
174+
175+
class DatabaseCollectionUserOptions(TypedDict, total=False):
176+
query_params: bool
177+
178+
class DatabaseCollectionBehaviour(TypedDict):
179+
query_params: bool
180+
181+
class HttpHeadersCollectionUserOptions(TypedDict, total=False):
182+
request: "KeyValueCollectionBehaviour"
183+
response: "KeyValueCollectionBehaviour"
184+
185+
class HttpHeadersCollectionBehaviour(TypedDict):
186+
request: "KeyValueCollectionBehaviour"
187+
response: "KeyValueCollectionBehaviour"
188+
189+
class DataCollectionUserOptions(TypedDict, total=False):
190+
user_info: bool
191+
cookies: "KeyValueCollectionBehaviour"
192+
http_headers: "HttpHeadersCollectionBehaviour"
193+
http_bodies: "List[str]"
194+
query_params: "KeyValueCollectionBehaviour"
195+
graphql: "GraphQLCollectionBehaviour"
196+
gen_ai: "GenAICollectionBehaviour"
197+
database: "DatabaseCollectionBehaviour"
198+
stack_frame_variables: bool
199+
frame_context_lines: int
200+
201+
class DataCollection(TypedDict):
202+
provided_by_user: bool
203+
user_info: bool
204+
cookies: "KeyValueCollectionBehaviour"
205+
http_headers: "HttpHeadersCollectionBehaviour"
206+
http_bodies: "List[str]"
207+
query_params: "KeyValueCollectionBehaviour"
208+
graphql: "GraphQLCollectionBehaviour"
209+
gen_ai: "GenAICollectionBehaviour"
210+
database: "DatabaseCollectionBehaviour"
211+
stack_frame_variables: bool
212+
frame_context_lines: int
213+
155214
# "critical" is an alias of "fatal" recognized by Relay
156215
LogLevelStr = Literal["fatal", "critical", "error", "warning", "info", "debug"]
157216

sentry_sdk/client.py

Lines changed: 18 additions & 69 deletions
Original file line numberDiff line numberDiff line change
@@ -24,8 +24,6 @@
2424
ClientConstructor,
2525
)
2626
from sentry_sdk.data_collection import (
27-
OFF_DATA_COLLECTION,
28-
DataCollection,
2927
_map_from_send_default_pii,
3028
resolve_data_collection,
3129
)
@@ -76,6 +74,7 @@
7674
from sentry_sdk._log_batcher import LogBatcher
7775
from sentry_sdk._metrics_batcher import MetricsBatcher
7876
from sentry_sdk._types import (
77+
DataCollection,
7978
Event,
8079
EventDataCategory,
8180
Hint,
@@ -355,7 +354,7 @@ def _get_options(*args: "Optional[str]", **kwargs: "Any") -> "Dict[str, Any]":
355354

356355
if rv["event_scrubber"] is None:
357356
rv["event_scrubber"] = EventScrubber(
358-
send_default_pii=rv["data_collection"].user_info
357+
send_default_pii=rv["data_collection"]["user_info"]
359358
)
360359

361360
if rv["socket_options"] and not isinstance(rv["socket_options"], list):
@@ -392,6 +391,12 @@ def _get_options(*args: "Optional[str]", **kwargs: "Any") -> "Dict[str, Any]":
392391
# Older Python versions
393392
module_not_found_error = ImportError # type: ignore
394393

394+
_DISABLED_DATA_COLLECTION_CONFIG = _map_from_send_default_pii(
395+
send_default_pii=False,
396+
include_local_variables=True,
397+
include_source_context=True,
398+
)
399+
395400

396401
class BaseClient:
397402
"""
@@ -433,20 +438,7 @@ def should_send_default_pii(self) -> bool:
433438

434439
@property
435440
def data_collection(self) -> "DataCollection":
436-
return OFF_DATA_COLLECTION
437-
438-
def should_collect_user_info(self) -> bool:
439-
return False
440-
441-
def should_collect_gen_ai_inputs(
442-
self, include_prompts: "Optional[bool]" = None
443-
) -> bool:
444-
return False
445-
446-
def should_collect_gen_ai_outputs(
447-
self, include_prompts: "Optional[bool]" = None
448-
) -> bool:
449-
return False
441+
return _DISABLED_DATA_COLLECTION_CONFIG
450442

451443
def is_active(self) -> bool:
452444
"""
@@ -639,14 +631,16 @@ def _record_lost_event(
639631
self.options["profiles_sampler"] = sample_all
640632
# data_collection was resolved in _get_options() before this
641633
# spotlight override flipped send_default_pii on. Re-derive it so
642-
# the should_collect_* accessors agree with should_send_default_pii()
643-
# in DSN-less spotlight mode (only when the user did not set
634+
# data_collection agrees with should_send_default_pii() in
635+
# DSN-less spotlight mode (only when the user did not set
644636
# data_collection explicitly).
645-
if not self.options["data_collection"].explicit:
637+
if not self.options["data_collection"]["provided_by_user"]:
646638
self.options["data_collection"] = _map_from_send_default_pii(
647-
True,
648-
self.options["include_local_variables"] is not False,
649-
self.options["include_source_context"] is not False,
639+
send_default_pii=True,
640+
include_local_variables=self.options["include_local_variables"]
641+
is not False,
642+
include_source_context=self.options["include_source_context"]
643+
is not False,
650644
)
651645

652646
self.session_flusher = SessionFlusher(capture_func=_capture_envelope)
@@ -764,52 +758,7 @@ def data_collection(self) -> "DataCollection":
764758
Returns the resolved :class:`~sentry_sdk.data_collection.DataCollection`
765759
config for this client.
766760
"""
767-
dc = self.options.get("data_collection")
768-
return dc if dc is not None else OFF_DATA_COLLECTION
769-
770-
def should_collect_user_info(self) -> bool:
771-
"""
772-
Returns whether the SDK should automatically populate ``user.*`` fields
773-
(id, email, username, ip_address) from instrumentation.
774-
"""
775-
return bool(self.data_collection.user_info)
776-
777-
def should_collect_gen_ai_inputs(
778-
self, include_prompts: "Optional[bool]" = None
779-
) -> bool:
780-
"""
781-
Returns whether the SDK should collect generative AI input content.
782-
783-
``include_prompts`` is the integration-level override (if set, it takes
784-
precedence over the global ``data_collection.gen_ai.inputs`` setting).
785-
"""
786-
return self._should_collect_gen_ai_content("inputs", include_prompts)
787-
788-
def should_collect_gen_ai_outputs(
789-
self, include_prompts: "Optional[bool]" = None
790-
) -> bool:
791-
"""
792-
Returns whether the SDK should collect generative AI output content.
793-
794-
``include_prompts`` is the integration-level override (if set, it takes
795-
precedence over the global ``data_collection.gen_ai.outputs`` setting).
796-
"""
797-
return self._should_collect_gen_ai_content("outputs", include_prompts)
798-
799-
def _should_collect_gen_ai_content(
800-
self, direction: str, include_prompts: "Optional[bool]"
801-
) -> bool:
802-
dc = self.data_collection
803-
if dc.explicit:
804-
# Integration-level override wins over the global gen_ai setting.
805-
if include_prompts is not None:
806-
return include_prompts
807-
return bool(getattr(dc.gen_ai, direction))
808-
# Legacy (data_collection not set): preserve the historical gate
809-
# `should_send_default_pii() and integration.include_prompts`.
810-
# `include_prompts is None` means "no integration-level override", which
811-
# falls back to the legacy default of True (collect when PII is on).
812-
return self.should_send_default_pii() and (include_prompts is not False)
761+
return self.options["data_collection"]
813762

814763
@property
815764
def dsn(self) -> "Optional[str]":

sentry_sdk/consts.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ class CompressionAlgo(Enum):
4545
from sentry_sdk._types import (
4646
BreadcrumbProcessor,
4747
ContinuousProfilerMode,
48+
DataCollectionUserOptions,
4849
Event,
4950
EventProcessor,
5051
Hint,
@@ -56,7 +57,6 @@ class CompressionAlgo(Enum):
5657
TracesSampler,
5758
TransactionProcessor,
5859
)
59-
from sentry_sdk.data_collection import DataCollection
6060

6161
# Experiments are feature flags to enable and disable certain unstable SDK
6262
# functionality. Changing them from the defaults (`None`) in production
@@ -1273,7 +1273,7 @@ def __init__(
12731273
transport_queue_size: int = DEFAULT_QUEUE_SIZE,
12741274
sample_rate: float = 1.0,
12751275
send_default_pii: "Optional[bool]" = None,
1276-
data_collection: "Optional[Union[DataCollection, Dict[str, Any]]]" = None,
1276+
data_collection: "Optional[DataCollectionUserOptions]" = None,
12771277
http_proxy: "Optional[str]" = None,
12781278
https_proxy: "Optional[str]" = None,
12791279
ignore_errors: "Sequence[Union[type, str]]" = [], # noqa: B006
@@ -1432,12 +1432,12 @@ def __init__(
14321432
Use `data_collection` instead. `send_default_pii` is still honored when `data_collection` is not set.
14331433
14341434
:param data_collection: Structured configuration controlling what data integrations collect automatically,
1435-
superseding `send_default_pii`. Pass a dict or a :class:`sentry_sdk.DataCollection` instance to enable or
1435+
superseding `send_default_pii`. Pass a dict to enable or
14361436
restrict collection per category (user identity, cookies, HTTP headers/bodies, query params, generative AI
14371437
inputs/outputs, stack frame variables, source context).
14381438
14391439
When `data_collection` is set, omitted fields use their defaults (most categories are collected, with the
1440-
sensitive denylist scrubbing values). When it is not set, the SDK derives behavior from `send_default_pii`
1440+
sensitive denylist scrubbing values). When it is not set, the SDK derives behaviour from `send_default_pii`
14411441
so that upgrading without configuring `data_collection` changes nothing. If both are set, `data_collection`
14421442
takes precedence.
14431443

0 commit comments

Comments
 (0)