We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
[FR] Limit integer label filter sliders to integers
Here's a dataset with some scalar values on its samples that have type `IntField`:

```
Name:           quickstart
Media type      image
Num samples:    200
Persistent:     False
Info:           {}
Tags:           ['validation']
Sample fields:
    media_type:   fiftyone.core.fields.StringField
    filepath:     fiftyone.core.fields.StringField
    tags:         fiftyone.core.fields.ListField(fiftyone.core.fields.StringField)
    metadata:     fiftyone.core.fields.EmbeddedDocumentField(fiftyone.core.metadata.Metadata)
    ground_truth: fiftyone.core.fields.EmbeddedDocumentField(fiftyone.core.labels.Detections)
    uniqueness:   fiftyone.core.fields.FloatField
    predictions:  fiftyone.core.fields.EmbeddedDocumentField(fiftyone.core.labels.Detections)
    tp_iou_0_75:  fiftyone.core.fields.IntField
    fp_iou_0_75:  fiftyone.core.fields.IntField
    fn_iou_0_75:  fiftyone.core.fields.IntField
```

It would be desirable if the label filter in the App respected the int data type and locked the slider into int values, rather than allowing/showing `[0.00    [34.92, 81.48]     97.00]` in the App

<img width="267" alt="Screen Shot 2020-10-07 at 2 01 12 PM" src="https://user-images.githubusercontent.com/25985824/95369815-f0f2c480-08a5-11eb-9b16-29c59c7abd7b.png">


--- 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/config.py`
Content:
```
1 """
2 FiftyOne config.
3 
4 | Copyright 2017-2021, Voxel51, Inc.
5 | `voxel51.com <https://voxel51.com/>`_
6 |
7 """
8 import logging
9 import os
10 
11 try:
12     from importlib import metadata as importlib_metadata  # Python 3.8
13 except ImportError:
14     import importlib_metadata  # Python < 3.8
15 
16 import eta
17 import eta.core.config as etac
18 
19 import fiftyone.constants as foc
20 
21 
22 logger = logging.getLogger(__name__)
23 
24 
25 class Config(etac.Config):
26     """Base class for JSON serializable config classes."""
27 
28     def __repr__(self):
29         return self.__str__()
30 
31 
32 class Configurable(etac.Configurable):
33     """Base class for classes that can be initialized with a :class:`Config`
34     instance that configures their behavior.
35 
36     :class:`Configurable` subclasses must obey the following rules:
37 
38         (a) Configurable class ``Foo`` has an associated Config class
39             ``FooConfig`` that is importable from the same namespace as ``Foo``
40 
41         (b) Configurable class ``Foo`` must be initializable via the syntax
42             ``Foo(config)``, where config is a ``FooConfig`` instance
43 
44     Args:
45         config: a :class:`Config`
46     """
47 
48     def __init__(self, config):
49         self.validate(config)
50         self.config = config
51 
52 
53 class EnvConfig(etac.EnvConfig):
54     def __repr__(self):
55         return self.__str__()
56 
57 
58 class FiftyOneConfig(EnvConfig):
59     """FiftyOne configuration settings."""
60 
61     def __init__(self, d=None):
62         if d is None:
63             d = {}
64 
65         self.database_dir = self.parse_string(
66             d,
67             "database_dir",
68             env_var="FIFTYONE_DATABASE_DIR",
69             default=foc.DEFAULT_DB_DIR,
70         )
71         self.dataset_zoo_dir = self.parse_string(
72             d,
73             "dataset_zoo_dir",
74             env_var="FIFTYONE_DATASET_ZOO_DIR",
75             default=None,
76         )
77         self.model_zoo_dir = self.parse_string(
78             d, "model_zoo_dir", env_var="FIFTYONE_MODEL_ZOO_DIR", default=None,
79         )
80         self.dataset_zoo_manifest_paths = self.parse_string_array(
81             d,
82             "dataset_zoo_manifest_paths",
83             env_var="FIFTYONE_DATASET_ZOO_MANIFEST_PATHS",
84             default=None,
85         )
86         self.model_zoo_manifest_paths = self.parse_string_array(
87             d,
88             "model_zoo_manifest_paths",
89             env_var="FIFTYONE_MODEL_ZOO_MANIFEST_PATHS",
90             default=None,
91         )
92         self.default_dataset_dir = self.parse_string(
93             d,
94             "default_dataset_dir",
95             env_var="FIFTYONE_DEFAULT_DATASET_DIR",
96             default=None,
97         )
98         self.default_ml_backend = self.parse_string(
99             d,
100             "default_ml_backend",
101             env_var="FIFTYONE_DEFAULT_ML_BACKEND",
102             default=None,
103         )
104         self.default_batch_size = self.parse_int(
105             d,
106             "default_batch_size",
107             env_var="FIFTYONE_DEFAULT_BATCH_SIZE",
108             default=None,
109         )
110         self.default_sequence_idx = self.parse_string(
111             d,
112             "default_sequence_idx",
113             env_var="FIFTYONE_DEFAULT_SEQUENCE_IDX",
114             default="%06d",
115         )
116         self.default_image_ext = self.parse_string(
117             d,
118             "default_image_ext",
119             env_var="FIFTYONE_DEFAULT_IMAGE_EXT",
120             default=".jpg",
121         )
122         self.default_video_ext = self.parse_string(
123             d,
124             "default_video_ext",
125             env_var="FIFTYONE_DEFAULT_VIDEO_EXT",
126             default=".mp4",
127         )
128         self.default_app_port = self.parse_int(
129             d,
130             "default_app_port",
131             env_var="FIFTYONE_DEFAULT_APP_PORT",
132             default=5151,
133         )
134         self.desktop_app = self.parse_bool(
135             d, "desktop_app", env_var="FIFTYONE_DESKTOP_APP", default=False,
136         )
137         self._show_progress_bars = None  # declare
138         self.show_progress_bars = self.parse_bool(
139             d,
140             "show_progress_bars",
141             env_var="FIFTYONE_SHOW_PROGRESS_BARS",
142             default=True,
143         )
144         self.do_not_track = self.parse_bool(
145             d, "do_not_track", env_var="FIFTYONE_DO_NOT_TRACK", default=False,
146         )
147         self.requirement_error_level = self.parse_int(
148             d,
149             "requirement_error_level",
150             env_var="FIFTYONE_REQUIREMENT_ERROR_LEVEL",
151             default=0,
152         )
153 
154         self._set_defaults()
155         self._validate()
156 
157     @property
158     def show_progress_bars(self):
159         return self._show_progress_bars
160 
161     @show_progress_bars.setter
162     def show_progress_bars(self, value):
163         self._show_progress_bars = value
164         try:
165             # Keep ETA config in-sync
166             eta.config.show_progress_bars = value
167         except:
168             pass
169 
170     def attributes(self):
171         # Includes `show_progress_bars`
172         return super().custom_attributes(dynamic=True)
173 
174     def _set_defaults(self):
175         if self.default_dataset_dir is None:
176             self.default_dataset_dir = os.path.join(
177                 os.path.expanduser("~"), "fiftyone"
178             )
179 
180         if self.dataset_zoo_dir is None:
181             self.dataset_zoo_dir = self.default_dataset_dir
182 
183         if self.model_zoo_dir is None:
184             self.model_zoo_dir = os.path.join(
185                 self.default_dataset_dir, "__models__"
186             )
187 
188         if self.default_ml_backend is None:
189             installed_packages = _get_installed_packages()
190 
191             if "torch" in installed_packages:
192                 self.default_ml_backend = "torch"
193             elif "tensorflow" in installed_packages:
194                 self.default_ml_backend = "tensorflow"
195 
196     def _validate(self):
197         if self.default_ml_backend is not None:
198             self.default_ml_backend = self.default_ml_backend.lower()
199 
200 
201 class AppConfig(EnvConfig):
202     """FiftyOne App configuration settings."""
203 
204     def __init__(self, d=None):
205         if d is None:
206             d = {}
207 
208         self.color_pool = self.parse_string_array(
209             d,
210             "color_pool",
211             env_var="FIFTYONE_APP_COLOR_POOL",
212             default=foc.DEFAULT_APP_COLOR_POOL,
213         )
214         self.notebook_height = self.parse_int(
215             d,
216             "notebook_height",
217             env_var="FIFTYONE_APP_NOTEBOOK_HEIGHT",
218             default=800,
219         )
220         self.show_attributes = self.parse_bool(
221             d,
222             "show_attributes",
223             env_var="FIFTYONE_APP_SHOW_ATTRIBUTES",
224             default=False,
225         )
226         self.show_confidence = self.parse_bool(
227             d,
228             "show_confidence",
229             env_var="FIFTYONE_APP_SHOW_CONFIDENCE",
230             default=True,
231         )
232         self.show_tooltip = self.parse_bool(
233             d,
234             "show_tooltip",
235             env_var="FIFTYONE_APP_SHOW_TOOLTIP",
236             default=True,
237         )
238 
239 
240 def locate_config():
241     """Returns the path to the :class:`FiftyOneConfig` on disk.
242 
243     The default location is ``~/.fiftyone/config.json``, but you can override
244     this path by setting the ``FIFTYONE_CONFIG_PATH`` environment variable.
245 
246     Note that a config file may not actually exist on disk in the default
247     location, in which case the default config settings will be used.
248 
249     Returns:
250         the path to the :class:`FiftyOneConfig` on disk
251 
252     Raises:
253         OSError: if the config path has been customized but the file does not
254             exist on disk
255     """
256     if "FIFTYONE_CONFIG_PATH" not in os.environ:
257         return foc.FIFTYONE_CONFIG_PATH
258 
259     config_path = os.environ["FIFTYONE_CONFIG_PATH"]
260     if not os.path.isfile(config_path):
261         raise OSError("Config file '%s' not found" % config_path)
262 
263     return config_path
264 
265 
266 def locate_app_config():
267     """Returns the path to the :class:`AppConfig` on disk.
268 
269     The default location is ``~/.fiftyone/app_config.json``, but you can
270     override this path by setting the ``FIFTYONE_APP_CONFIG_PATH`` environment
271     variable.
272 
273     Note that a config file may not actually exist on disk in the default
274     location, in which case the default config settings will be used.
275 
276     Returns:
277         the path to the :class:`AppConfig` on disk
278 
279     Raises:
280         OSError: if the App config path has been customized but the file does
281             not exist on disk
282     """
283     if "FIFTYONE_APP_CONFIG_PATH" not in os.environ:
284         return foc.FIFTYONE_APP_CONFIG_PATH
285 
286     config_path = os.environ["FIFTYONE_APP_CONFIG_PATH"]
287     if not os.path.isfile(config_path):
288         raise OSError("App config file '%s' not found" % config_path)
289 
290     return config_path
291 
292 
293 def load_config():
294     """Loads the FiftyOne config.
295 
296     Returns:
297         a :class:`FiftyOneConfig` instance
298     """
299     config_path = locate_config()
300     if os.path.isfile(config_path):
301         return FiftyOneConfig.from_json(config_path)
302 
303     return FiftyOneConfig()
304 
305 
306 def load_app_config():
307     """Loads the FiftyOne App config.
308 
309     Returns:
310         an :class:`AppConfig` instance
311     """
312     app_config_path = locate_app_config()
313     if os.path.isfile(app_config_path):
314         return AppConfig.from_json(app_config_path)
315 
316     return AppConfig()
317 
318 
319 def set_config_settings(**kwargs):
320     """Sets the given FiftyOne config setting(s).
321 
322     Args:
323         **kwargs: keyword arguments defining valid :class:`FiftyOneConfig`
324             attributes and values
325 
326     Raises:
327         EnvConfigError: if the settings were invalid
328     """
329     import fiftyone as fo
330 
331     # Validiate settings
332     FiftyOneConfig.from_dict(kwargs)
333 
334     _set_settings(fo.config, kwargs)
335 
336 
337 def set_app_config_settings(**kwargs):
338     """Sets the given FiftyOne App config setting(s).
339 
340     Args:
341         **kwargs: keyword arguments defining valid :class:`AppConfig`
342             attributes and values
343 
344     Raises:
345         EnvConfigError: if the settings were invalid
346     """
347     import fiftyone as fo
348 
349     # Validiate settings
350     AppConfig.from_dict(kwargs)
351 
352     _set_settings(fo.app_config, kwargs)
353 
354 
355 def _set_settings(config, kwargs):
356     # Apply settings
357     for field, val in kwargs.items():
358         if not hasattr(config, field):
359             logger.warning("Skipping unknown config setting '%s'", field)
360             continue
361 
362         setattr(config, field, val)
363 
364 
365 def _get_installed_packages():
366     try:
367         return set(
368             d.metadata["Name"] for d in importlib_metadata.distributions()
369         )
370     except:
371         logger.debug("Failed to get installed packages")
372         return set()
373
```
--- 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/config.py b/fiftyone/core/config.py
--- a/fiftyone/core/config.py
+++ b/fiftyone/core/config.py
@@ -211,6 +211,12 @@
             env_var="FIFTYONE_APP_COLOR_POOL",
             default=foc.DEFAULT_APP_COLOR_POOL,
         )
+        self.default_grid_zoom = self.parse_int(
+            d,
+            "default_grid_zoom",
+            env_var="FIFTYONE_APP_GRID_ZOOM",
+            default=5,
+        )
         self.notebook_height = self.parse_int(
             d,
             "notebook_height",
@@ -236,6 +242,23 @@
             default=True,
         )
 
+        self._validate()
+
+    def _validate(self):
+        if self.default_grid_zoom < 0 or self.default_grid_zoom > 10:
+            raise AppConfigError(
+                "`default_grid_zoom` must be in [0, 10]; found %d"
+                % self.default_grid_zoom
+            )
+
+
+class AppConfigError(etac.EnvConfigError):
+    """Exception raised when an invalid :class:`AppConfig` instance is
+    encountered.
+    """
+
+    pass
+
 
 def locate_config():
     """Returns the path to the :class:`FiftyOneConfig` on disk.

{"golden_diff": "diff --git a/fiftyone/core/config.py b/fiftyone/core/config.py\n--- a/fiftyone/core/config.py\n+++ b/fiftyone/core/config.py\n@@ -211,6 +211,12 @@\n             env_var=\"FIFTYONE_APP_COLOR_POOL\",\n             default=foc.DEFAULT_APP_COLOR_POOL,\n         )\n+        self.default_grid_zoom = self.parse_int(\n+            d,\n+            \"default_grid_zoom\",\n+            env_var=\"FIFTYONE_APP_GRID_ZOOM\",\n+            default=5,\n+        )\n         self.notebook_height = self.parse_int(\n             d,\n             \"notebook_height\",\n@@ -236,6 +242,23 @@\n             default=True,\n         )\n \n+        self._validate()\n+\n+    def _validate(self):\n+        if self.default_grid_zoom < 0 or self.default_grid_zoom > 10:\n+            raise AppConfigError(\n+                \"`default_grid_zoom` must be in [0, 10]; found %d\"\n+                % self.default_grid_zoom\n+            )\n+\n+\n+class AppConfigError(etac.EnvConfigError):\n+    \"\"\"Exception raised when an invalid :class:`AppConfig` instance is\n+    encountered.\n+    \"\"\"\n+\n+    pass\n+\n \n def locate_config():\n     \"\"\"Returns the path to the :class:`FiftyOneConfig` on disk.\n", "issue": "[FR] Limit integer label filter sliders to integers\nHere's a dataset with some scalar values on its samples that have type `IntField`:\r\n\r\n```\r\nName:           quickstart\r\nMedia type      image\r\nNum samples:    200\r\nPersistent:     False\r\nInfo:           {}\r\nTags:           ['validation']\r\nSample fields:\r\n    media_type:   fiftyone.core.fields.StringField\r\n    filepath:     fiftyone.core.fields.StringField\r\n    tags:         fiftyone.core.fields.ListField(fiftyone.core.fields.StringField)\r\n    metadata:     fiftyone.core.fields.EmbeddedDocumentField(fiftyone.core.metadata.Metadata)\r\n    ground_truth: fiftyone.core.fields.EmbeddedDocumentField(fiftyone.core.labels.Detections)\r\n    uniqueness:   fiftyone.core.fields.FloatField\r\n    predictions:  fiftyone.core.fields.EmbeddedDocumentField(fiftyone.core.labels.Detections)\r\n    tp_iou_0_75:  fiftyone.core.fields.IntField\r\n    fp_iou_0_75:  fiftyone.core.fields.IntField\r\n    fn_iou_0_75:  fiftyone.core.fields.IntField\r\n```\r\n\r\nIt would be desirable if the label filter in the App respected the int data type and locked the slider into int values, rather than allowing/showing `[0.00    [34.92, 81.48]     97.00]` in the App\r\n\r\n<img width=\"267\" alt=\"Screen Shot 2020-10-07 at 2 01 12 PM\" src=\"https://user-images.githubusercontent.com/25985824/95369815-f0f2c480-08a5-11eb-9b16-29c59c7abd7b.png\">\r\n\n", "before_files": [{"content": "\"\"\"\nFiftyOne config.\n\n| Copyright 2017-2021, Voxel51, Inc.\n| `voxel51.com <https://voxel51.com/>`_\n|\n\"\"\"\nimport logging\nimport os\n\ntry:\n    from importlib import metadata as importlib_metadata  # Python 3.8\nexcept ImportError:\n    import importlib_metadata  # Python < 3.8\n\nimport eta\nimport eta.core.config as etac\n\nimport fiftyone.constants as foc\n\n\nlogger = logging.getLogger(__name__)\n\n\nclass Config(etac.Config):\n    \"\"\"Base class for JSON serializable config classes.\"\"\"\n\n    def __repr__(self):\n        return self.__str__()\n\n\nclass Configurable(etac.Configurable):\n    \"\"\"Base class for classes that can be initialized with a :class:`Config`\n    instance that configures their behavior.\n\n    :class:`Configurable` subclasses must obey the following rules:\n\n        (a) Configurable class ``Foo`` has an associated Config class\n            ``FooConfig`` that is importable from the same namespace as ``Foo``\n\n        (b) Configurable class ``Foo`` must be initializable via the syntax\n            ``Foo(config)``, where config is a ``FooConfig`` instance\n\n    Args:\n        config: a :class:`Config`\n    \"\"\"\n\n    def __init__(self, config):\n        self.validate(config)\n        self.config = config\n\n\nclass EnvConfig(etac.EnvConfig):\n    def __repr__(self):\n        return self.__str__()\n\n\nclass FiftyOneConfig(EnvConfig):\n    \"\"\"FiftyOne configuration settings.\"\"\"\n\n    def __init__(self, d=None):\n        if d is None:\n            d = {}\n\n        self.database_dir = self.parse_string(\n            d,\n            \"database_dir\",\n            env_var=\"FIFTYONE_DATABASE_DIR\",\n            default=foc.DEFAULT_DB_DIR,\n        )\n        self.dataset_zoo_dir = self.parse_string(\n            d,\n            \"dataset_zoo_dir\",\n            env_var=\"FIFTYONE_DATASET_ZOO_DIR\",\n            default=None,\n        )\n        self.model_zoo_dir = self.parse_string(\n            d, \"model_zoo_dir\", env_var=\"FIFTYONE_MODEL_ZOO_DIR\", default=None,\n        )\n        self.dataset_zoo_manifest_paths = self.parse_string_array(\n            d,\n            \"dataset_zoo_manifest_paths\",\n            env_var=\"FIFTYONE_DATASET_ZOO_MANIFEST_PATHS\",\n            default=None,\n        )\n        self.model_zoo_manifest_paths = self.parse_string_array(\n            d,\n            \"model_zoo_manifest_paths\",\n            env_var=\"FIFTYONE_MODEL_ZOO_MANIFEST_PATHS\",\n            default=None,\n        )\n        self.default_dataset_dir = self.parse_string(\n            d,\n            \"default_dataset_dir\",\n            env_var=\"FIFTYONE_DEFAULT_DATASET_DIR\",\n            default=None,\n        )\n        self.default_ml_backend = self.parse_string(\n            d,\n            \"default_ml_backend\",\n            env_var=\"FIFTYONE_DEFAULT_ML_BACKEND\",\n            default=None,\n        )\n        self.default_batch_size = self.parse_int(\n            d,\n            \"default_batch_size\",\n            env_var=\"FIFTYONE_DEFAULT_BATCH_SIZE\",\n            default=None,\n        )\n        self.default_sequence_idx = self.parse_string(\n            d,\n            \"default_sequence_idx\",\n            env_var=\"FIFTYONE_DEFAULT_SEQUENCE_IDX\",\n            default=\"%06d\",\n        )\n        self.default_image_ext = self.parse_string(\n            d,\n            \"default_image_ext\",\n            env_var=\"FIFTYONE_DEFAULT_IMAGE_EXT\",\n            default=\".jpg\",\n        )\n        self.default_video_ext = self.parse_string(\n            d,\n            \"default_video_ext\",\n            env_var=\"FIFTYONE_DEFAULT_VIDEO_EXT\",\n            default=\".mp4\",\n        )\n        self.default_app_port = self.parse_int(\n            d,\n            \"default_app_port\",\n            env_var=\"FIFTYONE_DEFAULT_APP_PORT\",\n            default=5151,\n        )\n        self.desktop_app = self.parse_bool(\n            d, \"desktop_app\", env_var=\"FIFTYONE_DESKTOP_APP\", default=False,\n        )\n        self._show_progress_bars = None  # declare\n        self.show_progress_bars = self.parse_bool(\n            d,\n            \"show_progress_bars\",\n            env_var=\"FIFTYONE_SHOW_PROGRESS_BARS\",\n            default=True,\n        )\n        self.do_not_track = self.parse_bool(\n            d, \"do_not_track\", env_var=\"FIFTYONE_DO_NOT_TRACK\", default=False,\n        )\n        self.requirement_error_level = self.parse_int(\n            d,\n            \"requirement_error_level\",\n            env_var=\"FIFTYONE_REQUIREMENT_ERROR_LEVEL\",\n            default=0,\n        )\n\n        self._set_defaults()\n        self._validate()\n\n    @property\n    def show_progress_bars(self):\n        return self._show_progress_bars\n\n    @show_progress_bars.setter\n    def show_progress_bars(self, value):\n        self._show_progress_bars = value\n        try:\n            # Keep ETA config in-sync\n            eta.config.show_progress_bars = value\n        except:\n            pass\n\n    def attributes(self):\n        # Includes `show_progress_bars`\n        return super().custom_attributes(dynamic=True)\n\n    def _set_defaults(self):\n        if self.default_dataset_dir is None:\n            self.default_dataset_dir = os.path.join(\n                os.path.expanduser(\"~\"), \"fiftyone\"\n            )\n\n        if self.dataset_zoo_dir is None:\n            self.dataset_zoo_dir = self.default_dataset_dir\n\n        if self.model_zoo_dir is None:\n            self.model_zoo_dir = os.path.join(\n                self.default_dataset_dir, \"__models__\"\n            )\n\n        if self.default_ml_backend is None:\n            installed_packages = _get_installed_packages()\n\n            if \"torch\" in installed_packages:\n                self.default_ml_backend = \"torch\"\n            elif \"tensorflow\" in installed_packages:\n                self.default_ml_backend = \"tensorflow\"\n\n    def _validate(self):\n        if self.default_ml_backend is not None:\n            self.default_ml_backend = self.default_ml_backend.lower()\n\n\nclass AppConfig(EnvConfig):\n    \"\"\"FiftyOne App configuration settings.\"\"\"\n\n    def __init__(self, d=None):\n        if d is None:\n            d = {}\n\n        self.color_pool = self.parse_string_array(\n            d,\n            \"color_pool\",\n            env_var=\"FIFTYONE_APP_COLOR_POOL\",\n            default=foc.DEFAULT_APP_COLOR_POOL,\n        )\n        self.notebook_height = self.parse_int(\n            d,\n            \"notebook_height\",\n            env_var=\"FIFTYONE_APP_NOTEBOOK_HEIGHT\",\n            default=800,\n        )\n        self.show_attributes = self.parse_bool(\n            d,\n            \"show_attributes\",\n            env_var=\"FIFTYONE_APP_SHOW_ATTRIBUTES\",\n            default=False,\n        )\n        self.show_confidence = self.parse_bool(\n            d,\n            \"show_confidence\",\n            env_var=\"FIFTYONE_APP_SHOW_CONFIDENCE\",\n            default=True,\n        )\n        self.show_tooltip = self.parse_bool(\n            d,\n            \"show_tooltip\",\n            env_var=\"FIFTYONE_APP_SHOW_TOOLTIP\",\n            default=True,\n        )\n\n\ndef locate_config():\n    \"\"\"Returns the path to the :class:`FiftyOneConfig` on disk.\n\n    The default location is ``~/.fiftyone/config.json``, but you can override\n    this path by setting the ``FIFTYONE_CONFIG_PATH`` environment variable.\n\n    Note that a config file may not actually exist on disk in the default\n    location, in which case the default config settings will be used.\n\n    Returns:\n        the path to the :class:`FiftyOneConfig` on disk\n\n    Raises:\n        OSError: if the config path has been customized but the file does not\n            exist on disk\n    \"\"\"\n    if \"FIFTYONE_CONFIG_PATH\" not in os.environ:\n        return foc.FIFTYONE_CONFIG_PATH\n\n    config_path = os.environ[\"FIFTYONE_CONFIG_PATH\"]\n    if not os.path.isfile(config_path):\n        raise OSError(\"Config file '%s' not found\" % config_path)\n\n    return config_path\n\n\ndef locate_app_config():\n    \"\"\"Returns the path to the :class:`AppConfig` on disk.\n\n    The default location is ``~/.fiftyone/app_config.json``, but you can\n    override this path by setting the ``FIFTYONE_APP_CONFIG_PATH`` environment\n    variable.\n\n    Note that a config file may not actually exist on disk in the default\n    location, in which case the default config settings will be used.\n\n    Returns:\n        the path to the :class:`AppConfig` on disk\n\n    Raises:\n        OSError: if the App config path has been customized but the file does\n            not exist on disk\n    \"\"\"\n    if \"FIFTYONE_APP_CONFIG_PATH\" not in os.environ:\n        return foc.FIFTYONE_APP_CONFIG_PATH\n\n    config_path = os.environ[\"FIFTYONE_APP_CONFIG_PATH\"]\n    if not os.path.isfile(config_path):\n        raise OSError(\"App config file '%s' not found\" % config_path)\n\n    return config_path\n\n\ndef load_config():\n    \"\"\"Loads the FiftyOne config.\n\n    Returns:\n        a :class:`FiftyOneConfig` instance\n    \"\"\"\n    config_path = locate_config()\n    if os.path.isfile(config_path):\n        return FiftyOneConfig.from_json(config_path)\n\n    return FiftyOneConfig()\n\n\ndef load_app_config():\n    \"\"\"Loads the FiftyOne App config.\n\n    Returns:\n        an :class:`AppConfig` instance\n    \"\"\"\n    app_config_path = locate_app_config()\n    if os.path.isfile(app_config_path):\n        return AppConfig.from_json(app_config_path)\n\n    return AppConfig()\n\n\ndef set_config_settings(**kwargs):\n    \"\"\"Sets the given FiftyOne config setting(s).\n\n    Args:\n        **kwargs: keyword arguments defining valid :class:`FiftyOneConfig`\n            attributes and values\n\n    Raises:\n        EnvConfigError: if the settings were invalid\n    \"\"\"\n    import fiftyone as fo\n\n    # Validiate settings\n    FiftyOneConfig.from_dict(kwargs)\n\n    _set_settings(fo.config, kwargs)\n\n\ndef set_app_config_settings(**kwargs):\n    \"\"\"Sets the given FiftyOne App config setting(s).\n\n    Args:\n        **kwargs: keyword arguments defining valid :class:`AppConfig`\n            attributes and values\n\n    Raises:\n        EnvConfigError: if the settings were invalid\n    \"\"\"\n    import fiftyone as fo\n\n    # Validiate settings\n    AppConfig.from_dict(kwargs)\n\n    _set_settings(fo.app_config, kwargs)\n\n\ndef _set_settings(config, kwargs):\n    # Apply settings\n    for field, val in kwargs.items():\n        if not hasattr(config, field):\n            logger.warning(\"Skipping unknown config setting '%s'\", field)\n            continue\n\n        setattr(config, field, val)\n\n\ndef _get_installed_packages():\n    try:\n        return set(\n            d.metadata[\"Name\"] for d in importlib_metadata.distributions()\n        )\n    except:\n        logger.debug(\"Failed to get installed packages\")\n        return set()\n", "path": "fiftyone/core/config.py"}], "after_files": [{"content": "\"\"\"\nFiftyOne config.\n\n| Copyright 2017-2021, Voxel51, Inc.\n| `voxel51.com <https://voxel51.com/>`_\n|\n\"\"\"\nimport logging\nimport os\n\ntry:\n    from importlib import metadata as importlib_metadata  # Python 3.8\nexcept ImportError:\n    import importlib_metadata  # Python < 3.8\n\nimport eta\nimport eta.core.config as etac\n\nimport fiftyone.constants as foc\n\n\nlogger = logging.getLogger(__name__)\n\n\nclass Config(etac.Config):\n    \"\"\"Base class for JSON serializable config classes.\"\"\"\n\n    def __repr__(self):\n        return self.__str__()\n\n\nclass Configurable(etac.Configurable):\n    \"\"\"Base class for classes that can be initialized with a :class:`Config`\n    instance that configures their behavior.\n\n    :class:`Configurable` subclasses must obey the following rules:\n\n        (a) Configurable class ``Foo`` has an associated Config class\n            ``FooConfig`` that is importable from the same namespace as ``Foo``\n\n        (b) Configurable class ``Foo`` must be initializable via the syntax\n            ``Foo(config)``, where config is a ``FooConfig`` instance\n\n    Args:\n        config: a :class:`Config`\n    \"\"\"\n\n    def __init__(self, config):\n        self.validate(config)\n        self.config = config\n\n\nclass EnvConfig(etac.EnvConfig):\n    def __repr__(self):\n        return self.__str__()\n\n\nclass FiftyOneConfig(EnvConfig):\n    \"\"\"FiftyOne configuration settings.\"\"\"\n\n    def __init__(self, d=None):\n        if d is None:\n            d = {}\n\n        self.database_dir = self.parse_string(\n            d,\n            \"database_dir\",\n            env_var=\"FIFTYONE_DATABASE_DIR\",\n            default=foc.DEFAULT_DB_DIR,\n        )\n        self.dataset_zoo_dir = self.parse_string(\n            d,\n            \"dataset_zoo_dir\",\n            env_var=\"FIFTYONE_DATASET_ZOO_DIR\",\n            default=None,\n        )\n        self.model_zoo_dir = self.parse_string(\n            d, \"model_zoo_dir\", env_var=\"FIFTYONE_MODEL_ZOO_DIR\", default=None,\n        )\n        self.dataset_zoo_manifest_paths = self.parse_string_array(\n            d,\n            \"dataset_zoo_manifest_paths\",\n            env_var=\"FIFTYONE_DATASET_ZOO_MANIFEST_PATHS\",\n            default=None,\n        )\n        self.model_zoo_manifest_paths = self.parse_string_array(\n            d,\n            \"model_zoo_manifest_paths\",\n            env_var=\"FIFTYONE_MODEL_ZOO_MANIFEST_PATHS\",\n            default=None,\n        )\n        self.default_dataset_dir = self.parse_string(\n            d,\n            \"default_dataset_dir\",\n            env_var=\"FIFTYONE_DEFAULT_DATASET_DIR\",\n            default=None,\n        )\n        self.default_ml_backend = self.parse_string(\n            d,\n            \"default_ml_backend\",\n            env_var=\"FIFTYONE_DEFAULT_ML_BACKEND\",\n            default=None,\n        )\n        self.default_batch_size = self.parse_int(\n            d,\n            \"default_batch_size\",\n            env_var=\"FIFTYONE_DEFAULT_BATCH_SIZE\",\n            default=None,\n        )\n        self.default_sequence_idx = self.parse_string(\n            d,\n            \"default_sequence_idx\",\n            env_var=\"FIFTYONE_DEFAULT_SEQUENCE_IDX\",\n            default=\"%06d\",\n        )\n        self.default_image_ext = self.parse_string(\n            d,\n            \"default_image_ext\",\n            env_var=\"FIFTYONE_DEFAULT_IMAGE_EXT\",\n            default=\".jpg\",\n        )\n        self.default_video_ext = self.parse_string(\n            d,\n            \"default_video_ext\",\n            env_var=\"FIFTYONE_DEFAULT_VIDEO_EXT\",\n            default=\".mp4\",\n        )\n        self.default_app_port = self.parse_int(\n            d,\n            \"default_app_port\",\n            env_var=\"FIFTYONE_DEFAULT_APP_PORT\",\n            default=5151,\n        )\n        self.desktop_app = self.parse_bool(\n            d, \"desktop_app\", env_var=\"FIFTYONE_DESKTOP_APP\", default=False,\n        )\n        self._show_progress_bars = None  # declare\n        self.show_progress_bars = self.parse_bool(\n            d,\n            \"show_progress_bars\",\n            env_var=\"FIFTYONE_SHOW_PROGRESS_BARS\",\n            default=True,\n        )\n        self.do_not_track = self.parse_bool(\n            d, \"do_not_track\", env_var=\"FIFTYONE_DO_NOT_TRACK\", default=False,\n        )\n        self.requirement_error_level = self.parse_int(\n            d,\n            \"requirement_error_level\",\n            env_var=\"FIFTYONE_REQUIREMENT_ERROR_LEVEL\",\n            default=0,\n        )\n\n        self._set_defaults()\n        self._validate()\n\n    @property\n    def show_progress_bars(self):\n        return self._show_progress_bars\n\n    @show_progress_bars.setter\n    def show_progress_bars(self, value):\n        self._show_progress_bars = value\n        try:\n            # Keep ETA config in-sync\n            eta.config.show_progress_bars = value\n        except:\n            pass\n\n    def attributes(self):\n        # Includes `show_progress_bars`\n        return super().custom_attributes(dynamic=True)\n\n    def _set_defaults(self):\n        if self.default_dataset_dir is None:\n            self.default_dataset_dir = os.path.join(\n                os.path.expanduser(\"~\"), \"fiftyone\"\n            )\n\n        if self.dataset_zoo_dir is None:\n            self.dataset_zoo_dir = self.default_dataset_dir\n\n        if self.model_zoo_dir is None:\n            self.model_zoo_dir = os.path.join(\n                self.default_dataset_dir, \"__models__\"\n            )\n\n        if self.default_ml_backend is None:\n            installed_packages = _get_installed_packages()\n\n            if \"torch\" in installed_packages:\n                self.default_ml_backend = \"torch\"\n            elif \"tensorflow\" in installed_packages:\n                self.default_ml_backend = \"tensorflow\"\n\n    def _validate(self):\n        if self.default_ml_backend is not None:\n            self.default_ml_backend = self.default_ml_backend.lower()\n\n\nclass AppConfig(EnvConfig):\n    \"\"\"FiftyOne App configuration settings.\"\"\"\n\n    def __init__(self, d=None):\n        if d is None:\n            d = {}\n\n        self.color_pool = self.parse_string_array(\n            d,\n            \"color_pool\",\n            env_var=\"FIFTYONE_APP_COLOR_POOL\",\n            default=foc.DEFAULT_APP_COLOR_POOL,\n        )\n        self.default_grid_zoom = self.parse_int(\n            d,\n            \"default_grid_zoom\",\n            env_var=\"FIFTYONE_APP_GRID_ZOOM\",\n            default=5,\n        )\n        self.notebook_height = self.parse_int(\n            d,\n            \"notebook_height\",\n            env_var=\"FIFTYONE_APP_NOTEBOOK_HEIGHT\",\n            default=800,\n        )\n        self.show_attributes = self.parse_bool(\n            d,\n            \"show_attributes\",\n            env_var=\"FIFTYONE_APP_SHOW_ATTRIBUTES\",\n            default=False,\n        )\n        self.show_confidence = self.parse_bool(\n            d,\n            \"show_confidence\",\n            env_var=\"FIFTYONE_APP_SHOW_CONFIDENCE\",\n            default=True,\n        )\n        self.show_tooltip = self.parse_bool(\n            d,\n            \"show_tooltip\",\n            env_var=\"FIFTYONE_APP_SHOW_TOOLTIP\",\n            default=True,\n        )\n\n        self._validate()\n\n    def _validate(self):\n        if self.default_grid_zoom < 0 or self.default_grid_zoom > 10:\n            raise AppConfigError(\n                \"`default_grid_zoom` must be in [0, 10]; found %d\"\n                % self.default_grid_zoom\n            )\n\n\nclass AppConfigError(etac.EnvConfigError):\n    \"\"\"Exception raised when an invalid :class:`AppConfig` instance is\n    encountered.\n    \"\"\"\n\n    pass\n\n\ndef locate_config():\n    \"\"\"Returns the path to the :class:`FiftyOneConfig` on disk.\n\n    The default location is ``~/.fiftyone/config.json``, but you can override\n    this path by setting the ``FIFTYONE_CONFIG_PATH`` environment variable.\n\n    Note that a config file may not actually exist on disk in the default\n    location, in which case the default config settings will be used.\n\n    Returns:\n        the path to the :class:`FiftyOneConfig` on disk\n\n    Raises:\n        OSError: if the config path has been customized but the file does not\n            exist on disk\n    \"\"\"\n    if \"FIFTYONE_CONFIG_PATH\" not in os.environ:\n        return foc.FIFTYONE_CONFIG_PATH\n\n    config_path = os.environ[\"FIFTYONE_CONFIG_PATH\"]\n    if not os.path.isfile(config_path):\n        raise OSError(\"Config file '%s' not found\" % config_path)\n\n    return config_path\n\n\ndef locate_app_config():\n    \"\"\"Returns the path to the :class:`AppConfig` on disk.\n\n    The default location is ``~/.fiftyone/app_config.json``, but you can\n    override this path by setting the ``FIFTYONE_APP_CONFIG_PATH`` environment\n    variable.\n\n    Note that a config file may not actually exist on disk in the default\n    location, in which case the default config settings will be used.\n\n    Returns:\n        the path to the :class:`AppConfig` on disk\n\n    Raises:\n        OSError: if the App config path has been customized but the file does\n            not exist on disk\n    \"\"\"\n    if \"FIFTYONE_APP_CONFIG_PATH\" not in os.environ:\n        return foc.FIFTYONE_APP_CONFIG_PATH\n\n    config_path = os.environ[\"FIFTYONE_APP_CONFIG_PATH\"]\n    if not os.path.isfile(config_path):\n        raise OSError(\"App config file '%s' not found\" % config_path)\n\n    return config_path\n\n\ndef load_config():\n    \"\"\"Loads the FiftyOne config.\n\n    Returns:\n        a :class:`FiftyOneConfig` instance\n    \"\"\"\n    config_path = locate_config()\n    if os.path.isfile(config_path):\n        return FiftyOneConfig.from_json(config_path)\n\n    return FiftyOneConfig()\n\n\ndef load_app_config():\n    \"\"\"Loads the FiftyOne App config.\n\n    Returns:\n        an :class:`AppConfig` instance\n    \"\"\"\n    app_config_path = locate_app_config()\n    if os.path.isfile(app_config_path):\n        return AppConfig.from_json(app_config_path)\n\n    return AppConfig()\n\n\ndef set_config_settings(**kwargs):\n    \"\"\"Sets the given FiftyOne config setting(s).\n\n    Args:\n        **kwargs: keyword arguments defining valid :class:`FiftyOneConfig`\n            attributes and values\n\n    Raises:\n        EnvConfigError: if the settings were invalid\n    \"\"\"\n    import fiftyone as fo\n\n    # Validiate settings\n    FiftyOneConfig.from_dict(kwargs)\n\n    _set_settings(fo.config, kwargs)\n\n\ndef set_app_config_settings(**kwargs):\n    \"\"\"Sets the given FiftyOne App config setting(s).\n\n    Args:\n        **kwargs: keyword arguments defining valid :class:`AppConfig`\n            attributes and values\n\n    Raises:\n        EnvConfigError: if the settings were invalid\n    \"\"\"\n    import fiftyone as fo\n\n    # Validiate settings\n    AppConfig.from_dict(kwargs)\n\n    _set_settings(fo.app_config, kwargs)\n\n\ndef _set_settings(config, kwargs):\n    # Apply settings\n    for field, val in kwargs.items():\n        if not hasattr(config, field):\n            logger.warning(\"Skipping unknown config setting '%s'\", field)\n            continue\n\n        setattr(config, field, val)\n\n\ndef _get_installed_packages():\n    try:\n        return set(\n            d.metadata[\"Name\"] for d in importlib_metadata.distributions()\n        )\n    except:\n        logger.debug(\"Failed to get installed packages\")\n        return set()\n", "path": "fiftyone/core/config.py"}]}