We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
capture_backtrace raises AttributeError on PEP-420 namespace packages
The new `capture_backtrace` function in `scout_apm.core.backtrace` raises an AttributeError when the stack includes a [PEP-420] namespace package.

This is caused by the [`module_filepath` function](https://github.com/scoutapp/scout_apm_python/blob/v2.21.0/src/scout_apm/core/backtrace.py#L26-L33), specifically line 32:

```python
    module_dir = sys.modules[root_module].__file__.rsplit(os.sep, 2)[0]
```

If `sys.modules[root_module]` is a [PEP-420] namespace package, this will raise
```
AttributeError: 'NoneType' object has no attribute 'rsplit'
```

### Steps to reproduce

Create a namespace package, with some modules inside, e.g.:
```
namespace/
    foo/
        __init__.py
    bar/
       __init__.py
```

Then on an interactive Python shell:

```
>>> from scout_apm.core.backtrace import module_filepath
>>> from namespace import foo
>>> module_filepath("namespace.foo", "namespace")
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/home/jack/venvs/tmp-a17ac7185189989/lib/python3.8/site-packages/scout_apm/core/backtrace.py", line 32, in module_filepath
    module_dir = sys.modules[root_module].__file__.rsplit(os.sep, 2)[0]
AttributeError: 'NoneType' object has no attribute 'rsplit'
```

### Details

- Tested with version 2.21.0
- Current workaround is to pin version to 2.20.0

[PEP-420]: https://www.python.org/dev/peps/pep-0420/

--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `src/scout_apm/core/backtrace.py`
Content:
```
1 # coding=utf-8
2 from __future__ import absolute_import, division, print_function, unicode_literals
3 
4 import itertools
5 import os
6 import sys
7 import sysconfig
8 import traceback
9 import warnings
10 
11 # Maximum non-Scout frames to target retrieving
12 LIMIT = 50
13 # How many upper frames from inside Scout to ignore
14 IGNORED = 1
15 
16 
17 def filter_frames(frames):
18     """Filter the stack trace frames down to non-library code."""
19     paths = sysconfig.get_paths()
20     library_paths = {paths["purelib"], paths["platlib"]}
21     for frame in frames:
22         if not any(frame["file"].startswith(exclusion) for exclusion in library_paths):
23             yield frame
24 
25 
26 def module_filepath(module, filepath):
27     """Get the filepath relative to the base module."""
28     root_module = module.split(".", 1)[0]
29     if root_module == module:
30         return os.path.basename(filepath)
31 
32     module_dir = sys.modules[root_module].__file__.rsplit(os.sep, 2)[0]
33     return filepath.split(module_dir, 1)[-1].lstrip(os.sep)
34 
35 
36 def filepath(frame):
37     """Get the filepath for frame."""
38     module = frame.f_globals.get("__name__", None)
39     filepath = frame.f_code.co_filename
40 
41     if filepath.endswith(".pyc"):
42         filepath = filepath[:-1]
43 
44     if not module:
45         return filepath
46     return module_filepath(module, filepath)
47 
48 
49 if sys.version_info >= (3, 5):
50 
51     def stacktrace_walker(tb):
52         """Iterate over each frame of the stack downards for exceptions."""
53         for frame, lineno in traceback.walk_tb(tb):
54             name = frame.f_code.co_name
55             yield {"file": filepath(frame), "line": lineno, "function": name}
56 
57     def backtrace_walker():
58         """Iterate over each frame of the stack upwards.
59 
60         Taken from python3/traceback.ExtractSummary.extract to support
61         iterating over the entire stack, but without creating a large
62         data structure.
63         """
64         start_frame = sys._getframe().f_back
65         for frame, lineno in traceback.walk_stack(start_frame):
66             name = frame.f_code.co_name
67             yield {"file": filepath(frame), "line": lineno, "function": name}
68 
69 
70 else:
71 
72     def stacktrace_walker(tb):
73         """Iterate over each frame of the stack downards for exceptions."""
74         while tb is not None:
75             lineno = tb.tb_lineno
76             name = tb.tb_frame.f_code.co_name
77             yield {
78                 "file": filepath(tb.tb_frame),
79                 "line": lineno,
80                 "function": name,
81             }
82             tb = tb.tb_next
83 
84     def backtrace_walker():
85         """Iterate over each frame of the stack upwards.
86 
87         Taken from python2.7/traceback.extract_stack to support iterating
88         over the entire stack, but without creating a large data structure.
89         """
90         try:
91             raise ZeroDivisionError
92         except ZeroDivisionError:
93             # Get the current frame
94             frame = sys.exc_info()[2].tb_frame.f_back
95 
96         while frame is not None:
97             lineno = frame.f_lineno
98             name = frame.f_code.co_name
99             yield {"file": filepath(frame), "line": lineno, "function": name}
100             frame = frame.f_back
101 
102 
103 def capture_backtrace():
104     walker = filter_frames(backtrace_walker())
105     return list(itertools.islice(walker, LIMIT))
106 
107 
108 def capture_stacktrace(tb):
109     walker = stacktrace_walker(tb)
110     return list(reversed(list(itertools.islice(walker, LIMIT))))
111 
112 
113 def capture():
114     warnings.warn(
115         "capture is deprecated, instead use capture_backtrace instead.",
116         DeprecationWarning,
117         2,
118     )
119     return capture_backtrace()
120
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

diff --git a/src/scout_apm/core/backtrace.py b/src/scout_apm/core/backtrace.py
--- a/src/scout_apm/core/backtrace.py
+++ b/src/scout_apm/core/backtrace.py
@@ -7,6 +7,9 @@
 import sysconfig
 import traceback
 import warnings
+from logging import getLogger
+
+logger = getLogger(__name__)
 
 # Maximum non-Scout frames to target retrieving
 LIMIT = 50
@@ -25,11 +28,25 @@
 
 def module_filepath(module, filepath):
     """Get the filepath relative to the base module."""
-    root_module = module.split(".", 1)[0]
-    if root_module == module:
+    root_module_name = module.split(".", 1)[0]
+    if root_module_name == module:
         return os.path.basename(filepath)
 
-    module_dir = sys.modules[root_module].__file__.rsplit(os.sep, 2)[0]
+    root_module = sys.modules[root_module_name]
+    if root_module.__file__:
+        module_dir = root_module.__file__.rsplit(os.sep, 2)[0]
+    elif root_module.__path__:
+        # Default to using the first path specified for the module.
+        module_dir = root_module.__path__[0].rsplit(os.sep, 1)[0]
+        if len(root_module.__path__) > 1:
+            logger.debug(
+                "{} has {} paths. Use the first and ignore the rest.".format(
+                    root_module, len(root_module.__path__)
+                )
+            )
+    else:
+        # If the file path don't exist, then return the full path.
+        return filepath
     return filepath.split(module_dir, 1)[-1].lstrip(os.sep)

{"golden_diff": "diff --git a/src/scout_apm/core/backtrace.py b/src/scout_apm/core/backtrace.py\n--- a/src/scout_apm/core/backtrace.py\n+++ b/src/scout_apm/core/backtrace.py\n@@ -7,6 +7,9 @@\n import sysconfig\n import traceback\n import warnings\n+from logging import getLogger\n+\n+logger = getLogger(__name__)\n \n # Maximum non-Scout frames to target retrieving\n LIMIT = 50\n@@ -25,11 +28,25 @@\n \n def module_filepath(module, filepath):\n     \"\"\"Get the filepath relative to the base module.\"\"\"\n-    root_module = module.split(\".\", 1)[0]\n-    if root_module == module:\n+    root_module_name = module.split(\".\", 1)[0]\n+    if root_module_name == module:\n         return os.path.basename(filepath)\n \n-    module_dir = sys.modules[root_module].__file__.rsplit(os.sep, 2)[0]\n+    root_module = sys.modules[root_module_name]\n+    if root_module.__file__:\n+        module_dir = root_module.__file__.rsplit(os.sep, 2)[0]\n+    elif root_module.__path__:\n+        # Default to using the first path specified for the module.\n+        module_dir = root_module.__path__[0].rsplit(os.sep, 1)[0]\n+        if len(root_module.__path__) > 1:\n+            logger.debug(\n+                \"{} has {} paths. Use the first and ignore the rest.\".format(\n+                    root_module, len(root_module.__path__)\n+                )\n+            )\n+    else:\n+        # If the file path don't exist, then return the full path.\n+        return filepath\n     return filepath.split(module_dir, 1)[-1].lstrip(os.sep)\n", "issue": "capture_backtrace raises AttributeError on PEP-420 namespace packages\nThe new `capture_backtrace` function in `scout_apm.core.backtrace` raises an AttributeError when the stack includes a [PEP-420] namespace package.\r\n\r\nThis is caused by the [`module_filepath` function](https://github.com/scoutapp/scout_apm_python/blob/v2.21.0/src/scout_apm/core/backtrace.py#L26-L33), specifically line 32:\r\n\r\n```python\r\n    module_dir = sys.modules[root_module].__file__.rsplit(os.sep, 2)[0]\r\n```\r\n\r\nIf `sys.modules[root_module]` is a [PEP-420] namespace package, this will raise\r\n```\r\nAttributeError: 'NoneType' object has no attribute 'rsplit'\r\n```\r\n\r\n### Steps to reproduce\r\n\r\nCreate a namespace package, with some modules inside, e.g.:\r\n```\r\nnamespace/\r\n    foo/\r\n        __init__.py\r\n    bar/\r\n       __init__.py\r\n```\r\n\r\nThen on an interactive Python shell:\r\n\r\n```\r\n>>> from scout_apm.core.backtrace import module_filepath\r\n>>> from namespace import foo\r\n>>> module_filepath(\"namespace.foo\", \"namespace\")\r\nTraceback (most recent call last):\r\n  File \"<stdin>\", line 1, in <module>\r\n  File \"/home/jack/venvs/tmp-a17ac7185189989/lib/python3.8/site-packages/scout_apm/core/backtrace.py\", line 32, in module_filepath\r\n    module_dir = sys.modules[root_module].__file__.rsplit(os.sep, 2)[0]\r\nAttributeError: 'NoneType' object has no attribute 'rsplit'\r\n```\r\n\r\n### Details\r\n\r\n- Tested with version 2.21.0\r\n- Current workaround is to pin version to 2.20.0\r\n\r\n[PEP-420]: https://www.python.org/dev/peps/pep-0420/\n", "before_files": [{"content": "# coding=utf-8\nfrom __future__ import absolute_import, division, print_function, unicode_literals\n\nimport itertools\nimport os\nimport sys\nimport sysconfig\nimport traceback\nimport warnings\n\n# Maximum non-Scout frames to target retrieving\nLIMIT = 50\n# How many upper frames from inside Scout to ignore\nIGNORED = 1\n\n\ndef filter_frames(frames):\n    \"\"\"Filter the stack trace frames down to non-library code.\"\"\"\n    paths = sysconfig.get_paths()\n    library_paths = {paths[\"purelib\"], paths[\"platlib\"]}\n    for frame in frames:\n        if not any(frame[\"file\"].startswith(exclusion) for exclusion in library_paths):\n            yield frame\n\n\ndef module_filepath(module, filepath):\n    \"\"\"Get the filepath relative to the base module.\"\"\"\n    root_module = module.split(\".\", 1)[0]\n    if root_module == module:\n        return os.path.basename(filepath)\n\n    module_dir = sys.modules[root_module].__file__.rsplit(os.sep, 2)[0]\n    return filepath.split(module_dir, 1)[-1].lstrip(os.sep)\n\n\ndef filepath(frame):\n    \"\"\"Get the filepath for frame.\"\"\"\n    module = frame.f_globals.get(\"__name__\", None)\n    filepath = frame.f_code.co_filename\n\n    if filepath.endswith(\".pyc\"):\n        filepath = filepath[:-1]\n\n    if not module:\n        return filepath\n    return module_filepath(module, filepath)\n\n\nif sys.version_info >= (3, 5):\n\n    def stacktrace_walker(tb):\n        \"\"\"Iterate over each frame of the stack downards for exceptions.\"\"\"\n        for frame, lineno in traceback.walk_tb(tb):\n            name = frame.f_code.co_name\n            yield {\"file\": filepath(frame), \"line\": lineno, \"function\": name}\n\n    def backtrace_walker():\n        \"\"\"Iterate over each frame of the stack upwards.\n\n        Taken from python3/traceback.ExtractSummary.extract to support\n        iterating over the entire stack, but without creating a large\n        data structure.\n        \"\"\"\n        start_frame = sys._getframe().f_back\n        for frame, lineno in traceback.walk_stack(start_frame):\n            name = frame.f_code.co_name\n            yield {\"file\": filepath(frame), \"line\": lineno, \"function\": name}\n\n\nelse:\n\n    def stacktrace_walker(tb):\n        \"\"\"Iterate over each frame of the stack downards for exceptions.\"\"\"\n        while tb is not None:\n            lineno = tb.tb_lineno\n            name = tb.tb_frame.f_code.co_name\n            yield {\n                \"file\": filepath(tb.tb_frame),\n                \"line\": lineno,\n                \"function\": name,\n            }\n            tb = tb.tb_next\n\n    def backtrace_walker():\n        \"\"\"Iterate over each frame of the stack upwards.\n\n        Taken from python2.7/traceback.extract_stack to support iterating\n        over the entire stack, but without creating a large data structure.\n        \"\"\"\n        try:\n            raise ZeroDivisionError\n        except ZeroDivisionError:\n            # Get the current frame\n            frame = sys.exc_info()[2].tb_frame.f_back\n\n        while frame is not None:\n            lineno = frame.f_lineno\n            name = frame.f_code.co_name\n            yield {\"file\": filepath(frame), \"line\": lineno, \"function\": name}\n            frame = frame.f_back\n\n\ndef capture_backtrace():\n    walker = filter_frames(backtrace_walker())\n    return list(itertools.islice(walker, LIMIT))\n\n\ndef capture_stacktrace(tb):\n    walker = stacktrace_walker(tb)\n    return list(reversed(list(itertools.islice(walker, LIMIT))))\n\n\ndef capture():\n    warnings.warn(\n        \"capture is deprecated, instead use capture_backtrace instead.\",\n        DeprecationWarning,\n        2,\n    )\n    return capture_backtrace()\n", "path": "src/scout_apm/core/backtrace.py"}], "after_files": [{"content": "# coding=utf-8\nfrom __future__ import absolute_import, division, print_function, unicode_literals\n\nimport itertools\nimport os\nimport sys\nimport sysconfig\nimport traceback\nimport warnings\nfrom logging import getLogger\n\nlogger = getLogger(__name__)\n\n# Maximum non-Scout frames to target retrieving\nLIMIT = 50\n# How many upper frames from inside Scout to ignore\nIGNORED = 1\n\n\ndef filter_frames(frames):\n    \"\"\"Filter the stack trace frames down to non-library code.\"\"\"\n    paths = sysconfig.get_paths()\n    library_paths = {paths[\"purelib\"], paths[\"platlib\"]}\n    for frame in frames:\n        if not any(frame[\"file\"].startswith(exclusion) for exclusion in library_paths):\n            yield frame\n\n\ndef module_filepath(module, filepath):\n    \"\"\"Get the filepath relative to the base module.\"\"\"\n    root_module_name = module.split(\".\", 1)[0]\n    if root_module_name == module:\n        return os.path.basename(filepath)\n\n    root_module = sys.modules[root_module_name]\n    if root_module.__file__:\n        module_dir = root_module.__file__.rsplit(os.sep, 2)[0]\n    elif root_module.__path__:\n        # Default to using the first path specified for the module.\n        module_dir = root_module.__path__[0].rsplit(os.sep, 1)[0]\n        if len(root_module.__path__) > 1:\n            logger.debug(\n                \"{} has {} paths. Use the first and ignore the rest.\".format(\n                    root_module, len(root_module.__path__)\n                )\n            )\n    else:\n        # If the file path don't exist, then return the full path.\n        return filepath\n    return filepath.split(module_dir, 1)[-1].lstrip(os.sep)\n\n\ndef filepath(frame):\n    \"\"\"Get the filepath for frame.\"\"\"\n    module = frame.f_globals.get(\"__name__\", None)\n    filepath = frame.f_code.co_filename\n\n    if filepath.endswith(\".pyc\"):\n        filepath = filepath[:-1]\n\n    if not module:\n        return filepath\n    return module_filepath(module, filepath)\n\n\nif sys.version_info >= (3, 5):\n\n    def stacktrace_walker(tb):\n        \"\"\"Iterate over each frame of the stack downards for exceptions.\"\"\"\n        for frame, lineno in traceback.walk_tb(tb):\n            name = frame.f_code.co_name\n            yield {\"file\": filepath(frame), \"line\": lineno, \"function\": name}\n\n    def backtrace_walker():\n        \"\"\"Iterate over each frame of the stack upwards.\n\n        Taken from python3/traceback.ExtractSummary.extract to support\n        iterating over the entire stack, but without creating a large\n        data structure.\n        \"\"\"\n        start_frame = sys._getframe().f_back\n        for frame, lineno in traceback.walk_stack(start_frame):\n            name = frame.f_code.co_name\n            yield {\"file\": filepath(frame), \"line\": lineno, \"function\": name}\n\n\nelse:\n\n    def stacktrace_walker(tb):\n        \"\"\"Iterate over each frame of the stack downards for exceptions.\"\"\"\n        while tb is not None:\n            lineno = tb.tb_lineno\n            name = tb.tb_frame.f_code.co_name\n            yield {\n                \"file\": filepath(tb.tb_frame),\n                \"line\": lineno,\n                \"function\": name,\n            }\n            tb = tb.tb_next\n\n    def backtrace_walker():\n        \"\"\"Iterate over each frame of the stack upwards.\n\n        Taken from python2.7/traceback.extract_stack to support iterating\n        over the entire stack, but without creating a large data structure.\n        \"\"\"\n        try:\n            raise ZeroDivisionError\n        except ZeroDivisionError:\n            # Get the current frame\n            frame = sys.exc_info()[2].tb_frame.f_back\n\n        while frame is not None:\n            lineno = frame.f_lineno\n            name = frame.f_code.co_name\n            yield {\"file\": filepath(frame), \"line\": lineno, \"function\": name}\n            frame = frame.f_back\n\n\ndef capture_backtrace():\n    walker = filter_frames(backtrace_walker())\n    return list(itertools.islice(walker, LIMIT))\n\n\ndef capture_stacktrace(tb):\n    walker = stacktrace_walker(tb)\n    return list(reversed(list(itertools.islice(walker, LIMIT))))\n\n\ndef capture():\n    warnings.warn(\n        \"capture is deprecated, instead use capture_backtrace instead.\",\n        DeprecationWarning,\n        2,\n    )\n    return capture_backtrace()\n", "path": "src/scout_apm/core/backtrace.py"}]}