Skip to content

Commit 4ea3a83

Browse files
authored
ref(data-collection)!: Drop send_default_pii mapping and provided_by_user (#7883)
Remove `_map_from_send_default_pii` and the `provided_by_user` flag from the resolved `DataCollection`. `data_collection` is now always resolved from its explicit options (with spec defaults), so the `send_default_pii` fallback branches are no longer needed. - Delete `has_data_collection_enabled` and read `data_collection` directly in `get_lines_from_file`, `Scope`, outgoing request URL attributes, and the FastAPI and Strawberry integrations. - Drop the DSN-less Spotlight re-derivation of `data_collection`. Fixes PY-2932 Fixes #7882
1 parent a3135ed commit 4ea3a83

19 files changed

Lines changed: 49 additions & 537 deletions

‎sentry_sdk/_types.py‎

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -187,7 +187,6 @@ class DataCollectionUserOptions(TypedDict, total=False):
187187
frame_context_lines: int
188188

189189
class DataCollection(TypedDict):
190-
provided_by_user: bool
191190
user_info: bool
192191
cookies: "KeyValueCollectionBehaviour"
193192
http_headers: "HttpHeadersCollectionBehaviour"

‎sentry_sdk/client.py‎

Lines changed: 4 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,6 @@
1919
ClientConstructor,
2020
)
2121
from sentry_sdk.data_collection import (
22-
_map_from_send_default_pii,
2322
_resolve_data_collection,
2423
)
2524
from sentry_sdk.envelope import Envelope, Item
@@ -153,9 +152,10 @@ class BaseClient:
153152
spotlight: "Optional[SpotlightClient]" = None
154153

155154
def __init__(self, options: "Optional[Dict[str, Any]]" = None) -> None:
156-
self.options: "Dict[str, Any]" = (
157-
options if options is not None else DEFAULT_OPTIONS
158-
)
155+
if options is None:
156+
options = dict(DEFAULT_OPTIONS)
157+
options["data_collection"] = _resolve_data_collection(options)
158+
self.options: "Dict[str, Any]" = options
159159

160160
self.transport: "Optional[Transport]" = None
161161
self.monitor: "Optional[Monitor]" = None
@@ -178,9 +178,6 @@ def dsn(self) -> "Optional[str]":
178178
def parsed_dsn(self) -> "Optional[Dsn]":
179179
return None
180180

181-
def should_send_default_pii(self) -> bool:
182-
return False
183-
184181
def is_active(self) -> bool:
185182
"""
186183
.. versionadded:: 2.0.0
@@ -366,18 +363,8 @@ def _record_lost_event(
366363
self.spotlight = setup_spotlight(self.options)
367364
if self.spotlight is not None and not self.options["dsn"]:
368365
sample_all = lambda *_args, **_kwargs: 1.0
369-
self.options["send_default_pii"] = True
370366
self.options["error_sampler"] = sample_all
371367
self.options["traces_sampler"] = sample_all
372-
# data_collection was resolved in _get_options() before this
373-
# spotlight override flipped send_default_pii on. Re-derive it so
374-
# data_collection agrees with should_send_default_pii() in
375-
# DSN-less spotlight mode (only when the user did not set
376-
# data_collection explicitly).
377-
if not self.options["data_collection"]["provided_by_user"]:
378-
self.options["data_collection"] = _map_from_send_default_pii(
379-
send_default_pii=True
380-
)
381368

382369
self.session_flusher = SessionFlusher(capture_func=_capture_envelope)
383370

@@ -451,14 +438,6 @@ def is_active(self) -> bool:
451438
"""
452439
return True
453440

454-
def should_send_default_pii(self) -> bool:
455-
"""
456-
.. versionadded:: 2.0.0
457-
458-
Returns whether the client should send default PII (Personally Identifiable Information) data to Sentry.
459-
"""
460-
return self.options.get("send_default_pii") or False
461-
462441
@property
463442
def dsn(self) -> "Optional[str]":
464443
"""Returns the configured DSN as string."""

‎sentry_sdk/consts.py‎

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1361,7 +1361,7 @@ def __init__(
13611361
transport_queue_size: int = DEFAULT_QUEUE_SIZE,
13621362
sample_rate: float = 1.0,
13631363
send_default_pii: "Optional[bool]" = None,
1364-
data_collection: "Optional[DataCollectionUserOptions]" = None,
1364+
data_collection: "Optional[DataCollectionUserOptions]" = {},
13651365
http_proxy: "Optional[str]" = None,
13661366
https_proxy: "Optional[str]" = None,
13671367
ignore_errors: "Sequence[Union[type, str]]" = [], # noqa: B006
@@ -1505,12 +1505,10 @@ def __init__(
15051505
managing `Sensitive Data <https://docs.sentry.io/data-management/sensitive-data/>`_.
15061506
15071507
:param data_collection: Structured configuration controlling what data integrations collect
1508-
automatically, superseding `send_default_pii`. Passing a dict opts into the feature; omitted
1509-
fields use their defaults (most categories are collected, with the sensitive denylist
1510-
scrubbing values). When it is not set, the SDK derives behaviour from `send_default_pii` so
1511-
that upgrading changes nothing. Restrict collection per category (user identity, cookies,
1508+
automatically. Omitted fields use their defaults (most categories are collected, with the sensitive denylist
1509+
scrubbing values). Restrict collection per category (user identity, cookies,
15121510
HTTP headers/bodies, query params, generative AI inputs/outputs, stack frame variables,
1513-
source context). If `send_default_pii` is also set, `data_collection` takes precedence.
1511+
source context).
15141512
15151513
Example::
15161514

‎sentry_sdk/data_collection.py‎

Lines changed: 10 additions & 69 deletions
Original file line numberDiff line numberDiff line change
@@ -5,21 +5,10 @@
55
"Data Collection" spec
66
(https://develop.sentry.dev/sdk/foundations/client/data-collection/).
77
8-
``data_collection`` supersedes the single ``send_default_pii`` boolean with a
9-
structured configuration that lets users enable or restrict automatically
8+
``data_collection`` lets users enable or restrict automatically
109
collected data by category (user identity, cookies, HTTP headers, URL query params,
1110
HTTP bodies, generative AI inputs/outputs, stack frame variables, source
1211
context).
13-
14-
Resolution precedence (see :func:`_resolve_data_collection`):
15-
16-
* ``data_collection`` set, ``send_default_pii`` unset -> honour ``data_collection``
17-
using the spec defaults for any omitted field.
18-
* ``send_default_pii`` set, ``data_collection`` unset -> derive a
19-
resolved ``DataCollection`` that mirrors what ``send_default_pii`` collects today.
20-
* neither set -> treated as ``send_default_pii=False``.
21-
* both set -> ``data_collection`` wins (it is the single source of truth); a
22-
``DeprecationWarning`` is emitted for ``send_default_pii``.
2312
"""
2413

2514
from typing import TYPE_CHECKING, Any, Dict, List, Mapping, Optional, Union, cast
@@ -149,37 +138,7 @@ def _apply_key_value_collection_filtering(
149138
return result
150139

151140

152-
def _map_from_send_default_pii(*, send_default_pii: bool) -> "DataCollection":
153-
"""
154-
Build a fully-resolved ``DataCollection`` dict that mirrors the data
155-
``send_default_pii`` collects today. Used when ``data_collection`` is not
156-
provided explicitly.
157-
"""
158-
terms = [] if send_default_pii else ["forwarded", "-ip", "remote-", "via", "-user"]
159-
160-
return {
161-
"provided_by_user": False,
162-
"user_info": send_default_pii,
163-
"cookies": {"mode": "denylist", "terms": terms},
164-
# Headers are collected in both PII modes today (sensitive ones filtered
165-
# when PII is off), so this never maps to "off".
166-
"http_headers": {
167-
"request": {"mode": "denylist", "terms": terms},
168-
},
169-
# Bodies are collected regardless of PII today, bounded by
170-
# ``max_request_body_size``.
171-
"http_bodies": list(_ALL_HTTP_BODY_TYPES),
172-
"url_query_params": {"mode": "denylist", "terms": terms},
173-
"graphql": {"document": send_default_pii, "variables": send_default_pii},
174-
"gen_ai": {"inputs": send_default_pii, "outputs": send_default_pii},
175-
"database_query_data": send_default_pii,
176-
"queues": send_default_pii,
177-
"stack_frame_variables": True,
178-
"frame_context_lines": _DEFAULT_FRAME_CONTEXT_LINES,
179-
}
180-
181-
182-
def _resolve_explicit(
141+
def _resolve(
183142
d: "dict[str, Any]",
184143
) -> "DataCollection":
185144
"""
@@ -214,7 +173,6 @@ def _resolve_explicit(
214173
)
215174

216175
return {
217-
"provided_by_user": True,
218176
"user_info": d.get("user_info", True),
219177
"cookies": _kvcb_from_value(d.get("cookies") or {}),
220178
"http_headers": _http_headers_from_value(d.get("http_headers") or {}),
@@ -280,37 +238,20 @@ def _resolve_data_collection(options: "Dict[str, Any]") -> "DataCollection":
280238
"""
281239
Resolve the effective ``DataCollection`` dict from client ``options``.
282240
283-
Reads ``data_collection``, ``send_default_pii`` and returns a fully-resolved
284-
dict with concrete values for every field.
241+
Reads ``data_collection`` and returns a fully-resolved dict with concrete values for every field.
285242
286243
``data_collection`` must be a plain ``dict``.
287244
288245
Must be called exactly once per options dict, before ``client._get_options``
289-
overwrites ``options["data_collection"]`` with the resolved result. Feeding an
290-
already-resolved dict back in would flip ``provided_by_user`` to ``True``.
246+
overwrites ``options["data_collection"]`` with the resolved result.
291247
"""
292-
from sentry_sdk.utils import deprecation_warning
293-
294248
user_dc = options.get("data_collection")
295-
if user_dc is None:
296-
user_dc = options.get("_experiments", {}).get("data_collection")
297249

298-
send_default_pii = options.get("send_default_pii")
299-
300-
if user_dc is not None:
301-
if not isinstance(user_dc, dict):
302-
raise TypeError(
303-
"`data_collection` must be a dict, got {!r}.".format(
304-
type(user_dc).__name__
305-
)
306-
)
307-
if send_default_pii is not None:
308-
deprecation_warning(
309-
"`send_default_pii` is deprecated and ignored when "
310-
"`data_collection` is set.",
311-
)
312-
return _resolve_explicit(
313-
user_dc,
250+
if not isinstance(user_dc, dict):
251+
raise TypeError(
252+
"`data_collection` must be a dict, got {!r}.".format(type(user_dc).__name__)
314253
)
315254

316-
return _map_from_send_default_pii(send_default_pii=bool(send_default_pii))
255+
return _resolve(
256+
user_dc,
257+
)

‎sentry_sdk/integrations/fastapi.py‎

Lines changed: 7 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@
1212
get_current_span,
1313
)
1414
from sentry_sdk.utils import (
15-
has_data_collection_enabled,
1615
parse_version,
1716
)
1817

@@ -116,12 +115,10 @@ def event_processor(event: "Event", hint: "Dict[str, Any]") -> "Event":
116115
if "cookies" in info:
117116
request_info["cookies"] = info["cookies"]
118117
if "data" in info:
119-
attach_request_data = True
120-
if has_data_collection_enabled(client.options):
121-
attach_request_data = (
122-
"incoming_request"
123-
in client.options["data_collection"]["http_bodies"]
124-
)
118+
attach_request_data = (
119+
"incoming_request"
120+
in client.options["data_collection"]["http_bodies"]
121+
)
125122

126123
if attach_request_data:
127124
request_info["data"] = info["data"]
@@ -142,12 +139,9 @@ def event_processor(event: "Event", hint: "Dict[str, Any]") -> "Event":
142139
current_span = get_current_span()
143140

144141
if type(current_span) is Span:
145-
attach_request_data = True
146-
if has_data_collection_enabled(client.options):
147-
attach_request_data = (
148-
"incoming_request"
149-
in client.options["data_collection"]["http_bodies"]
150-
)
142+
attach_request_data = (
143+
"incoming_request" in client.options["data_collection"]["http_bodies"]
144+
)
151145

152146
if attach_request_data:
153147
request_body = _get_cached_request_body_attribute(

‎sentry_sdk/integrations/strawberry.py‎

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -166,7 +166,6 @@ def on_operation(self) -> "Generator[None, None, None]":
166166
return
167167

168168
additional_attributes: "dict[str, Any]" = {}
169-
170169
if client.options["data_collection"]["graphql"]["document"]:
171170
additional_attributes["graphql.document"] = self.execution_context.query
172171

@@ -395,7 +394,11 @@ def _make_response_event_processor(
395394
def inner(event: "Event", hint: "dict[str, Any]") -> "Event":
396395
client_options = sentry_sdk.get_client().options
397396
with capture_internal_exceptions():
398-
if "outgoing_response" in client_options["data_collection"]["http_bodies"]:
397+
collect_response = (
398+
"outgoing_response" in client_options["data_collection"]["http_bodies"]
399+
)
400+
401+
if collect_response:
399402
contexts = event.setdefault("contexts", {})
400403
contexts["response"] = {"data": response_data}
401404

‎sentry_sdk/scope.py‎

Lines changed: 1 addition & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,6 @@
4343
event_from_exception,
4444
exc_info_from_error,
4545
format_attribute,
46-
has_data_collection_enabled,
4746
logger,
4847
)
4948

@@ -1443,10 +1442,7 @@ def _apply_user_attributes_to_telemetry(
14431442
attributes = telemetry._attributes
14441443

14451444
client_options = sentry_sdk.get_client().options
1446-
if has_data_collection_enabled(client_options):
1447-
if not client_options["data_collection"]["user_info"] or self._user is None:
1448-
return
1449-
elif not should_send_default_pii() or self._user is None:
1445+
if not client_options["data_collection"]["user_info"] or self._user is None:
14501446
return
14511447

14521448
for attribute_name, user_attribute in (
@@ -1874,11 +1870,6 @@ def use_isolation_scope(isolation_scope: "Scope") -> "Generator[Scope, None, Non
18741870
capture_internal_exception(sys.exc_info())
18751871

18761872

1877-
def should_send_default_pii() -> bool:
1878-
"""Shortcut for `Scope.get_client().should_send_default_pii()`."""
1879-
return Scope.get_client().should_send_default_pii()
1880-
1881-
18821873
# Circular imports
18831874
from sentry_sdk.client import NonRecordingClient
18841875

‎sentry_sdk/tracing_utils.py‎

Lines changed: 7 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,6 @@
2323
_module_in_list,
2424
capture_internal_exceptions,
2525
filename_for_module,
26-
has_data_collection_enabled,
2726
is_sentry_url,
2827
is_valid_sample_rate,
2928
logger,
@@ -185,26 +184,18 @@ def get_url_attributes_legacy(
185184
) -> "Attributes":
186185
"""Build the `url.*` span attributes for an outgoing HTTP request.
187186
188-
The query string is only included when the user has opted into collecting
189-
it, either through `data_collection` (in which case the configured
190-
filtering is applied) or through the legacy `send_default_pii`.
187+
The query string is filtered according to `data_collection`.
191188
"""
192189
attributes: "Attributes" = {}
193190
if parsed_url is None:
194191
return attributes
195192

196-
query: "Optional[str]"
197-
if has_data_collection_enabled(client.options):
198-
query = None
199-
if parsed_url.query:
200-
query = _apply_data_collection_filtering_to_query_string(
201-
query_string=parsed_url.query,
202-
behaviour=client.options["data_collection"]["url_query_params"],
203-
)
204-
elif client.should_send_default_pii():
205-
query = parsed_url.query
206-
else:
207-
return attributes
193+
query: "Optional[str]" = None
194+
if parsed_url.query:
195+
query = _apply_data_collection_filtering_to_query_string(
196+
query_string=parsed_url.query,
197+
behaviour=client.options["data_collection"]["url_query_params"],
198+
)
208199

209200
url_full = parsed_url.url
210201
if query:

‎sentry_sdk/utils.py‎

Lines changed: 1 addition & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,6 @@
6565

6666
from sentry_sdk._types import (
6767
AttributeValue,
68-
DataCollection,
6968
Event,
7069
ExcInfo,
7170
Hint,
@@ -477,11 +476,7 @@ def get_lines_from_file(
477476
) -> "Tuple[List[Annotated[str]], Optional[Annotated[str]], List[Annotated[str]]]":
478477
client_options = sentry_sdk.get_client().options
479478

480-
# This is the default pre-data collection. Should be removed once data collection
481-
# is fully released
482-
context_lines = 5
483-
if has_data_collection_enabled(client_options):
484-
context_lines = client_options["data_collection"]["frame_context_lines"]
479+
context_lines = client_options["data_collection"]["frame_context_lines"]
485480

486481
source = None
487482
if loader is not None and hasattr(loader, "get_source"):
@@ -1910,19 +1905,6 @@ def serialize_item(
19101905
return str(data)
19111906

19121907

1913-
def has_data_collection_enabled(options: "Optional[dict[str, Any]]") -> bool:
1914-
if options is None:
1915-
return False
1916-
1917-
data_collection: "Optional[DataCollection]" = options.get("data_collection")
1918-
# Client options are resolved as part of client initialization, so `data_collection`
1919-
# being None could be that the user just didn't provide it.
1920-
# `provided_by_user` is what actually records whether the user actually configured it.
1921-
return data_collection is not None and data_collection.get(
1922-
"provided_by_user", False
1923-
)
1924-
1925-
19261908
def get_before_send_log(
19271909
options: "Optional[dict[str, Any]]",
19281910
) -> "Optional[Callable[[Log, Hint], Optional[Log]]]":

0 commit comments

Comments
 (0)