Repository: honeybadger-io/honeybadger-python

Issue:
Use in AWS Glue
I'm trying to use `honeybadger-python` in an AWS Glue job. I'm able to install this module, along with `psutil` and `six` by specifying them in the "Python library path" option and providing them as either zip or whl archives.

Unfortunately, when I try running the job I get the error below. I don't think AWS Glue is compatible with modules that rely on C-extensions:

```
ImportError: cannot import name '_psutil_linux' from 'psutil' (/tmp/psutil-5.8.0-cp37-cp37m-manylinux2010_x86_64.whl/psutil/__init__.py)
```

To work-around the problem, I hacked the `honeybadger-python` source code to remove the dependency on `psutil` which wasn't important to me at this time.

Is there any interest in making `psutil` optional? Python 3 has ways of detecting if a module is available. If the module isn't available, it could skip [the stats part of the payload](https://github.com/honeybadger-io/honeybadger-python/blob/db28b0c5326efc95d7e1760cefc86d48545faf52/honeybadger/payload.py#L77-L85). If there's interest, I might try making a pull request.



Discussion:

Patch:
diff --git a/honeybadger/payload.py b/honeybadger/payload.py
index 67d519d..1dc22b0 100644
--- a/honeybadger/payload.py
+++ b/honeybadger/payload.py
@@ -7,8 +7,6 @@ from six.moves import zip
 from io import open
 from datetime import datetime
 
-import psutil
-
 from .version import __version__
 from .plugins import default_plugin_manager
 
@@ -56,36 +54,41 @@ def read_source(frame, source_radius=3):
     return {}
 
 def server_payload(config):
-    payload = {
+    return {
         'project_root': config.project_root,
         'environment_name': config.environment,
         'hostname': config.hostname,
         'time': datetime.utcnow().strftime("%Y-%m-%dT%H:%M:%SZ"),
         'pid': os.getpid(),
-        'stats': {}
-    }
-
-    s = psutil.virtual_memory()
-    loadavg = psutil.getloadavg()
-
-    free = float(s.free) / 1048576.0
-    buffers = hasattr(s, 'buffers') and float(s.buffers) / 1048576.0 or 0.0
-    cached = hasattr(s, 'cached') and float(s.cached) / 1048576.0 or 0.0
-    total_free = free + buffers + cached
-
-
-    payload['stats']['mem'] = {
-        'total': float(s.total) / 1048576.0, # bytes -> megabytes
-        'free': free,
-        'buffers': buffers,
-        'cached': cached,
-        'total_free': total_free
+        'stats': stats_payload()
     }
 
-    payload['stats']['load'] = dict(zip(('one', 'five', 'fifteen'), loadavg))
-
-    return payload
-
+def stats_payload():
+    try:
+        import psutil
+    except ImportError:
+        return {}
+    else:
+        s = psutil.virtual_memory()
+        loadavg = psutil.getloadavg()
+
+        free = float(s.free) / 1048576.0
+        buffers = hasattr(s, 'buffers') and float(s.buffers) / 1048576.0 or 0.0
+        cached = hasattr(s, 'cached') and float(s.cached) / 1048576.0 or 0.0
+        total_free = free + buffers + cached
+        payload = {}
+
+        payload['mem'] = {
+            'total': float(s.total) / 1048576.0, # bytes -> megabytes
+            'free': free,
+            'buffers': buffers,
+            'cached': cached,
+            'total_free': total_free
+        }
+
+        payload['load'] = dict(zip(('one', 'five', 'fifteen'), loadavg))
+
+        return payload
 
 def create_payload(exception, exc_traceback=None, config=None, context={}):
     if exc_traceback is None:


Test patch:
diff --git a/honeybadger/tests/test_payload.py b/honeybadger/tests/test_payload.py
index 8ddb266..8ead0d0 100644
--- a/honeybadger/tests/test_payload.py
+++ b/honeybadger/tests/test_payload.py
@@ -2,6 +2,7 @@ from six.moves import range
 from six.moves import zip
 from contextlib import contextmanager
 import os
+import sys
 
 from honeybadger.payload import error_payload
 from honeybadger.payload import server_payload
@@ -79,3 +80,10 @@ def test_server_payload():
     eq_(payload['pid'], os.getpid())
     assert type(payload['stats']['mem']['total']) == float
     assert type(payload['stats']['mem']['free']) == float
+
+def test_psutil_is_optional():
+    config = Configuration()
+
+    with patch.dict(sys.modules, {'psutil':None}):
+        payload = server_payload(config)
+        eq_(payload['stats'], {})