Skip to content

Commit

Permalink
Merge pull request #25 from codex-team/feat/flask-support
Browse files Browse the repository at this point in the history
feat: add flask support
  • Loading branch information
slaveeks authored Nov 1, 2024
2 parents f8ce5a0 + e85e1a4 commit 60af829
Show file tree
Hide file tree
Showing 9 changed files with 268 additions and 17 deletions.
28 changes: 16 additions & 12 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,10 +1,8 @@
Hawk Python Catcher
===========
# Hawk Python Catcher

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

Usage
-----
## Usage

Register an account and get a new project token.

Expand Down Expand Up @@ -53,7 +51,6 @@ except:
hawk.send(ValueError("error description"))
```


### Event context

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

Init params
-----------
## Init params

To init Hawk Catcher just pass a project token.

Expand All @@ -96,19 +92,27 @@ hawk = Hawk({
})
```

Requirements
------------
Parameters:

| name | type | required | description |
| -------------------- | ---------------------- | ------------ | ------------------------------------------------------------------------ |
| `token` | str | **required** | Your project's Integration Token |
| `release` | str | optional | Release name for Suspected Commits feature |
| `collector_endpoint` | string | optional | Collector endpoint for sending event to |
| `context` | dict | optional | Additional context to be send with every event |
| `before_send` | Callable[[dict], None] | optional | This Method allows you to filter any data you don't want sending to Hawk |

## Requirements

- Python \>= 3.5
- requests

Links
-----
## Links

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

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

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

CodeX Team: <https://ifmo.su>
CodeX Team: <https://codex.so/>
92 changes: 92 additions & 0 deletions docs/flask.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
# Flask integration

This extension adds support for the [Flask](http://flask.pocoo.org/) web framework.

## Installation

```bash
pip install hawkcatcher[flask]
```

import Catcher module to your project.

```python
from hawkcatcher.modules.flask import HawkFlask
```

```python
hawk = HawkFlask(
"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJwcm9qZWN0SWQiOiI1ZTZmNWM3NzAzOWI0MDAwMjNmZDViODAiLCJpYXQiOjE1ODQzNTY0NzF9.t-5Gelx3MgHVBrxTsoMyPQAdQ6ufVbPsts9zZLW3gM8")
```

Now all global flask errors would be sent to Hawk.

### Try-except

If you want to catch errors in try-except blocks see [this](../README.md#try-except)

## Manual sending

You can send any error to Hawk. See [this](../README.md#manual-sending)

### Event context

See [this](../README.md#event-context)

### Affected user

See [this](../README.md#affected-user)

### Addons

When some event handled by Flask Catcher, it adds some addons to the event data for Hawk.

| name | type | description |
| --------- | ---- | ----------------- |
| `url` | str | Request URL |
| `method` | str | Request method |
| `headers` | dict | Request headers |
| `cookies` | dict | Request cookies |
| `params` | dict | Request params |
| `form` | dict | Request form |
| `json` | dict | Request json data |

## Init params

To init Hawk Catcher just pass a project token.

```python
hawk = HawkFlask('1234567-abcd-8901-efgh-123456789012')
```

### Additional params

If you need to use custom Hawk server then pass a dictionary with params.

```python
hawk = HawkFlask({
'token': '1234567-abcd-8901-efgh-123456789012',
'collector_endpoint': 'https://<id>.k1.hawk.so',
})
```

Parameters:

| name | type | required | description |
| -------------------- | ------------------------- | ------------ | ---------------------------------------------------------------------------- |
| `token` | str | **required** | Your project's Integration Token |
| `release` | str | optional | Release name for Suspected Commits feature |
| `collector_endpoint` | string | optional | Collector endpoint for sending event to |
| `context` | dict | optional | Additional context to be send with every event |
| `before_send` | Callable[[dict], None] | optional | This Method allows you to filter any data you don't want sending to Hawk |
| `set_user` | Callable[[Request], User] | optional | This Method allows you to set user for every request by flask request object |
| `with_addons` | bool | optional | Add framework addons to event data |

## Requirements

See [this](../README.md#requirements)

And for flask you need:

- Flask
- blinker
2 changes: 2 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ classifiers = [
"Environment :: Console",
"Environment :: Web Environment",
]
[project.optional-dependencies]
flask = ["flask"]
[tool.hatch.version]
path = "src/hawkcatcher/__init__.py"
[project.urls]
Expand Down
19 changes: 14 additions & 5 deletions src/hawkcatcher/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,9 +46,10 @@ def get_params(settings) -> Union[HawkCatcherSettings, None]:
settings.get('token')),
'release': settings.get('release'),
'before_send': settings.get('before_send'),
'context': settings.get('context', None)
}

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

if not self.params:
return

# in case passed context is empty set default from config
if context is None:
context = self.params.get('context')

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

if addons is None:
addons = {}

event = {
'token': self.params['token'],
'catcherType': 'errors/python',
Expand All @@ -81,7 +89,8 @@ def handler(self, exc_cls: type, exc: Exception, tb: traceback, context=None, us
'release': self.params['release'],
'context': context,
'catcherVersion': hawkcatcher.__version__,
'user': user
'user': user,
'addons': addons
}
}

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

def send(self, event: Exception = None, context=None, user=None):
def send(self, event: Exception = None, context=None, user=None, addons=None):
"""
Method for manually send error to Hawk
:param event: event to send
Expand All @@ -110,9 +119,9 @@ def send(self, event: Exception = None, context=None, user=None):
exc_cls, exc, tb = sys.exc_info()

if event is not None:
self.handler(type(event), event, tb, context, user)
self.handler(type(event), event, tb, context, user, addons)
else:
self.handler(exc_cls, exc, tb, context, user)
self.handler(exc_cls, exc, tb, context, user, addons)

@staticmethod
def parse_traceback(tb):
Expand Down
3 changes: 3 additions & 0 deletions src/hawkcatcher/errors.py
Original file line number Diff line number Diff line change
@@ -1,2 +1,5 @@
class InvalidHawkToken(Exception):
pass

class ModuleError(Exception):
pass
13 changes: 13 additions & 0 deletions src/hawkcatcher/modules/flask/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
from .flask import HawkFlask
from .types import HawkCatcherSettings
from .types import FlaskSettings

hawk = HawkFlask()


def init(*args, **kwargs):
hawk.init(*args, **kwargs)


def send(*args, **kwargs):
hawk.send(*args, **kwargs)
97 changes: 97 additions & 0 deletions src/hawkcatcher/modules/flask/flask.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
from ...core import Hawk
from typing import Union
from hawkcatcher.modules.flask.types import FlaskSettings, User, Addons
from hawkcatcher.errors import ModuleError

try:
from flask.signals import got_request_exception
from flask import Flask, request
except ImportError:
raise ModuleError("Flask is not installed")

# class for catching errors in flask app
class HawkFlask(Hawk):
params: FlaskSettings = {}

def init(self, settings: Union[str, FlaskSettings] = None) -> None:
self.params = self.get_params(settings)
got_request_exception.connect(self._handle_request_exception)

@staticmethod
def get_params(settings) -> Union[FlaskSettings, None]:
hawk_params = Hawk.get_params(settings)

if hawk_params is None:
return None

return {
**hawk_params,
'set_user': settings.get('set_user'),
'with_addons': settings.get('with_addons', True)
}

def send(self, exception, context=None, user=None, addons=None):
"""
Method for manually send error to Hawk
:param exception: exception
:param context: additional context to send with error
:param user: user information who faced with that event
"""
if addons is None:
addons = self._set_addons()

if user is None:
user = self._set_user(request)

super().send(exception, context, user, addons)

def _handle_request_exception(self, sender: Flask, exception):
"""
Catch, prepare and send error
:param sender: flask app
:param exception: exception
"""
addons = self._set_addons()

user = self._set_user(request)

ctx = self.params.get('context', None)

self.send(exception, ctx, user, addons)

def _set_addons(self) -> Union[Addons, None]:
"""
Set flask addons to send with error
"""
addons: Union[Addons, None] = None

if self.params.get('with_addons') == True:
headers = dict(request.headers)
cookies = dict(request.cookies)

addons = {
'flask': {
'url': request.url,
'method': request.method,
'headers': headers,
'cookies': cookies,
'params': request.args,
'form': request.form,
'json': request.json
}
}

return addons

def _set_user(self, request) -> Union[User, None]:
"""
Set user information by set_user callback
"""
user = None

if self.params.get('set_user') is not None:
user = self.params['set_user'](request)

return user

22 changes: 22 additions & 0 deletions src/hawkcatcher/modules/flask/types.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
from hawkcatcher.types import HawkCatcherSettings, User
from typing import Callable, TypedDict
from flask import Request

class FlaskAddons(TypedDict):
app: str # name of flask app
url: str # url of request
method: str # method of request
headers: dict # headers of request
cookies: dict # cookies of request
params: dict # request params
form: dict # request form data
json: dict # request json data

class Addons(TypedDict):
flask: FlaskAddons

class FlaskSettings(HawkCatcherSettings):
"""Settings for Flask catcher for errors tracking"""

set_user: Callable[[Request], User] # This hook allows you to identify user
with_addons: bool = True # This parameter points if you want to send request data with error (cookies, headers, params, form, json)
9 changes: 9 additions & 0 deletions src/hawkcatcher/types.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,19 @@
from typing import TypedDict, Callable


class User(TypedDict):
"""User data for sending with event"""

id: str # Internal user's identifier inside an app
name: str # User public name
image: str # User's public picture
url: str # URL for user's details page

class HawkCatcherSettings(TypedDict):
"""Settings for Hawk catcher for errors tracking"""

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

0 comments on commit 60af829

Please sign in to comment.