We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
Failed to package local pip installed dependency in 0.8.4
An issue first reported in #bentoml-users slack channel by @fernandocamargoti

In some python environment, we see the following error when doing a `BenoService#save`:
```
TypeError: expected str, bytes or os.PathLike object, not NoneType
```

The reason is some packages ended up with a `module_path == None` when accessing form `pkg_resources.working_set`:
```python
import pkg_resources
[dist for dist in pkg_resources.working_set if not dist.module_path]
Out[4]: [terminado 0.8.3 (/home/fernandocamargo/anaconda3/envs/logit/lib/python3.6/site-packages)]
```

Here is a conda environment yaml file that can reproduce this issue:
```
name: logit
channels:
#- anaconda
- conda-forge
- anaconda
- pytorch
- https://public.dhe.ibm.com/ibmdl/export/pub/software/server/ibm-ai/conda
- powerai
dependencies:
- python=3.6
- pandas=1.0
- pyarrow=0.15
- matplotlib=3.1
- plotly=4.6
- scipy=1.3
- numpy=1.16
- seaborn=0.10
- scikit-learn=0.22
- pytorch=1.2
- statsmodels=0.11
- holidays=0.9
- convertdate=2.2
- python-dateutil=2.8
- fbprophet=0.6
- luigi=2.8
- tqdm=4.44
- jupyterlab=1.2
- ipywidgets=7.5
- pyspark=2.4.5
- psutil=5.5
- pydantic=1.6
- sqlalchemy=1.3
- psycopg2=2.8
# GPU:
- cudatoolkit=10.1
- pip
- pip:
#  CPU:
#  - mxnet==1.4.1
#  GPU:
  - mxnet-cu101mkl==1.4.1
#  Power-9:
#  - /root/packages/mxnet/mxnet-1.4.1-cp36-cp36m-linux_ppc64le.whl
  - gluonts==0.5
  - pmdarima==1.4
  - torchbearer==0.5
  - imbalanced-learn==0.6
  - tensorboardx==2.0
  - diskcache==3.0
  - bentoml==0.8.3
```

Interestingly, installing terminado alone does not cause this problem. I'm not sure if this is caused by other packages in this list. But will submit a fix to ensure BentoML handles this scenario properly.

We will release a new version including this fix very soon.


--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `bentoml/saved_bundle/pip_pkg.py`
Content:
```
1 # Copyright 2019 Atalaya Tech, Inc.
2 
3 # Licensed under the Apache License, Version 2.0 (the "License");
4 # you may not use this file except in compliance with the License.
5 # You may obtain a copy of the License at
6 
7 # http://www.apache.org/licenses/LICENSE-2.0
8 
9 # Unless required by applicable law or agreed to in writing, software
10 # distributed under the License is distributed on an "AS IS" BASIS,
11 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 # See the License for the specific language governing permissions and
13 # limitations under the License.
14 
15 import os
16 import sys
17 import logging
18 import pkgutil
19 import ast
20 import zipimport
21 
22 
23 EPP_NO_ERROR = 0
24 EPP_PKG_NOT_EXIST = 1
25 EPP_PKG_VERSION_MISMATCH = 2
26 
27 __mm = None
28 
29 
30 logger = logging.getLogger(__name__)
31 
32 
33 def parse_requirement_string(rs):
34     name, _, version = rs.partition("==")
35     return name, version
36 
37 
38 def verify_pkg(pkg_name, pkg_version):
39     global __mm  # pylint: disable=global-statement
40     if __mm is None:
41         __mm = ModuleManager()
42     return __mm.verify_pkg(pkg_name, pkg_version)
43 
44 
45 def seek_pip_dependencies(target_py_file_path):
46     global __mm  # pylint: disable=global-statement
47     if __mm is None:
48         __mm = ModuleManager()
49     return __mm.seek_pip_dependencies(target_py_file_path)
50 
51 
52 def get_all_pip_installed_modules():
53     global __mm  # pylint: disable=global-statement
54     if __mm is None:
55         __mm = ModuleManager()
56 
57     installed_modules = list(
58         # local modules are the ones imported from current directory, either from a
59         # module.py file or a module directory that contains a `__init__.py` file
60         filter(lambda m: not m.is_local, __mm.searched_modules.values())
61     )
62     return list(map(lambda m: m.name, installed_modules))
63 
64 
65 class ModuleInfo(object):
66     def __init__(self, name, path, is_local, is_pkg):
67         super(ModuleInfo, self).__init__()
68         self.name = name
69         self.path = path
70         self.is_local = is_local
71         self.is_pkg = is_pkg
72 
73 
74 class ModuleManager(object):
75     def __init__(self):
76         super(ModuleManager, self).__init__()
77         self.pip_pkg_map = {}
78         self.pip_module_map = {}
79         self.setuptools_module_set = set()
80         self.nonlocal_package_path = set()
81 
82         import pkg_resources
83 
84         for dist in pkg_resources.working_set:  # pylint: disable=not-an-iterable
85             if os.path.realpath(dist.module_path) != os.getcwd():
86                 # add to nonlocal_package path only if it's not current directory
87                 self.nonlocal_package_path.add(dist.module_path)
88 
89             self.pip_pkg_map[dist._key] = dist._version
90             for mn in dist._get_metadata("top_level.txt"):
91                 if dist._key != "setuptools":
92                     self.pip_module_map.setdefault(mn, []).append(
93                         (dist._key, dist._version)
94                     )
95                 else:
96                     self.setuptools_module_set.add(mn)
97 
98         self.searched_modules = {}
99         for m in pkgutil.iter_modules():
100             if isinstance(m.module_finder, zipimport.zipimporter):
101                 logger.warning(f"Skipped unsupported zipimporter {m.module_finder}")
102                 continue
103             if m.name not in self.searched_modules:
104                 path = m.module_finder.path
105                 is_local = self.is_local_path(path)
106                 self.searched_modules[m.name] = ModuleInfo(
107                     m.name, path, is_local, m.ispkg
108                 )
109 
110     def verify_pkg(self, pkg_name, pkg_version):
111         if pkg_name not in self.pip_pkg_map:
112             # package does not exist in the current python session
113             return EPP_PKG_NOT_EXIST
114         if pkg_version and pkg_version != self.pip_pkg_map[pkg_name]:
115             # package version is different from the version being used
116             # in the current python session
117             return EPP_PKG_VERSION_MISMATCH
118         return EPP_NO_ERROR
119 
120     def seek_pip_dependencies(self, target_py_file_path):
121         work = DepSeekWork(self, target_py_file_path)
122         work.do()
123         requirements = {}
124         for _, pkg_info_list in work.dependencies.items():
125             for pkg_name, pkg_version in pkg_info_list:
126                 requirements[pkg_name] = pkg_version
127 
128         return requirements, work.unknown_module_set
129 
130     def is_local_path(self, path):
131         if path in self.nonlocal_package_path:
132             return False
133 
134         dir_name = os.path.split(path)[1]
135 
136         if (
137             "site-packages" in path
138             or "anaconda" in path
139             or path.endswith("packages")
140             or dir_name == "bin"
141             or dir_name.startswith("lib")
142             or dir_name.startswith("python")
143             or dir_name.startswith("plat")
144         ):
145             self.nonlocal_package_path.add(path)
146             return False
147 
148         return True
149 
150 
151 class DepSeekWork(object):
152     def __init__(self, module_manager, target_py_file_path):
153         super(DepSeekWork, self).__init__()
154         self.module_manager = module_manager
155         self.target_py_file_path = target_py_file_path
156 
157         self.dependencies = {}
158         self.unknown_module_set = set()
159         self.parsed_module_set = set()
160 
161     def do(self):
162         self.seek_in_file(self.target_py_file_path)
163 
164     def seek_in_file(self, file_path):
165         # Extract all dependency modules by searching through the trees of the Python
166         # abstract syntax grammar with Python's built-in ast module
167         try:
168             with open(file_path) as f:
169                 content = f.read()
170         except UnicodeDecodeError:
171             with open(file_path, encoding='utf-8') as f:
172                 content = f.read()
173 
174         tree = ast.parse(content)
175         import_set = set()
176         for node in ast.walk(tree):
177             if isinstance(node, ast.Import):
178                 for name in node.names:
179                     import_set.add(name.name.partition(".")[0])
180             elif isinstance(node, ast.ImportFrom):
181                 if node.module is not None and node.level == 0:
182                     import_set.add(node.module.partition(".")[0])
183         for module_name in import_set:
184             # Avoid parsing BentoML when BentoML is imported from local source code repo
185             if module_name == 'bentoml':
186                 continue
187             if module_name in self.parsed_module_set:
188                 continue
189             self.parsed_module_set.add(module_name)
190 
191             if module_name in self.module_manager.searched_modules:
192                 m = self.module_manager.searched_modules[module_name]
193                 if m.is_local:
194                     # Recursively search dependencies in sub-modules
195                     if m.is_pkg:
196                         self.seek_in_dir(os.path.join(m.path, m.name))
197                     else:
198                         self.seek_in_file(os.path.join(m.path, "{}.py".format(m.name)))
199                 else:
200                     # check if the package has already been added to the list
201                     if (
202                         module_name in self.module_manager.pip_module_map
203                         and module_name not in self.dependencies
204                         and module_name not in self.module_manager.setuptools_module_set
205                     ):
206                         self.dependencies[
207                             module_name
208                         ] = self.module_manager.pip_module_map[module_name]
209             else:
210                 if module_name in self.module_manager.pip_module_map:
211                     if module_name not in self.dependencies:
212                         # In some special cases, the pip-installed module can not
213                         # be located in the searched_modules
214                         self.dependencies[
215                             module_name
216                         ] = self.module_manager.pip_module_map[module_name]
217                 else:
218                     if module_name not in sys.builtin_module_names:
219                         self.unknown_module_set.add(module_name)
220 
221     def seek_in_dir(self, dir_path):
222         for path, dir_list, file_list in os.walk(dir_path):
223             for file_name in file_list:
224                 if not file_name.endswith(".py"):
225                     continue
226                 self.seek_in_file(os.path.join(path, file_name))
227             for dir_name in dir_list:
228                 if dir_name == '__pycache__':
229                     continue
230                 self.seek_in_dir(os.path.join(path, dir_name))
231
```
--- 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/bentoml/saved_bundle/pip_pkg.py b/bentoml/saved_bundle/pip_pkg.py
--- a/bentoml/saved_bundle/pip_pkg.py
+++ b/bentoml/saved_bundle/pip_pkg.py
@@ -82,9 +82,14 @@
         import pkg_resources
 
         for dist in pkg_resources.working_set:  # pylint: disable=not-an-iterable
-            if os.path.realpath(dist.module_path) != os.getcwd():
+            module_path = dist.module_path or dist.location
+            if not module_path:
+                # Skip if no module path was found for pkg distribution
+                continue
+
+            if os.path.realpath(module_path) != os.getcwd():
                 # add to nonlocal_package path only if it's not current directory
-                self.nonlocal_package_path.add(dist.module_path)
+                self.nonlocal_package_path.add(module_path)
 
             self.pip_pkg_map[dist._key] = dist._version
             for mn in dist._get_metadata("top_level.txt"):

{"golden_diff": "diff --git a/bentoml/saved_bundle/pip_pkg.py b/bentoml/saved_bundle/pip_pkg.py\n--- a/bentoml/saved_bundle/pip_pkg.py\n+++ b/bentoml/saved_bundle/pip_pkg.py\n@@ -82,9 +82,14 @@\n         import pkg_resources\n \n         for dist in pkg_resources.working_set:  # pylint: disable=not-an-iterable\n-            if os.path.realpath(dist.module_path) != os.getcwd():\n+            module_path = dist.module_path or dist.location\n+            if not module_path:\n+                # Skip if no module path was found for pkg distribution\n+                continue\n+\n+            if os.path.realpath(module_path) != os.getcwd():\n                 # add to nonlocal_package path only if it's not current directory\n-                self.nonlocal_package_path.add(dist.module_path)\n+                self.nonlocal_package_path.add(module_path)\n \n             self.pip_pkg_map[dist._key] = dist._version\n             for mn in dist._get_metadata(\"top_level.txt\"):\n", "issue": "Failed to package local pip installed dependency in 0.8.4\nAn issue first reported in #bentoml-users slack channel by @fernandocamargoti\r\n\r\nIn some python environment, we see the following error when doing a `BenoService#save`:\r\n```\r\nTypeError: expected str, bytes or os.PathLike object, not NoneType\r\n```\r\n\r\nThe reason is some packages ended up with a `module_path == None` when accessing form `pkg_resources.working_set`:\r\n```python\r\nimport pkg_resources\r\n[dist for dist in pkg_resources.working_set if not dist.module_path]\r\nOut[4]: [terminado 0.8.3 (/home/fernandocamargo/anaconda3/envs/logit/lib/python3.6/site-packages)]\r\n```\r\n\r\nHere is a conda environment yaml file that can reproduce this issue:\r\n```\r\nname: logit\r\nchannels:\r\n#- anaconda\r\n- conda-forge\r\n- anaconda\r\n- pytorch\r\n- https://public.dhe.ibm.com/ibmdl/export/pub/software/server/ibm-ai/conda\r\n- powerai\r\ndependencies:\r\n- python=3.6\r\n- pandas=1.0\r\n- pyarrow=0.15\r\n- matplotlib=3.1\r\n- plotly=4.6\r\n- scipy=1.3\r\n- numpy=1.16\r\n- seaborn=0.10\r\n- scikit-learn=0.22\r\n- pytorch=1.2\r\n- statsmodels=0.11\r\n- holidays=0.9\r\n- convertdate=2.2\r\n- python-dateutil=2.8\r\n- fbprophet=0.6\r\n- luigi=2.8\r\n- tqdm=4.44\r\n- jupyterlab=1.2\r\n- ipywidgets=7.5\r\n- pyspark=2.4.5\r\n- psutil=5.5\r\n- pydantic=1.6\r\n- sqlalchemy=1.3\r\n- psycopg2=2.8\r\n# GPU:\r\n- cudatoolkit=10.1\r\n- pip\r\n- pip:\r\n#  CPU:\r\n#  - mxnet==1.4.1\r\n#  GPU:\r\n  - mxnet-cu101mkl==1.4.1\r\n#  Power-9:\r\n#  - /root/packages/mxnet/mxnet-1.4.1-cp36-cp36m-linux_ppc64le.whl\r\n  - gluonts==0.5\r\n  - pmdarima==1.4\r\n  - torchbearer==0.5\r\n  - imbalanced-learn==0.6\r\n  - tensorboardx==2.0\r\n  - diskcache==3.0\r\n  - bentoml==0.8.3\r\n```\r\n\r\nInterestingly, installing terminado alone does not cause this problem. I'm not sure if this is caused by other packages in this list. But will submit a fix to ensure BentoML handles this scenario properly.\r\n\r\nWe will release a new version including this fix very soon.\r\n\n", "before_files": [{"content": "# Copyright 2019 Atalaya Tech, Inc.\n\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n\n# http://www.apache.org/licenses/LICENSE-2.0\n\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\nimport os\nimport sys\nimport logging\nimport pkgutil\nimport ast\nimport zipimport\n\n\nEPP_NO_ERROR = 0\nEPP_PKG_NOT_EXIST = 1\nEPP_PKG_VERSION_MISMATCH = 2\n\n__mm = None\n\n\nlogger = logging.getLogger(__name__)\n\n\ndef parse_requirement_string(rs):\n    name, _, version = rs.partition(\"==\")\n    return name, version\n\n\ndef verify_pkg(pkg_name, pkg_version):\n    global __mm  # pylint: disable=global-statement\n    if __mm is None:\n        __mm = ModuleManager()\n    return __mm.verify_pkg(pkg_name, pkg_version)\n\n\ndef seek_pip_dependencies(target_py_file_path):\n    global __mm  # pylint: disable=global-statement\n    if __mm is None:\n        __mm = ModuleManager()\n    return __mm.seek_pip_dependencies(target_py_file_path)\n\n\ndef get_all_pip_installed_modules():\n    global __mm  # pylint: disable=global-statement\n    if __mm is None:\n        __mm = ModuleManager()\n\n    installed_modules = list(\n        # local modules are the ones imported from current directory, either from a\n        # module.py file or a module directory that contains a `__init__.py` file\n        filter(lambda m: not m.is_local, __mm.searched_modules.values())\n    )\n    return list(map(lambda m: m.name, installed_modules))\n\n\nclass ModuleInfo(object):\n    def __init__(self, name, path, is_local, is_pkg):\n        super(ModuleInfo, self).__init__()\n        self.name = name\n        self.path = path\n        self.is_local = is_local\n        self.is_pkg = is_pkg\n\n\nclass ModuleManager(object):\n    def __init__(self):\n        super(ModuleManager, self).__init__()\n        self.pip_pkg_map = {}\n        self.pip_module_map = {}\n        self.setuptools_module_set = set()\n        self.nonlocal_package_path = set()\n\n        import pkg_resources\n\n        for dist in pkg_resources.working_set:  # pylint: disable=not-an-iterable\n            if os.path.realpath(dist.module_path) != os.getcwd():\n                # add to nonlocal_package path only if it's not current directory\n                self.nonlocal_package_path.add(dist.module_path)\n\n            self.pip_pkg_map[dist._key] = dist._version\n            for mn in dist._get_metadata(\"top_level.txt\"):\n                if dist._key != \"setuptools\":\n                    self.pip_module_map.setdefault(mn, []).append(\n                        (dist._key, dist._version)\n                    )\n                else:\n                    self.setuptools_module_set.add(mn)\n\n        self.searched_modules = {}\n        for m in pkgutil.iter_modules():\n            if isinstance(m.module_finder, zipimport.zipimporter):\n                logger.warning(f\"Skipped unsupported zipimporter {m.module_finder}\")\n                continue\n            if m.name not in self.searched_modules:\n                path = m.module_finder.path\n                is_local = self.is_local_path(path)\n                self.searched_modules[m.name] = ModuleInfo(\n                    m.name, path, is_local, m.ispkg\n                )\n\n    def verify_pkg(self, pkg_name, pkg_version):\n        if pkg_name not in self.pip_pkg_map:\n            # package does not exist in the current python session\n            return EPP_PKG_NOT_EXIST\n        if pkg_version and pkg_version != self.pip_pkg_map[pkg_name]:\n            # package version is different from the version being used\n            # in the current python session\n            return EPP_PKG_VERSION_MISMATCH\n        return EPP_NO_ERROR\n\n    def seek_pip_dependencies(self, target_py_file_path):\n        work = DepSeekWork(self, target_py_file_path)\n        work.do()\n        requirements = {}\n        for _, pkg_info_list in work.dependencies.items():\n            for pkg_name, pkg_version in pkg_info_list:\n                requirements[pkg_name] = pkg_version\n\n        return requirements, work.unknown_module_set\n\n    def is_local_path(self, path):\n        if path in self.nonlocal_package_path:\n            return False\n\n        dir_name = os.path.split(path)[1]\n\n        if (\n            \"site-packages\" in path\n            or \"anaconda\" in path\n            or path.endswith(\"packages\")\n            or dir_name == \"bin\"\n            or dir_name.startswith(\"lib\")\n            or dir_name.startswith(\"python\")\n            or dir_name.startswith(\"plat\")\n        ):\n            self.nonlocal_package_path.add(path)\n            return False\n\n        return True\n\n\nclass DepSeekWork(object):\n    def __init__(self, module_manager, target_py_file_path):\n        super(DepSeekWork, self).__init__()\n        self.module_manager = module_manager\n        self.target_py_file_path = target_py_file_path\n\n        self.dependencies = {}\n        self.unknown_module_set = set()\n        self.parsed_module_set = set()\n\n    def do(self):\n        self.seek_in_file(self.target_py_file_path)\n\n    def seek_in_file(self, file_path):\n        # Extract all dependency modules by searching through the trees of the Python\n        # abstract syntax grammar with Python's built-in ast module\n        try:\n            with open(file_path) as f:\n                content = f.read()\n        except UnicodeDecodeError:\n            with open(file_path, encoding='utf-8') as f:\n                content = f.read()\n\n        tree = ast.parse(content)\n        import_set = set()\n        for node in ast.walk(tree):\n            if isinstance(node, ast.Import):\n                for name in node.names:\n                    import_set.add(name.name.partition(\".\")[0])\n            elif isinstance(node, ast.ImportFrom):\n                if node.module is not None and node.level == 0:\n                    import_set.add(node.module.partition(\".\")[0])\n        for module_name in import_set:\n            # Avoid parsing BentoML when BentoML is imported from local source code repo\n            if module_name == 'bentoml':\n                continue\n            if module_name in self.parsed_module_set:\n                continue\n            self.parsed_module_set.add(module_name)\n\n            if module_name in self.module_manager.searched_modules:\n                m = self.module_manager.searched_modules[module_name]\n                if m.is_local:\n                    # Recursively search dependencies in sub-modules\n                    if m.is_pkg:\n                        self.seek_in_dir(os.path.join(m.path, m.name))\n                    else:\n                        self.seek_in_file(os.path.join(m.path, \"{}.py\".format(m.name)))\n                else:\n                    # check if the package has already been added to the list\n                    if (\n                        module_name in self.module_manager.pip_module_map\n                        and module_name not in self.dependencies\n                        and module_name not in self.module_manager.setuptools_module_set\n                    ):\n                        self.dependencies[\n                            module_name\n                        ] = self.module_manager.pip_module_map[module_name]\n            else:\n                if module_name in self.module_manager.pip_module_map:\n                    if module_name not in self.dependencies:\n                        # In some special cases, the pip-installed module can not\n                        # be located in the searched_modules\n                        self.dependencies[\n                            module_name\n                        ] = self.module_manager.pip_module_map[module_name]\n                else:\n                    if module_name not in sys.builtin_module_names:\n                        self.unknown_module_set.add(module_name)\n\n    def seek_in_dir(self, dir_path):\n        for path, dir_list, file_list in os.walk(dir_path):\n            for file_name in file_list:\n                if not file_name.endswith(\".py\"):\n                    continue\n                self.seek_in_file(os.path.join(path, file_name))\n            for dir_name in dir_list:\n                if dir_name == '__pycache__':\n                    continue\n                self.seek_in_dir(os.path.join(path, dir_name))\n", "path": "bentoml/saved_bundle/pip_pkg.py"}], "after_files": [{"content": "# Copyright 2019 Atalaya Tech, Inc.\n\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n\n# http://www.apache.org/licenses/LICENSE-2.0\n\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\nimport os\nimport sys\nimport logging\nimport pkgutil\nimport ast\nimport zipimport\n\n\nEPP_NO_ERROR = 0\nEPP_PKG_NOT_EXIST = 1\nEPP_PKG_VERSION_MISMATCH = 2\n\n__mm = None\n\n\nlogger = logging.getLogger(__name__)\n\n\ndef parse_requirement_string(rs):\n    name, _, version = rs.partition(\"==\")\n    return name, version\n\n\ndef verify_pkg(pkg_name, pkg_version):\n    global __mm  # pylint: disable=global-statement\n    if __mm is None:\n        __mm = ModuleManager()\n    return __mm.verify_pkg(pkg_name, pkg_version)\n\n\ndef seek_pip_dependencies(target_py_file_path):\n    global __mm  # pylint: disable=global-statement\n    if __mm is None:\n        __mm = ModuleManager()\n    return __mm.seek_pip_dependencies(target_py_file_path)\n\n\ndef get_all_pip_installed_modules():\n    global __mm  # pylint: disable=global-statement\n    if __mm is None:\n        __mm = ModuleManager()\n\n    installed_modules = list(\n        # local modules are the ones imported from current directory, either from a\n        # module.py file or a module directory that contains a `__init__.py` file\n        filter(lambda m: not m.is_local, __mm.searched_modules.values())\n    )\n    return list(map(lambda m: m.name, installed_modules))\n\n\nclass ModuleInfo(object):\n    def __init__(self, name, path, is_local, is_pkg):\n        super(ModuleInfo, self).__init__()\n        self.name = name\n        self.path = path\n        self.is_local = is_local\n        self.is_pkg = is_pkg\n\n\nclass ModuleManager(object):\n    def __init__(self):\n        super(ModuleManager, self).__init__()\n        self.pip_pkg_map = {}\n        self.pip_module_map = {}\n        self.setuptools_module_set = set()\n        self.nonlocal_package_path = set()\n\n        import pkg_resources\n\n        for dist in pkg_resources.working_set:  # pylint: disable=not-an-iterable\n            module_path = dist.module_path or dist.location\n            if not module_path:\n                # Skip if no module path was found for pkg distribution\n                continue\n\n            if os.path.realpath(module_path) != os.getcwd():\n                # add to nonlocal_package path only if it's not current directory\n                self.nonlocal_package_path.add(module_path)\n\n            self.pip_pkg_map[dist._key] = dist._version\n            for mn in dist._get_metadata(\"top_level.txt\"):\n                if dist._key != \"setuptools\":\n                    self.pip_module_map.setdefault(mn, []).append(\n                        (dist._key, dist._version)\n                    )\n                else:\n                    self.setuptools_module_set.add(mn)\n\n        self.searched_modules = {}\n        for m in pkgutil.iter_modules():\n            if isinstance(m.module_finder, zipimport.zipimporter):\n                logger.warning(f\"Skipped unsupported zipimporter {m.module_finder}\")\n                continue\n            if m.name not in self.searched_modules:\n                path = m.module_finder.path\n                is_local = self.is_local_path(path)\n                self.searched_modules[m.name] = ModuleInfo(\n                    m.name, path, is_local, m.ispkg\n                )\n\n    def verify_pkg(self, pkg_name, pkg_version):\n        if pkg_name not in self.pip_pkg_map:\n            # package does not exist in the current python session\n            return EPP_PKG_NOT_EXIST\n        if pkg_version and pkg_version != self.pip_pkg_map[pkg_name]:\n            # package version is different from the version being used\n            # in the current python session\n            return EPP_PKG_VERSION_MISMATCH\n        return EPP_NO_ERROR\n\n    def seek_pip_dependencies(self, target_py_file_path):\n        work = DepSeekWork(self, target_py_file_path)\n        work.do()\n        requirements = {}\n        for _, pkg_info_list in work.dependencies.items():\n            for pkg_name, pkg_version in pkg_info_list:\n                requirements[pkg_name] = pkg_version\n\n        return requirements, work.unknown_module_set\n\n    def is_local_path(self, path):\n        if path in self.nonlocal_package_path:\n            return False\n\n        dir_name = os.path.split(path)[1]\n\n        if (\n            \"site-packages\" in path\n            or \"anaconda\" in path\n            or path.endswith(\"packages\")\n            or dir_name == \"bin\"\n            or dir_name.startswith(\"lib\")\n            or dir_name.startswith(\"python\")\n            or dir_name.startswith(\"plat\")\n        ):\n            self.nonlocal_package_path.add(path)\n            return False\n\n        return True\n\n\nclass DepSeekWork(object):\n    def __init__(self, module_manager, target_py_file_path):\n        super(DepSeekWork, self).__init__()\n        self.module_manager = module_manager\n        self.target_py_file_path = target_py_file_path\n\n        self.dependencies = {}\n        self.unknown_module_set = set()\n        self.parsed_module_set = set()\n\n    def do(self):\n        self.seek_in_file(self.target_py_file_path)\n\n    def seek_in_file(self, file_path):\n        # Extract all dependency modules by searching through the trees of the Python\n        # abstract syntax grammar with Python's built-in ast module\n        try:\n            with open(file_path) as f:\n                content = f.read()\n        except UnicodeDecodeError:\n            with open(file_path, encoding='utf-8') as f:\n                content = f.read()\n\n        tree = ast.parse(content)\n        import_set = set()\n        for node in ast.walk(tree):\n            if isinstance(node, ast.Import):\n                for name in node.names:\n                    import_set.add(name.name.partition(\".\")[0])\n            elif isinstance(node, ast.ImportFrom):\n                if node.module is not None and node.level == 0:\n                    import_set.add(node.module.partition(\".\")[0])\n        for module_name in import_set:\n            # Avoid parsing BentoML when BentoML is imported from local source code repo\n            if module_name == 'bentoml':\n                continue\n            if module_name in self.parsed_module_set:\n                continue\n            self.parsed_module_set.add(module_name)\n\n            if module_name in self.module_manager.searched_modules:\n                m = self.module_manager.searched_modules[module_name]\n                if m.is_local:\n                    # Recursively search dependencies in sub-modules\n                    if m.is_pkg:\n                        self.seek_in_dir(os.path.join(m.path, m.name))\n                    else:\n                        self.seek_in_file(os.path.join(m.path, \"{}.py\".format(m.name)))\n                else:\n                    # check if the package has already been added to the list\n                    if (\n                        module_name in self.module_manager.pip_module_map\n                        and module_name not in self.dependencies\n                        and module_name not in self.module_manager.setuptools_module_set\n                    ):\n                        self.dependencies[\n                            module_name\n                        ] = self.module_manager.pip_module_map[module_name]\n            else:\n                if module_name in self.module_manager.pip_module_map:\n                    if module_name not in self.dependencies:\n                        # In some special cases, the pip-installed module can not\n                        # be located in the searched_modules\n                        self.dependencies[\n                            module_name\n                        ] = self.module_manager.pip_module_map[module_name]\n                else:\n                    if module_name not in sys.builtin_module_names:\n                        self.unknown_module_set.add(module_name)\n\n    def seek_in_dir(self, dir_path):\n        for path, dir_list, file_list in os.walk(dir_path):\n            for file_name in file_list:\n                if not file_name.endswith(\".py\"):\n                    continue\n                self.seek_in_file(os.path.join(path, file_name))\n            for dir_name in dir_list:\n                if dir_name == '__pycache__':\n                    continue\n                self.seek_in_dir(os.path.join(path, dir_name))\n", "path": "bentoml/saved_bundle/pip_pkg.py"}]}