We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
Documentation build takes a long time
Reproducible with `SPHINXOPTS=-E time ./docs/generate_docs.bash` - notably, it takes multiple minutes to build this page:
```
reading sources... [ 18%] api/fiftyone.core.odm                                                 
```

This is at least part of the reason why documentation builds on GitHub actions are taking so long.

Possibly related to #182, although I don't think earlier builds on GitHub actions ran into this.

--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `fiftyone/core/odm/database.py`
Content:
```
1 """
2 Database connection.
3 
4 | Copyright 2017-2020, Voxel51, Inc.
5 | `voxel51.com <https://voxel51.com/>`_
6 |
7 """
8 # pragma pylint: disable=redefined-builtin
9 # pragma pylint: disable=unused-wildcard-import
10 # pragma pylint: disable=wildcard-import
11 from __future__ import absolute_import
12 from __future__ import division
13 from __future__ import print_function
14 from __future__ import unicode_literals
15 
16 # pragma pylint: enable=redefined-builtin
17 # pragma pylint: enable=unused-wildcard-import
18 # pragma pylint: enable=wildcard-import
19 
20 
21 from mongoengine import connect
22 import pymongo
23 
24 _DEFAULT_DATABASE = "fiftyone"
25 
26 
27 _db = connect(_DEFAULT_DATABASE)
28 
29 
30 def get_db_conn():
31     """Creates a connection to the database"""
32     return pymongo.MongoClient()[_DEFAULT_DATABASE]
33 
34 
35 def drop_database():
36     """Drops the database."""
37     client = pymongo.MongoClient()
38     client.drop_database(_DEFAULT_DATABASE)
39
```

Path: `fiftyone/core/service.py`
Content:
```
1 """
2 FiftyOne Services.
3 
4 | Copyright 2017-2020, Voxel51, Inc.
5 | `voxel51.com <https://voxel51.com/>`_
6 |
7 """
8 # pragma pylint: disable=redefined-builtin
9 # pragma pylint: disable=unused-wildcard-import
10 # pragma pylint: disable=wildcard-import
11 from __future__ import absolute_import
12 from __future__ import division
13 from __future__ import print_function
14 from __future__ import unicode_literals
15 from builtins import *
16 
17 # pragma pylint: enable=redefined-builtin
18 # pragma pylint: enable=unused-wildcard-import
19 # pragma pylint: enable=wildcard-import
20 
21 import logging
22 import os
23 import re
24 import subprocess
25 import sys
26 
27 from packaging.version import Version
28 import requests
29 
30 import eta.core.utils as etau
31 
32 import fiftyone.constants as foc
33 
34 
35 logger = logging.getLogger(__name__)
36 
37 
38 class Service(object):
39     """Interface for FiftyOne services.
40 
41     All services must define a ``command`` property.
42 
43     Services are run in an isolated Python subprocess (see ``_service_main.py``)
44     to ensure that they are shut down when the main Python process exits. The
45     ``command`` and ``working_dir`` properties control the execution of the
46     service in the subprocess.
47     """
48 
49     working_dir = "."
50 
51     def __init__(self):
52         """Creates (starts) the Service."""
53         self._system = os.system
54         self._is_server = os.environ.get(
55             "FIFTYONE_SERVER", False
56         ) or os.environ.get("FIFTYONE_DISABLE_SERVICES", False)
57         self.child = None
58         if not self._is_server:
59             self.start()
60 
61     def __del__(self):
62         """Deletes (stops) the Service."""
63         if not self._is_server:
64             try:
65                 self.stop()
66             except:
67                 # something probably failed due to interpreter shutdown, which
68                 # will be handled by _service_main.py
69                 pass
70 
71     @property
72     def command(self):
73         raise NotImplementedError("subclasses must define `command`")
74 
75     def start(self):
76         """Starts the Service."""
77         service_main_path = os.path.join(
78             os.path.dirname(os.path.abspath(__file__)),
79             "..",
80             "_service_main.py",
81         )
82         self.child = subprocess.Popen(
83             [sys.executable, service_main_path] + self.command,
84             cwd=self.working_dir,
85             stdin=subprocess.PIPE,
86         )
87 
88     def stop(self):
89         """Stops the Service."""
90         self.child.stdin.close()
91         self.child.wait()
92 
93     def wait(self):
94         """Waits for the Service to exit and returns its exit code."""
95         return self.child.wait()
96 
97 
98 class DatabaseService(Service):
99     """Service that controls the underlying MongoDB database."""
100 
101     MIN_MONGO_VERSION = "3.6"
102 
103     @property
104     def command(self):
105         return [
106             DatabaseService.find_mongod(),
107             "--dbpath",
108             foc.DB_PATH,
109             "--logpath",
110             foc.DB_LOG_PATH,
111         ]
112 
113     def start(self):
114         """Starts the DatabaseService."""
115         for folder in (foc.DB_PATH, os.path.dirname(foc.DB_LOG_PATH)):
116             if not os.path.isdir(folder):
117                 os.makedirs(folder)
118 
119         super().start()
120 
121         # Drop non-persistent datasets
122         import fiftyone.core.dataset as fod
123 
124         fod.delete_non_persistent_datasets()
125 
126     @staticmethod
127     def find_mongod():
128         search_paths = [
129             foc.FIFTYONE_DB_BIN_DIR,
130             os.path.join(foc.FIFTYONE_CONFIG_DIR, "bin"),
131         ] + os.environ["PATH"].split(os.pathsep)
132         searched = set()
133         attempts = []
134         for folder in search_paths:
135             if folder in searched:
136                 continue
137             searched.add(folder)
138             mongod_path = os.path.join(folder, "mongod")
139             if os.path.isfile(mongod_path):
140                 ok, out, err = etau.communicate([mongod_path, "--version"])
141                 out = out.decode(errors="ignore").strip()
142                 err = err.decode(errors="ignore").strip()
143                 mongod_version = None
144                 if ok:
145                     match = re.search(r"db version.+?([\d\.]+)", out, re.I)
146                     if match:
147                         mongod_version = match.group(1)
148                         if Version(mongod_version) >= Version(
149                             DatabaseService.MIN_MONGO_VERSION
150                         ):
151                             return mongod_path
152                 attempts.append((mongod_path, mongod_version, err))
153         for path, version, err in attempts:
154             if version is not None:
155                 logger.warn("%s: incompatible version %s" % (path, version))
156             else:
157                 logger.error("%s: failed to launch: %s" % (path, err))
158         raise RuntimeError(
159             "Could not find mongod >= %s" % DatabaseService.MIN_MONGO_VERSION
160         )
161 
162 
163 class ServerService(Service):
164     """Service that controls the FiftyOne web server."""
165 
166     working_dir = foc.SERVER_DIR
167 
168     def __init__(self, port):
169         self._port = port
170         super(ServerService, self).__init__()
171 
172     def start(self):
173         server_version = None
174         try:
175             server_version = requests.get(
176                 "http://127.0.0.1:%i/fiftyone" % self._port, timeout=2
177             ).json()["version"]
178         except Exception:
179             # There is likely not a fiftyone server running (remote or local),
180             # so start a local server. If there actually is a fiftyone server
181             # running that didn't respond to /fiftyone, the local server will
182             # fail to start but the dashboard will still connect successfully.
183             super().start()
184 
185         if server_version is not None:
186             logger.info("Connected to fiftyone on local port %i" % self._port)
187             if server_version != foc.VERSION:
188                 logger.warn(
189                     "Server version (%s) does not match client version (%s)"
190                     % (server_version, foc.VERSION)
191                 )
192 
193     @property
194     def command(self):
195         command = [
196             "gunicorn",
197             "-w",
198             "1",
199             "--worker-class",
200             "eventlet",
201             "-b",
202             "127.0.0.1:%d" % self._port,
203             "main:app",
204         ]
205         if foc.DEV_INSTALL:
206             command += ["--reload"]
207         return command
208 
209     @property
210     def port(self):
211         """Getter for the current port"""
212         return self._port
213 
214 
215 class AppService(Service):
216     """Service that controls the FiftyOne app."""
217 
218     working_dir = foc.FIFTYONE_APP_DIR
219 
220     @property
221     def command(self):
222         with etau.WorkingDir(foc.FIFTYONE_APP_DIR):
223             if os.path.isfile("FiftyOne.AppImage"):
224                 # linux
225                 args = ["./FiftyOne.AppImage"]
226             elif os.path.isdir("FiftyOne.app"):
227                 args = ["./FiftyOne.app/Contents/MacOS/FiftyOne"]
228             elif os.path.isfile("package.json"):
229                 # dev build
230                 args = ["yarn", "dev"]
231             else:
232                 raise RuntimeError(
233                     "Could not find FiftyOne dashboard in %r"
234                     % foc.FIFTYONE_APP_DIR
235                 )
236         return args
237
```
--- 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/fiftyone/core/odm/database.py b/fiftyone/core/odm/database.py
--- a/fiftyone/core/odm/database.py
+++ b/fiftyone/core/odm/database.py
@@ -22,17 +22,23 @@
 import pymongo
 
 _DEFAULT_DATABASE = "fiftyone"
+_client = None
 
 
-_db = connect(_DEFAULT_DATABASE)
+def _connect():
+    global _client
+    if _client is None:
+        connect(_DEFAULT_DATABASE)
+        _client = pymongo.MongoClient()
 
 
 def get_db_conn():
     """Creates a connection to the database"""
-    return pymongo.MongoClient()[_DEFAULT_DATABASE]
+    _connect()
+    return _client[_DEFAULT_DATABASE]
 
 
 def drop_database():
     """Drops the database."""
-    client = pymongo.MongoClient()
-    client.drop_database(_DEFAULT_DATABASE)
+    _connect()
+    _client.drop_database(_DEFAULT_DATABASE)
diff --git a/fiftyone/core/service.py b/fiftyone/core/service.py
--- a/fiftyone/core/service.py
+++ b/fiftyone/core/service.py
@@ -118,6 +118,11 @@
 
         super().start()
 
+        # Set up a default connection
+        import fiftyone.core.odm as foo
+
+        foo.get_db_conn()
+
         # Drop non-persistent datasets
         import fiftyone.core.dataset as fod

{"golden_diff": "diff --git a/fiftyone/core/odm/database.py b/fiftyone/core/odm/database.py\n--- a/fiftyone/core/odm/database.py\n+++ b/fiftyone/core/odm/database.py\n@@ -22,17 +22,23 @@\n import pymongo\n \n _DEFAULT_DATABASE = \"fiftyone\"\n+_client = None\n \n \n-_db = connect(_DEFAULT_DATABASE)\n+def _connect():\n+    global _client\n+    if _client is None:\n+        connect(_DEFAULT_DATABASE)\n+        _client = pymongo.MongoClient()\n \n \n def get_db_conn():\n     \"\"\"Creates a connection to the database\"\"\"\n-    return pymongo.MongoClient()[_DEFAULT_DATABASE]\n+    _connect()\n+    return _client[_DEFAULT_DATABASE]\n \n \n def drop_database():\n     \"\"\"Drops the database.\"\"\"\n-    client = pymongo.MongoClient()\n-    client.drop_database(_DEFAULT_DATABASE)\n+    _connect()\n+    _client.drop_database(_DEFAULT_DATABASE)\ndiff --git a/fiftyone/core/service.py b/fiftyone/core/service.py\n--- a/fiftyone/core/service.py\n+++ b/fiftyone/core/service.py\n@@ -118,6 +118,11 @@\n \n         super().start()\n \n+        # Set up a default connection\n+        import fiftyone.core.odm as foo\n+\n+        foo.get_db_conn()\n+\n         # Drop non-persistent datasets\n         import fiftyone.core.dataset as fod\n", "issue": "Documentation build takes a long time\nReproducible with `SPHINXOPTS=-E time ./docs/generate_docs.bash` - notably, it takes multiple minutes to build this page:\r\n```\r\nreading sources... [ 18%] api/fiftyone.core.odm                                                 \r\n```\r\n\r\nThis is at least part of the reason why documentation builds on GitHub actions are taking so long.\r\n\r\nPossibly related to #182, although I don't think earlier builds on GitHub actions ran into this.\n", "before_files": [{"content": "\"\"\"\nDatabase connection.\n\n| Copyright 2017-2020, Voxel51, Inc.\n| `voxel51.com <https://voxel51.com/>`_\n|\n\"\"\"\n# pragma pylint: disable=redefined-builtin\n# pragma pylint: disable=unused-wildcard-import\n# pragma pylint: disable=wildcard-import\nfrom __future__ import absolute_import\nfrom __future__ import division\nfrom __future__ import print_function\nfrom __future__ import unicode_literals\n\n# pragma pylint: enable=redefined-builtin\n# pragma pylint: enable=unused-wildcard-import\n# pragma pylint: enable=wildcard-import\n\n\nfrom mongoengine import connect\nimport pymongo\n\n_DEFAULT_DATABASE = \"fiftyone\"\n\n\n_db = connect(_DEFAULT_DATABASE)\n\n\ndef get_db_conn():\n    \"\"\"Creates a connection to the database\"\"\"\n    return pymongo.MongoClient()[_DEFAULT_DATABASE]\n\n\ndef drop_database():\n    \"\"\"Drops the database.\"\"\"\n    client = pymongo.MongoClient()\n    client.drop_database(_DEFAULT_DATABASE)\n", "path": "fiftyone/core/odm/database.py"}, {"content": "\"\"\"\nFiftyOne Services.\n\n| Copyright 2017-2020, Voxel51, Inc.\n| `voxel51.com <https://voxel51.com/>`_\n|\n\"\"\"\n# pragma pylint: disable=redefined-builtin\n# pragma pylint: disable=unused-wildcard-import\n# pragma pylint: disable=wildcard-import\nfrom __future__ import absolute_import\nfrom __future__ import division\nfrom __future__ import print_function\nfrom __future__ import unicode_literals\nfrom builtins import *\n\n# pragma pylint: enable=redefined-builtin\n# pragma pylint: enable=unused-wildcard-import\n# pragma pylint: enable=wildcard-import\n\nimport logging\nimport os\nimport re\nimport subprocess\nimport sys\n\nfrom packaging.version import Version\nimport requests\n\nimport eta.core.utils as etau\n\nimport fiftyone.constants as foc\n\n\nlogger = logging.getLogger(__name__)\n\n\nclass Service(object):\n    \"\"\"Interface for FiftyOne services.\n\n    All services must define a ``command`` property.\n\n    Services are run in an isolated Python subprocess (see ``_service_main.py``)\n    to ensure that they are shut down when the main Python process exits. The\n    ``command`` and ``working_dir`` properties control the execution of the\n    service in the subprocess.\n    \"\"\"\n\n    working_dir = \".\"\n\n    def __init__(self):\n        \"\"\"Creates (starts) the Service.\"\"\"\n        self._system = os.system\n        self._is_server = os.environ.get(\n            \"FIFTYONE_SERVER\", False\n        ) or os.environ.get(\"FIFTYONE_DISABLE_SERVICES\", False)\n        self.child = None\n        if not self._is_server:\n            self.start()\n\n    def __del__(self):\n        \"\"\"Deletes (stops) the Service.\"\"\"\n        if not self._is_server:\n            try:\n                self.stop()\n            except:\n                # something probably failed due to interpreter shutdown, which\n                # will be handled by _service_main.py\n                pass\n\n    @property\n    def command(self):\n        raise NotImplementedError(\"subclasses must define `command`\")\n\n    def start(self):\n        \"\"\"Starts the Service.\"\"\"\n        service_main_path = os.path.join(\n            os.path.dirname(os.path.abspath(__file__)),\n            \"..\",\n            \"_service_main.py\",\n        )\n        self.child = subprocess.Popen(\n            [sys.executable, service_main_path] + self.command,\n            cwd=self.working_dir,\n            stdin=subprocess.PIPE,\n        )\n\n    def stop(self):\n        \"\"\"Stops the Service.\"\"\"\n        self.child.stdin.close()\n        self.child.wait()\n\n    def wait(self):\n        \"\"\"Waits for the Service to exit and returns its exit code.\"\"\"\n        return self.child.wait()\n\n\nclass DatabaseService(Service):\n    \"\"\"Service that controls the underlying MongoDB database.\"\"\"\n\n    MIN_MONGO_VERSION = \"3.6\"\n\n    @property\n    def command(self):\n        return [\n            DatabaseService.find_mongod(),\n            \"--dbpath\",\n            foc.DB_PATH,\n            \"--logpath\",\n            foc.DB_LOG_PATH,\n        ]\n\n    def start(self):\n        \"\"\"Starts the DatabaseService.\"\"\"\n        for folder in (foc.DB_PATH, os.path.dirname(foc.DB_LOG_PATH)):\n            if not os.path.isdir(folder):\n                os.makedirs(folder)\n\n        super().start()\n\n        # Drop non-persistent datasets\n        import fiftyone.core.dataset as fod\n\n        fod.delete_non_persistent_datasets()\n\n    @staticmethod\n    def find_mongod():\n        search_paths = [\n            foc.FIFTYONE_DB_BIN_DIR,\n            os.path.join(foc.FIFTYONE_CONFIG_DIR, \"bin\"),\n        ] + os.environ[\"PATH\"].split(os.pathsep)\n        searched = set()\n        attempts = []\n        for folder in search_paths:\n            if folder in searched:\n                continue\n            searched.add(folder)\n            mongod_path = os.path.join(folder, \"mongod\")\n            if os.path.isfile(mongod_path):\n                ok, out, err = etau.communicate([mongod_path, \"--version\"])\n                out = out.decode(errors=\"ignore\").strip()\n                err = err.decode(errors=\"ignore\").strip()\n                mongod_version = None\n                if ok:\n                    match = re.search(r\"db version.+?([\\d\\.]+)\", out, re.I)\n                    if match:\n                        mongod_version = match.group(1)\n                        if Version(mongod_version) >= Version(\n                            DatabaseService.MIN_MONGO_VERSION\n                        ):\n                            return mongod_path\n                attempts.append((mongod_path, mongod_version, err))\n        for path, version, err in attempts:\n            if version is not None:\n                logger.warn(\"%s: incompatible version %s\" % (path, version))\n            else:\n                logger.error(\"%s: failed to launch: %s\" % (path, err))\n        raise RuntimeError(\n            \"Could not find mongod >= %s\" % DatabaseService.MIN_MONGO_VERSION\n        )\n\n\nclass ServerService(Service):\n    \"\"\"Service that controls the FiftyOne web server.\"\"\"\n\n    working_dir = foc.SERVER_DIR\n\n    def __init__(self, port):\n        self._port = port\n        super(ServerService, self).__init__()\n\n    def start(self):\n        server_version = None\n        try:\n            server_version = requests.get(\n                \"http://127.0.0.1:%i/fiftyone\" % self._port, timeout=2\n            ).json()[\"version\"]\n        except Exception:\n            # There is likely not a fiftyone server running (remote or local),\n            # so start a local server. If there actually is a fiftyone server\n            # running that didn't respond to /fiftyone, the local server will\n            # fail to start but the dashboard will still connect successfully.\n            super().start()\n\n        if server_version is not None:\n            logger.info(\"Connected to fiftyone on local port %i\" % self._port)\n            if server_version != foc.VERSION:\n                logger.warn(\n                    \"Server version (%s) does not match client version (%s)\"\n                    % (server_version, foc.VERSION)\n                )\n\n    @property\n    def command(self):\n        command = [\n            \"gunicorn\",\n            \"-w\",\n            \"1\",\n            \"--worker-class\",\n            \"eventlet\",\n            \"-b\",\n            \"127.0.0.1:%d\" % self._port,\n            \"main:app\",\n        ]\n        if foc.DEV_INSTALL:\n            command += [\"--reload\"]\n        return command\n\n    @property\n    def port(self):\n        \"\"\"Getter for the current port\"\"\"\n        return self._port\n\n\nclass AppService(Service):\n    \"\"\"Service that controls the FiftyOne app.\"\"\"\n\n    working_dir = foc.FIFTYONE_APP_DIR\n\n    @property\n    def command(self):\n        with etau.WorkingDir(foc.FIFTYONE_APP_DIR):\n            if os.path.isfile(\"FiftyOne.AppImage\"):\n                # linux\n                args = [\"./FiftyOne.AppImage\"]\n            elif os.path.isdir(\"FiftyOne.app\"):\n                args = [\"./FiftyOne.app/Contents/MacOS/FiftyOne\"]\n            elif os.path.isfile(\"package.json\"):\n                # dev build\n                args = [\"yarn\", \"dev\"]\n            else:\n                raise RuntimeError(\n                    \"Could not find FiftyOne dashboard in %r\"\n                    % foc.FIFTYONE_APP_DIR\n                )\n        return args\n", "path": "fiftyone/core/service.py"}], "after_files": [{"content": "\"\"\"\nDatabase connection.\n\n| Copyright 2017-2020, Voxel51, Inc.\n| `voxel51.com <https://voxel51.com/>`_\n|\n\"\"\"\n# pragma pylint: disable=redefined-builtin\n# pragma pylint: disable=unused-wildcard-import\n# pragma pylint: disable=wildcard-import\nfrom __future__ import absolute_import\nfrom __future__ import division\nfrom __future__ import print_function\nfrom __future__ import unicode_literals\n\n# pragma pylint: enable=redefined-builtin\n# pragma pylint: enable=unused-wildcard-import\n# pragma pylint: enable=wildcard-import\n\n\nfrom mongoengine import connect\nimport pymongo\n\n_DEFAULT_DATABASE = \"fiftyone\"\n_client = None\n\n\ndef _connect():\n    global _client\n    if _client is None:\n        connect(_DEFAULT_DATABASE)\n        _client = pymongo.MongoClient()\n\n\ndef get_db_conn():\n    \"\"\"Creates a connection to the database\"\"\"\n    _connect()\n    return _client[_DEFAULT_DATABASE]\n\n\ndef drop_database():\n    \"\"\"Drops the database.\"\"\"\n    _connect()\n    _client.drop_database(_DEFAULT_DATABASE)\n", "path": "fiftyone/core/odm/database.py"}, {"content": "\"\"\"\nFiftyOne Services.\n\n| Copyright 2017-2020, Voxel51, Inc.\n| `voxel51.com <https://voxel51.com/>`_\n|\n\"\"\"\n# pragma pylint: disable=redefined-builtin\n# pragma pylint: disable=unused-wildcard-import\n# pragma pylint: disable=wildcard-import\nfrom __future__ import absolute_import\nfrom __future__ import division\nfrom __future__ import print_function\nfrom __future__ import unicode_literals\nfrom builtins import *\n\n# pragma pylint: enable=redefined-builtin\n# pragma pylint: enable=unused-wildcard-import\n# pragma pylint: enable=wildcard-import\n\nimport logging\nimport os\nimport re\nimport subprocess\nimport sys\n\nfrom packaging.version import Version\nimport requests\n\nimport eta.core.utils as etau\n\nimport fiftyone.constants as foc\n\n\nlogger = logging.getLogger(__name__)\n\n\nclass Service(object):\n    \"\"\"Interface for FiftyOne services.\n\n    All services must define a ``command`` property.\n\n    Services are run in an isolated Python subprocess (see ``_service_main.py``)\n    to ensure that they are shut down when the main Python process exits. The\n    ``command`` and ``working_dir`` properties control the execution of the\n    service in the subprocess.\n    \"\"\"\n\n    working_dir = \".\"\n\n    def __init__(self):\n        \"\"\"Creates (starts) the Service.\"\"\"\n        self._system = os.system\n        self._is_server = os.environ.get(\n            \"FIFTYONE_SERVER\", False\n        ) or os.environ.get(\"FIFTYONE_DISABLE_SERVICES\", False)\n        self.child = None\n        if not self._is_server:\n            self.start()\n\n    def __del__(self):\n        \"\"\"Deletes (stops) the Service.\"\"\"\n        if not self._is_server:\n            try:\n                self.stop()\n            except:\n                # something probably failed due to interpreter shutdown, which\n                # will be handled by _service_main.py\n                pass\n\n    @property\n    def command(self):\n        raise NotImplementedError(\"subclasses must define `command`\")\n\n    def start(self):\n        \"\"\"Starts the Service.\"\"\"\n        service_main_path = os.path.join(\n            os.path.dirname(os.path.abspath(__file__)),\n            \"..\",\n            \"_service_main.py\",\n        )\n        self.child = subprocess.Popen(\n            [sys.executable, service_main_path] + self.command,\n            cwd=self.working_dir,\n            stdin=subprocess.PIPE,\n        )\n\n    def stop(self):\n        \"\"\"Stops the Service.\"\"\"\n        self.child.stdin.close()\n        self.child.wait()\n\n    def wait(self):\n        \"\"\"Waits for the Service to exit and returns its exit code.\"\"\"\n        return self.child.wait()\n\n\nclass DatabaseService(Service):\n    \"\"\"Service that controls the underlying MongoDB database.\"\"\"\n\n    MIN_MONGO_VERSION = \"3.6\"\n\n    @property\n    def command(self):\n        return [\n            DatabaseService.find_mongod(),\n            \"--dbpath\",\n            foc.DB_PATH,\n            \"--logpath\",\n            foc.DB_LOG_PATH,\n        ]\n\n    def start(self):\n        \"\"\"Starts the DatabaseService.\"\"\"\n        for folder in (foc.DB_PATH, os.path.dirname(foc.DB_LOG_PATH)):\n            if not os.path.isdir(folder):\n                os.makedirs(folder)\n\n        super().start()\n\n        # Set up a default connection\n        import fiftyone.core.odm as foo\n\n        foo.get_db_conn()\n\n        # Drop non-persistent datasets\n        import fiftyone.core.dataset as fod\n\n        fod.delete_non_persistent_datasets()\n\n    @staticmethod\n    def find_mongod():\n        search_paths = [\n            foc.FIFTYONE_DB_BIN_DIR,\n            os.path.join(foc.FIFTYONE_CONFIG_DIR, \"bin\"),\n        ] + os.environ[\"PATH\"].split(os.pathsep)\n        searched = set()\n        attempts = []\n        for folder in search_paths:\n            if folder in searched:\n                continue\n            searched.add(folder)\n            mongod_path = os.path.join(folder, \"mongod\")\n            if os.path.isfile(mongod_path):\n                ok, out, err = etau.communicate([mongod_path, \"--version\"])\n                out = out.decode(errors=\"ignore\").strip()\n                err = err.decode(errors=\"ignore\").strip()\n                mongod_version = None\n                if ok:\n                    match = re.search(r\"db version.+?([\\d\\.]+)\", out, re.I)\n                    if match:\n                        mongod_version = match.group(1)\n                        if Version(mongod_version) >= Version(\n                            DatabaseService.MIN_MONGO_VERSION\n                        ):\n                            return mongod_path\n                attempts.append((mongod_path, mongod_version, err))\n        for path, version, err in attempts:\n            if version is not None:\n                logger.warn(\"%s: incompatible version %s\" % (path, version))\n            else:\n                logger.error(\"%s: failed to launch: %s\" % (path, err))\n        raise RuntimeError(\n            \"Could not find mongod >= %s\" % DatabaseService.MIN_MONGO_VERSION\n        )\n\n\nclass ServerService(Service):\n    \"\"\"Service that controls the FiftyOne web server.\"\"\"\n\n    working_dir = foc.SERVER_DIR\n\n    def __init__(self, port):\n        self._port = port\n        super(ServerService, self).__init__()\n\n    def start(self):\n        server_version = None\n        try:\n            server_version = requests.get(\n                \"http://127.0.0.1:%i/fiftyone\" % self._port, timeout=2\n            ).json()[\"version\"]\n        except Exception:\n            # There is likely not a fiftyone server running (remote or local),\n            # so start a local server. If there actually is a fiftyone server\n            # running that didn't respond to /fiftyone, the local server will\n            # fail to start but the dashboard will still connect successfully.\n            super().start()\n\n        if server_version is not None:\n            logger.info(\"Connected to fiftyone on local port %i\" % self._port)\n            if server_version != foc.VERSION:\n                logger.warn(\n                    \"Server version (%s) does not match client version (%s)\"\n                    % (server_version, foc.VERSION)\n                )\n\n    @property\n    def command(self):\n        command = [\n            \"gunicorn\",\n            \"-w\",\n            \"1\",\n            \"--worker-class\",\n            \"eventlet\",\n            \"-b\",\n            \"127.0.0.1:%d\" % self._port,\n            \"main:app\",\n        ]\n        if foc.DEV_INSTALL:\n            command += [\"--reload\"]\n        return command\n\n    @property\n    def port(self):\n        \"\"\"Getter for the current port\"\"\"\n        return self._port\n\n\nclass AppService(Service):\n    \"\"\"Service that controls the FiftyOne app.\"\"\"\n\n    working_dir = foc.FIFTYONE_APP_DIR\n\n    @property\n    def command(self):\n        with etau.WorkingDir(foc.FIFTYONE_APP_DIR):\n            if os.path.isfile(\"FiftyOne.AppImage\"):\n                # linux\n                args = [\"./FiftyOne.AppImage\"]\n            elif os.path.isdir(\"FiftyOne.app\"):\n                args = [\"./FiftyOne.app/Contents/MacOS/FiftyOne\"]\n            elif os.path.isfile(\"package.json\"):\n                # dev build\n                args = [\"yarn\", \"dev\"]\n            else:\n                raise RuntimeError(\n                    \"Could not find FiftyOne dashboard in %r\"\n                    % foc.FIFTYONE_APP_DIR\n                )\n        return args\n", "path": "fiftyone/core/service.py"}]}