Skip to content

Commit 60af829

Browse files
authored
Merge pull request #25 from codex-team/feat/flask-support
feat: add flask support
2 parents f8ce5a0 + e85e1a4 commit 60af829

File tree

9 files changed

+268
-17
lines changed

9 files changed

+268
-17
lines changed

README.md

Lines changed: 16 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,8 @@
1-
Hawk Python Catcher
2-
===========
1+
# Hawk Python Catcher
32

43
Python errors Catcher module for [Hawk.so](https://hawk.so).
54

6-
Usage
7-
-----
5+
## Usage
86

97
Register an account and get a new project token.
108

@@ -53,7 +51,6 @@ except:
5351
hawk.send(ValueError("error description"))
5452
```
5553

56-
5754
### Event context
5855

5956
It is possible to pass additional event context for debugging purposes:
@@ -76,8 +73,7 @@ except:
7673
hawk.send(ValueError("error description"), {"params": "value"}, {"id": 123})
7774
```
7875

79-
Init params
80-
-----------
76+
## Init params
8177

8278
To init Hawk Catcher just pass a project token.
8379

@@ -96,19 +92,27 @@ hawk = Hawk({
9692
})
9793
```
9894

99-
Requirements
100-
------------
95+
Parameters:
96+
97+
| name | type | required | description |
98+
| -------------------- | ---------------------- | ------------ | ------------------------------------------------------------------------ |
99+
| `token` | str | **required** | Your project's Integration Token |
100+
| `release` | str | optional | Release name for Suspected Commits feature |
101+
| `collector_endpoint` | string | optional | Collector endpoint for sending event to |
102+
| `context` | dict | optional | Additional context to be send with every event |
103+
| `before_send` | Callable[[dict], None] | optional | This Method allows you to filter any data you don't want sending to Hawk |
104+
105+
## Requirements
101106

102107
- Python \>= 3.5
103108
- requests
104109

105-
Links
106-
-----
110+
## Links
107111

108112
Repository: <https://github.com/codex-team/hawk.python>
109113

110114
Report a bug: <https://github.com/codex-team/hawk.python/issues>
111115

112116
PyPI Package: <https://pypi.python.org/pypi/hawkcatcher>
113117

114-
CodeX Team: <https://ifmo.su>
118+
CodeX Team: <https://codex.so/>

docs/flask.md

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
# Flask integration
2+
3+
This extension adds support for the [Flask](http://flask.pocoo.org/) web framework.
4+
5+
## Installation
6+
7+
```bash
8+
pip install hawkcatcher[flask]
9+
```
10+
11+
import Catcher module to your project.
12+
13+
```python
14+
from hawkcatcher.modules.flask import HawkFlask
15+
```
16+
17+
```python
18+
hawk = HawkFlask(
19+
"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJwcm9qZWN0SWQiOiI1ZTZmNWM3NzAzOWI0MDAwMjNmZDViODAiLCJpYXQiOjE1ODQzNTY0NzF9.t-5Gelx3MgHVBrxTsoMyPQAdQ6ufVbPsts9zZLW3gM8")
20+
```
21+
22+
Now all global flask errors would be sent to Hawk.
23+
24+
### Try-except
25+
26+
If you want to catch errors in try-except blocks see [this](../README.md#try-except)
27+
28+
## Manual sending
29+
30+
You can send any error to Hawk. See [this](../README.md#manual-sending)
31+
32+
### Event context
33+
34+
See [this](../README.md#event-context)
35+
36+
### Affected user
37+
38+
See [this](../README.md#affected-user)
39+
40+
### Addons
41+
42+
When some event handled by Flask Catcher, it adds some addons to the event data for Hawk.
43+
44+
| name | type | description |
45+
| --------- | ---- | ----------------- |
46+
| `url` | str | Request URL |
47+
| `method` | str | Request method |
48+
| `headers` | dict | Request headers |
49+
| `cookies` | dict | Request cookies |
50+
| `params` | dict | Request params |
51+
| `form` | dict | Request form |
52+
| `json` | dict | Request json data |
53+
54+
## Init params
55+
56+
To init Hawk Catcher just pass a project token.
57+
58+
```python
59+
hawk = HawkFlask('1234567-abcd-8901-efgh-123456789012')
60+
```
61+
62+
### Additional params
63+
64+
If you need to use custom Hawk server then pass a dictionary with params.
65+
66+
```python
67+
hawk = HawkFlask({
68+
'token': '1234567-abcd-8901-efgh-123456789012',
69+
'collector_endpoint': 'https://<id>.k1.hawk.so',
70+
})
71+
```
72+
73+
Parameters:
74+
75+
| name | type | required | description |
76+
| -------------------- | ------------------------- | ------------ | ---------------------------------------------------------------------------- |
77+
| `token` | str | **required** | Your project's Integration Token |
78+
| `release` | str | optional | Release name for Suspected Commits feature |
79+
| `collector_endpoint` | string | optional | Collector endpoint for sending event to |
80+
| `context` | dict | optional | Additional context to be send with every event |
81+
| `before_send` | Callable[[dict], None] | optional | This Method allows you to filter any data you don't want sending to Hawk |
82+
| `set_user` | Callable[[Request], User] | optional | This Method allows you to set user for every request by flask request object |
83+
| `with_addons` | bool | optional | Add framework addons to event data |
84+
85+
## Requirements
86+
87+
See [this](../README.md#requirements)
88+
89+
And for flask you need:
90+
91+
- Flask
92+
- blinker

pyproject.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@ classifiers = [
1717
"Environment :: Console",
1818
"Environment :: Web Environment",
1919
]
20+
[project.optional-dependencies]
21+
flask = ["flask"]
2022
[tool.hatch.version]
2123
path = "src/hawkcatcher/__init__.py"
2224
[project.urls]

src/hawkcatcher/core.py

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -46,9 +46,10 @@ def get_params(settings) -> Union[HawkCatcherSettings, None]:
4646
settings.get('token')),
4747
'release': settings.get('release'),
4848
'before_send': settings.get('before_send'),
49+
'context': settings.get('context', None)
4950
}
5051

51-
def handler(self, exc_cls: type, exc: Exception, tb: traceback, context=None, user=None):
52+
def handler(self, exc_cls: type, exc: Exception, tb: traceback, context=None, user=None, addons=None):
5253
"""
5354
Catch, prepare and send error
5455
@@ -61,6 +62,10 @@ def handler(self, exc_cls: type, exc: Exception, tb: traceback, context=None, us
6162

6263
if not self.params:
6364
return
65+
66+
# in case passed context is empty set default from config
67+
if context is None:
68+
context = self.params.get('context')
6469

6570
ex_message = traceback.format_exception_only(exc_cls, exc)[-1]
6671
ex_message = ex_message.strip()
@@ -71,6 +76,9 @@ def handler(self, exc_cls: type, exc: Exception, tb: traceback, context=None, us
7176
'value': context
7277
}
7378

79+
if addons is None:
80+
addons = {}
81+
7482
event = {
7583
'token': self.params['token'],
7684
'catcherType': 'errors/python',
@@ -81,7 +89,8 @@ def handler(self, exc_cls: type, exc: Exception, tb: traceback, context=None, us
8189
'release': self.params['release'],
8290
'context': context,
8391
'catcherVersion': hawkcatcher.__version__,
84-
'user': user
92+
'user': user,
93+
'addons': addons
8594
}
8695
}
8796

@@ -99,7 +108,7 @@ def send_to_collector(self, event):
99108
except Exception as e:
100109
print('[Hawk] Can\'t send error cause of %s' % e)
101110

102-
def send(self, event: Exception = None, context=None, user=None):
111+
def send(self, event: Exception = None, context=None, user=None, addons=None):
103112
"""
104113
Method for manually send error to Hawk
105114
:param event: event to send
@@ -110,9 +119,9 @@ def send(self, event: Exception = None, context=None, user=None):
110119
exc_cls, exc, tb = sys.exc_info()
111120

112121
if event is not None:
113-
self.handler(type(event), event, tb, context, user)
122+
self.handler(type(event), event, tb, context, user, addons)
114123
else:
115-
self.handler(exc_cls, exc, tb, context, user)
124+
self.handler(exc_cls, exc, tb, context, user, addons)
116125

117126
@staticmethod
118127
def parse_traceback(tb):

src/hawkcatcher/errors.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,5 @@
11
class InvalidHawkToken(Exception):
2+
pass
3+
4+
class ModuleError(Exception):
25
pass
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
from .flask import HawkFlask
2+
from .types import HawkCatcherSettings
3+
from .types import FlaskSettings
4+
5+
hawk = HawkFlask()
6+
7+
8+
def init(*args, **kwargs):
9+
hawk.init(*args, **kwargs)
10+
11+
12+
def send(*args, **kwargs):
13+
hawk.send(*args, **kwargs)
Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
from ...core import Hawk
2+
from typing import Union
3+
from hawkcatcher.modules.flask.types import FlaskSettings, User, Addons
4+
from hawkcatcher.errors import ModuleError
5+
6+
try:
7+
from flask.signals import got_request_exception
8+
from flask import Flask, request
9+
except ImportError:
10+
raise ModuleError("Flask is not installed")
11+
12+
# class for catching errors in flask app
13+
class HawkFlask(Hawk):
14+
params: FlaskSettings = {}
15+
16+
def init(self, settings: Union[str, FlaskSettings] = None) -> None:
17+
self.params = self.get_params(settings)
18+
got_request_exception.connect(self._handle_request_exception)
19+
20+
@staticmethod
21+
def get_params(settings) -> Union[FlaskSettings, None]:
22+
hawk_params = Hawk.get_params(settings)
23+
24+
if hawk_params is None:
25+
return None
26+
27+
return {
28+
**hawk_params,
29+
'set_user': settings.get('set_user'),
30+
'with_addons': settings.get('with_addons', True)
31+
}
32+
33+
def send(self, exception, context=None, user=None, addons=None):
34+
"""
35+
Method for manually send error to Hawk
36+
:param exception: exception
37+
:param context: additional context to send with error
38+
:param user: user information who faced with that event
39+
"""
40+
if addons is None:
41+
addons = self._set_addons()
42+
43+
if user is None:
44+
user = self._set_user(request)
45+
46+
super().send(exception, context, user, addons)
47+
48+
def _handle_request_exception(self, sender: Flask, exception):
49+
"""
50+
Catch, prepare and send error
51+
52+
:param sender: flask app
53+
:param exception: exception
54+
"""
55+
addons = self._set_addons()
56+
57+
user = self._set_user(request)
58+
59+
ctx = self.params.get('context', None)
60+
61+
self.send(exception, ctx, user, addons)
62+
63+
def _set_addons(self) -> Union[Addons, None]:
64+
"""
65+
Set flask addons to send with error
66+
"""
67+
addons: Union[Addons, None] = None
68+
69+
if self.params.get('with_addons') == True:
70+
headers = dict(request.headers)
71+
cookies = dict(request.cookies)
72+
73+
addons = {
74+
'flask': {
75+
'url': request.url,
76+
'method': request.method,
77+
'headers': headers,
78+
'cookies': cookies,
79+
'params': request.args,
80+
'form': request.form,
81+
'json': request.json
82+
}
83+
}
84+
85+
return addons
86+
87+
def _set_user(self, request) -> Union[User, None]:
88+
"""
89+
Set user information by set_user callback
90+
"""
91+
user = None
92+
93+
if self.params.get('set_user') is not None:
94+
user = self.params['set_user'](request)
95+
96+
return user
97+
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
from hawkcatcher.types import HawkCatcherSettings, User
2+
from typing import Callable, TypedDict
3+
from flask import Request
4+
5+
class FlaskAddons(TypedDict):
6+
app: str # name of flask app
7+
url: str # url of request
8+
method: str # method of request
9+
headers: dict # headers of request
10+
cookies: dict # cookies of request
11+
params: dict # request params
12+
form: dict # request form data
13+
json: dict # request json data
14+
15+
class Addons(TypedDict):
16+
flask: FlaskAddons
17+
18+
class FlaskSettings(HawkCatcherSettings):
19+
"""Settings for Flask catcher for errors tracking"""
20+
21+
set_user: Callable[[Request], User] # This hook allows you to identify user
22+
with_addons: bool = True # This parameter points if you want to send request data with error (cookies, headers, params, form, json)

src/hawkcatcher/types.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,19 @@
11
from typing import TypedDict, Callable
22

33

4+
class User(TypedDict):
5+
"""User data for sending with event"""
6+
7+
id: str # Internal user's identifier inside an app
8+
name: str # User public name
9+
image: str # User's public picture
10+
url: str # URL for user's details page
11+
412
class HawkCatcherSettings(TypedDict):
513
"""Settings for Hawk catcher for errors tracking"""
614

715
token: str # Hawk integration token
816
collector_endpoint: str # Collector endpoint for sending event to
917
release: str # Release name for Suspected Commits feature
1018
before_send: Callable[[dict], None] # This hook allows you to filter any data you don't want sending to Hawk
19+
context: dict # Additional context to be send with event

0 commit comments

Comments
 (0)