Skip to content

Commit eca23b8

Browse files
ref(api): Abstract base classes (getsentry#2667)
We have some classes in the SDK that essentially function as abstract classes, since they have some methods that raise NotImplementedError when called, since they are intended to be overriden by a subclass. Now that all supported Python versions support abstract base classes (ABCs), we should refactor these classes to be ABCs. Making this change will explicitly indicate which methods need to be overridden by a subclass. Changing a public class into an ABC is a breaking change, since instantiating the class, or any subclass that hasn't overridden all abstract methods, will raise an error. Therefore, I have added this (draft) PR to the SDK 2.0 milestone. * Convert `Transport` class into an ABC * ABC metrics * ABC scheduler * ABC integration * RequestExtractor comment * Deprecate `stop_profiling` and stop calling it This change is required because otherwise, the linter complains about the `stop_profiling` being an empty concrete method in an abstract class. * Actually, let's remove `stop_profiling` * Add ABCs to migration guide * fix mypy * Make docstring more relevant to users * `ensure_running` no longer abstract * Fix mypy
1 parent 656ac9d commit eca23b8

6 files changed

Lines changed: 67 additions & 31 deletions

File tree

‎MIGRATION_GUIDE.md‎

Lines changed: 20 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,18 @@ Looking to upgrade from Sentry SDK 1.x to 2.x? Here's a comprehensive list of wh
1515
- The `reraise` function was moved from `sentry_sdk._compat` to `sentry_sdk.utils`.
1616
- Moved the contents of `tracing_utils_py3.py` to `tracing_utils.py`. The `start_child_span_decorator` is now in `sentry_sdk.tracing_utils`.
1717
- The actual implementation of `get_current_span` was moved to `sentry_sdk.tracing_utils`. `sentry_sdk.get_current_span` is still accessible as part of the top-level API.
18+
- The classes listed in the table below are now abstract base classes. Therefore, they can no longer be instantiated. Subclasses can only be instantiated if they implement all of the abstract methods.
19+
<details>
20+
<summary><b>Show table</b></summary>
21+
22+
| Class | Abstract methods |
23+
| ------------------------------------- | -------------------------------------- |
24+
| `sentry_sdk.integrations.Integration` | `setup_once` |
25+
| `sentry_sdk.metrics.Metric` | `add`, `serialize_value`, and `weight` |
26+
| `sentry_sdk.profiler.Scheduler` | `setup` and `teardown` |
27+
| `sentry_sdk.transport.Transport` | `capture_envelope` |
28+
29+
</details>
1830

1931
## Removed
2032

@@ -33,16 +45,17 @@ Looking to upgrade from Sentry SDK 1.x to 2.x? Here's a comprehensive list of wh
3345
- Removed `sentry_sdk.utils.Auth.store_api_url`.
3446
- `sentry_sdk.utils.Auth.get_api_url`'s now accepts a `sentry_sdk.consts.EndpointType` enum instead of a string as its only parameter. We recommend omitting this argument when calling the function, since the parameter's default value is the only possible `sentry_sdk.consts.EndpointType` value. The parameter exists for future compatibility.
3547
- Removed `tracing_utils_py2.py`. The `start_child_span_decorator` is now in `sentry_sdk.tracing_utils`.
48+
- Removed the `sentry_sdk.profiler.Scheduler.stop_profiling` method. Any calls to this method can simply be removed, since this was a no-op method.
3649

3750
## Deprecated
3851

3952
- `profiler_mode` and `profiles_sample_rate` have been deprecated as `_experiments` options. Use them as top level options instead:
40-
```python
41-
sentry_sdk.init(
42-
...,
43-
profiler_mode="thread",
44-
profiles_sample_rate=1.0,
45-
)
46-
```
53+
```python
54+
sentry_sdk.init(
55+
...,
56+
profiler_mode="thread",
57+
profiles_sample_rate=1.0,
58+
)
59+
```
4760
- Deprecated `sentry_sdk.transport.Transport.capture_event`. Please use `sentry_sdk.transport.Transport.capture_envelope`, instead.
4861
- Passing a function to `sentry_sdk.init`'s `transport` keyword argument has been deprecated. If you wish to provide a custom transport, please pass a `sentry_sdk.transport.Transport` instance or a subclass.

‎sentry_sdk/integrations/__init__.py‎

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
from abc import ABC, abstractmethod
12
from threading import Lock
23

34
from sentry_sdk._types import TYPE_CHECKING
@@ -177,7 +178,7 @@ class DidNotEnable(Exception): # noqa: N818
177178
"""
178179

179180

180-
class Integration:
181+
class Integration(ABC):
181182
"""Baseclass for all integrations.
182183
183184
To accept options for an integration, implement your own constructor that
@@ -191,6 +192,7 @@ class Integration:
191192
"""String unique ID of integration type"""
192193

193194
@staticmethod
195+
@abstractmethod
194196
def setup_once():
195197
# type: () -> None
196198
"""
@@ -203,4 +205,4 @@ def setup_once():
203205
Inside those hooks `Integration.current` can be used to access the
204206
instance again.
205207
"""
206-
raise NotImplementedError()
208+
pass

‎sentry_sdk/integrations/_wsgi_common.py‎

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,15 @@ def request_body_within_bounds(client, content_length):
5151

5252

5353
class RequestExtractor:
54+
"""
55+
Base class for request extraction.
56+
"""
57+
58+
# It does not make sense to make this class an ABC because it is not used
59+
# for typing, only so that child classes can inherit common methods from
60+
# it. Only some child classes implement all methods that raise
61+
# NotImplementedError in this class.
62+
5463
def __init__(self, request):
5564
# type: (Any) -> None
5665
self.request = request

‎sentry_sdk/metrics.py‎

Lines changed: 18 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
import threading
77
import time
88
import zlib
9+
from abc import ABC, abstractmethod
910
from contextlib import contextmanager
1011
from datetime import datetime, timezone
1112
from functools import wraps, partial
@@ -119,23 +120,29 @@ def new_func(*args, **kwargs):
119120
return new_func
120121

121122

122-
class Metric:
123+
class Metric(ABC):
123124
__slots__ = ()
124125

126+
@abstractmethod
127+
def __init__(self, first):
128+
# type: (MetricValue) -> None
129+
pass
130+
125131
@property
132+
@abstractmethod
126133
def weight(self):
127-
# type: (...) -> int
128-
raise NotImplementedError()
134+
# type: () -> int
135+
pass
129136

130-
def add(
131-
self, value # type: MetricValue
132-
):
133-
# type: (...) -> None
134-
raise NotImplementedError()
137+
@abstractmethod
138+
def add(self, value):
139+
# type: (MetricValue) -> None
140+
pass
135141

142+
@abstractmethod
136143
def serialize_value(self):
137-
# type: (...) -> Iterable[FlushedMetricValue]
138-
raise NotImplementedError()
144+
# type: () -> Iterable[FlushedMetricValue]
145+
pass
139146

140147

141148
class CounterMetric(Metric):
@@ -333,7 +340,7 @@ def _encode_locations(timestamp, code_locations):
333340
"g": GaugeMetric,
334341
"d": DistributionMetric,
335342
"s": SetMetric,
336-
}
343+
} # type: dict[MetricType, type[Metric]]
337344

338345
# some of these are dumb
339346
TIMING_FUNCTIONS = {

‎sentry_sdk/profiler.py‎

Lines changed: 12 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@
3333
import threading
3434
import time
3535
import uuid
36+
from abc import ABC, abstractmethod
3637
from collections import deque
3738

3839
import sentry_sdk
@@ -584,7 +585,6 @@ def stop(self):
584585
assert self.scheduler, "No scheduler specified"
585586
logger.debug("[Profiling] Stopping profile")
586587
self.active = False
587-
self.scheduler.stop_profiling(self)
588588
self.stop_ns = nanosecond_time()
589589

590590
def __enter__(self):
@@ -750,7 +750,7 @@ def valid(self):
750750
return True
751751

752752

753-
class Scheduler:
753+
class Scheduler(ABC):
754754
mode = "unknown" # type: ProfilerMode
755755

756756
def __init__(self, frequency):
@@ -772,27 +772,30 @@ def __exit__(self, ty, value, tb):
772772
# type: (Optional[Any], Optional[Any], Optional[Any]) -> None
773773
self.teardown()
774774

775+
@abstractmethod
775776
def setup(self):
776777
# type: () -> None
777-
raise NotImplementedError
778+
pass
778779

780+
@abstractmethod
779781
def teardown(self):
780782
# type: () -> None
781-
raise NotImplementedError
783+
pass
782784

783785
def ensure_running(self):
784786
# type: () -> None
785-
raise NotImplementedError
787+
"""
788+
Ensure the scheduler is running. By default, this method is a no-op.
789+
The method should be overridden by any implementation for which it is
790+
relevant.
791+
"""
792+
return None
786793

787794
def start_profiling(self, profile):
788795
# type: (Profile) -> None
789796
self.ensure_running()
790797
self.new_profiles.append(profile)
791798

792-
def stop_profiling(self, profile):
793-
# type: (Profile) -> None
794-
pass
795-
796799
def make_sampler(self):
797800
# type: () -> Callable[..., None]
798801
cwd = os.getcwd()

‎sentry_sdk/transport.py‎

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
from abc import ABC, abstractmethod
12
import io
23
import warnings
34
import urllib3
@@ -33,7 +34,7 @@
3334
DataCategory = Optional[str]
3435

3536

36-
class Transport:
37+
class Transport(ABC):
3738
"""Baseclass for all transports.
3839
3940
A transport is used to send an event to sentry.
@@ -72,6 +73,7 @@ def capture_event(
7273
envelope.add_event(event)
7374
self.capture_envelope(envelope)
7475

76+
@abstractmethod
7577
def capture_envelope(
7678
self, envelope # type: Envelope
7779
):
@@ -83,7 +85,7 @@ def capture_envelope(
8385
submitted to Sentry. We use it to send all event data (including errors,
8486
transactions, crons checkins, etc.) to Sentry.
8587
"""
86-
raise NotImplementedError()
88+
pass
8789

8890
def flush(
8991
self,

0 commit comments

Comments
 (0)