Skip to content

Commit 4e69cb7

Browse files
✨ Add Typer integration (#3869)
--------- Co-authored-by: Ivana Kellyer <[email protected]>
1 parent 2666022 commit 4e69cb7

File tree

7 files changed

+135
-1
lines changed

7 files changed

+135
-1
lines changed

.github/workflows/test-integrations-misc.yml

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ jobs:
2727
strategy:
2828
fail-fast: false
2929
matrix:
30-
python-version: ["3.6","3.8","3.12","3.13"]
30+
python-version: ["3.6","3.7","3.8","3.12","3.13"]
3131
# python3.6 reached EOL and is no longer being supported on
3232
# new versions of hosted runners on Github Actions
3333
# ubuntu-20.04 is the last version that supported python3.6
@@ -73,6 +73,10 @@ jobs:
7373
run: |
7474
set -x # print commands that are executed
7575
./scripts/runtox.sh "py${{ matrix.python-version }}-trytond-latest"
76+
- name: Test typer latest
77+
run: |
78+
set -x # print commands that are executed
79+
./scripts/runtox.sh "py${{ matrix.python-version }}-typer-latest"
7680
- name: Generate coverage XML (Python 3.6)
7781
if: ${{ !cancelled() && matrix.python-version == '3.6' }}
7882
run: |
@@ -153,6 +157,10 @@ jobs:
153157
run: |
154158
set -x # print commands that are executed
155159
./scripts/runtox.sh --exclude-latest "py${{ matrix.python-version }}-trytond"
160+
- name: Test typer pinned
161+
run: |
162+
set -x # print commands that are executed
163+
./scripts/runtox.sh --exclude-latest "py${{ matrix.python-version }}-typer"
156164
- name: Generate coverage XML (Python 3.6)
157165
if: ${{ !cancelled() && matrix.python-version == '3.6' }}
158166
run: |

requirements-linting.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,3 +17,4 @@ pre-commit # local linting
1717
httpcore
1818
openfeature-sdk
1919
launchdarkly-server-sdk
20+
typer

scripts/split-tox-gh-actions/split-tox-gh-actions.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -132,6 +132,7 @@
132132
"potel",
133133
"pure_eval",
134134
"trytond",
135+
"typer",
135136
],
136137
}
137138

sentry_sdk/integrations/typer.py

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
import sentry_sdk
2+
from sentry_sdk.utils import (
3+
capture_internal_exceptions,
4+
event_from_exception,
5+
)
6+
from sentry_sdk.integrations import Integration, DidNotEnable
7+
8+
from typing import TYPE_CHECKING
9+
10+
if TYPE_CHECKING:
11+
from typing import Callable
12+
from typing import Any
13+
from typing import Type
14+
from typing import Optional
15+
16+
from types import TracebackType
17+
18+
Excepthook = Callable[
19+
[Type[BaseException], BaseException, Optional[TracebackType]],
20+
Any,
21+
]
22+
23+
try:
24+
import typer
25+
except ImportError:
26+
raise DidNotEnable("Typer not installed")
27+
28+
29+
class TyperIntegration(Integration):
30+
identifier = "typer"
31+
32+
@staticmethod
33+
def setup_once():
34+
# type: () -> None
35+
typer.main.except_hook = _make_excepthook(typer.main.except_hook) # type: ignore
36+
37+
38+
def _make_excepthook(old_excepthook):
39+
# type: (Excepthook) -> Excepthook
40+
def sentry_sdk_excepthook(type_, value, traceback):
41+
# type: (Type[BaseException], BaseException, Optional[TracebackType]) -> None
42+
integration = sentry_sdk.get_client().get_integration(TyperIntegration)
43+
44+
# Note: If we replace this with ensure_integration_enabled then
45+
# we break the exceptiongroup backport;
46+
# See: https://github.com/getsentry/sentry-python/issues/3097
47+
if integration is None:
48+
return old_excepthook(type_, value, traceback)
49+
50+
with capture_internal_exceptions():
51+
event, hint = event_from_exception(
52+
(type_, value, traceback),
53+
client_options=sentry_sdk.get_client().options,
54+
mechanism={"type": "typer", "handled": False},
55+
)
56+
sentry_sdk.capture_event(event, hint=hint)
57+
58+
return old_excepthook(type_, value, traceback)
59+
60+
return sentry_sdk_excepthook

tests/integrations/typer/__init__.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
import pytest
2+
3+
pytest.importorskip("typer")
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
import subprocess
2+
import sys
3+
from textwrap import dedent
4+
import pytest
5+
6+
from typer.testing import CliRunner
7+
8+
runner = CliRunner()
9+
10+
11+
def test_catch_exceptions(tmpdir):
12+
app = tmpdir.join("app.py")
13+
14+
app.write(
15+
dedent(
16+
"""
17+
import typer
18+
from unittest import mock
19+
20+
from sentry_sdk import init, transport
21+
from sentry_sdk.integrations.typer import TyperIntegration
22+
23+
def capture_envelope(self, envelope):
24+
print("capture_envelope was called")
25+
event = envelope.get_event()
26+
if event is not None:
27+
print(event)
28+
29+
transport.HttpTransport.capture_envelope = capture_envelope
30+
31+
init("http://foobar@localhost/123", integrations=[TyperIntegration()])
32+
33+
app = typer.Typer()
34+
35+
@app.command()
36+
def test():
37+
print("test called")
38+
raise Exception("pollo")
39+
40+
app()
41+
"""
42+
)
43+
)
44+
45+
with pytest.raises(subprocess.CalledProcessError) as excinfo:
46+
subprocess.check_output([sys.executable, str(app)], stderr=subprocess.STDOUT)
47+
48+
output = excinfo.value.output
49+
50+
assert b"capture_envelope was called" in output
51+
assert b"test called" in output
52+
assert b"pollo" in output

tox.ini

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -287,6 +287,10 @@ envlist =
287287
{py3.8,py3.11,py3.12}-trytond-v{7}
288288
{py3.8,py3.12,py3.13}-trytond-latest
289289

290+
# Typer
291+
{py3.7,py3.12,py3.13}-typer-v{0.15}
292+
{py3.7,py3.12,py3.13}-typer-latest
293+
290294
[testenv]
291295
deps =
292296
# if you change requirements-testing.txt and your change is not being reflected
@@ -724,6 +728,10 @@ deps =
724728
trytond-v7: trytond~=7.0
725729
trytond-latest: trytond
726730

731+
# Typer
732+
typer-v0.15: typer~=0.15.0
733+
typer-latest: typer
734+
727735
setenv =
728736
PYTHONDONTWRITEBYTECODE=1
729737
OBJC_DISABLE_INITIALIZE_FORK_SAFETY=YES
@@ -786,6 +794,7 @@ setenv =
786794
strawberry: TESTPATH=tests/integrations/strawberry
787795
tornado: TESTPATH=tests/integrations/tornado
788796
trytond: TESTPATH=tests/integrations/trytond
797+
typer: TESTPATH=tests/integrations/typer
789798
socket: TESTPATH=tests/integrations/socket
790799

791800
passenv =

0 commit comments

Comments
 (0)