Repository: plotly/dash

Issue:
Split out Dash CI only requirements into `requires-ci.txt`?
Both `pip install dash[dev]` and `pip install dash[testing]` are extra public Dash features (respectively for building Dash components, and using the shipped Dash app test utilities).

Looking at [`requires-dev.txt`](https://github.com/plotly/dash/blob/dev/requires-dev.txt) and [`requires-testing.txt`](https://github.com/plotly/dash/blob/dev/requires-testing.txt), there appears to have leaked https://github.com/plotly/dash CI/test suite dependencies into both files (i.e. dependencies not used by the features in `dash[dev]` and `dash[testing]`). Latest one introduced is [`flaky`](https://github.com/plotly/dash/pull/1741).

Split out Dash CI only dependencies to a new file?


Discussion:
Thanks @anders-kiaer - I was just thinking about exactly this last night and you're exactly right. I think internally we haven't been very clear about what these two extras mean but you're correct as to their original intent.

Resolving patch:
diff --git a/dash/long_callback/managers/celery_manager.py b/dash/long_callback/managers/celery_manager.py
--- a/dash/long_callback/managers/celery_manager.py
+++ b/dash/long_callback/managers/celery_manager.py
@@ -25,10 +25,18 @@ def __init__(self, celery_app, cache_by=None, expire=None):
             for ``expire`` seconds.  If not provided, the lifetime of cache entries
             is determined by the default behavior of the celery result backend.
         """
-        import celery  # pylint: disable=import-outside-toplevel,import-error
-        from celery.backends.base import (  # pylint: disable=import-outside-toplevel,import-error
-            DisabledBackend,
-        )
+        try:
+            import celery  # pylint: disable=import-outside-toplevel,import-error
+            from celery.backends.base import (  # pylint: disable=import-outside-toplevel,import-error
+                DisabledBackend,
+            )
+        except ImportError as missing_imports:
+            raise ImportError(
+                """\
+CeleryLongCallbackManager requires extra dependencies which can be installed doing
+
+    $ pip install "dash[celery]"\n"""
+            ) from missing_imports
 
         if not isinstance(celery_app, celery.Celery):
             raise ValueError("First argument must be a celery.Celery object")
diff --git a/dash/long_callback/managers/diskcache_manager.py b/dash/long_callback/managers/diskcache_manager.py
--- a/dash/long_callback/managers/diskcache_manager.py
+++ b/dash/long_callback/managers/diskcache_manager.py
@@ -4,14 +4,15 @@
 
 
 class DiskcacheLongCallbackManager(BaseLongCallbackManager):
-    def __init__(self, cache, cache_by=None, expire=None):
+    def __init__(self, cache=None, cache_by=None, expire=None):
         """
         Long callback manager that runs callback logic in a subprocess and stores
         results on disk using diskcache
 
         :param cache:
             A diskcache.Cache or diskcache.FanoutCache instance. See the diskcache
-            documentation for information on configuration options.
+            documentation for information on configuration options. If not provided,
+            a diskcache.Cache instance will be created with default values.
         :param cache_by:
             A list of zero-argument functions.  When provided, caching is enabled and
             the return values of these functions are combined with the callback
@@ -28,20 +29,22 @@ def __init__(self, cache, cache_by=None, expire=None):
         except ImportError as missing_imports:
             raise ImportError(
                 """\
-DiskcacheLongCallbackManager requires the multiprocess, diskcache, and psutil packages
-which can be installed using pip...
+DiskcacheLongCallbackManager requires extra dependencies which can be installed doing
 
-    $ pip install multiprocess diskcache psutil
-
-or conda.
-
-    $ conda install -c conda-forge multiprocess diskcache psutil\n"""
+    $ pip install "dash[diskcache]"\n"""
             ) from missing_imports
 
-        if not isinstance(cache, (diskcache.Cache, diskcache.FanoutCache)):
-            raise ValueError("First argument must be a diskcache.Cache object")
+        if cache is None:
+            self.handle = diskcache.Cache()
+        else:
+            if not isinstance(cache, (diskcache.Cache, diskcache.FanoutCache)):
+                raise ValueError(
+                    "First argument must be a diskcache.Cache "
+                    "or diskcache.FanoutCache object"
+                )
+            self.handle = cache
+
         super().__init__(cache_by)
-        self.handle = cache
         self.expire = expire
 
     def terminate_job(self, job):
diff --git a/setup.py b/setup.py
--- a/setup.py
+++ b/setup.py
@@ -28,8 +28,11 @@ def read_req_file(req_type):
     install_requires=read_req_file("install"),
     python_requires=">=3.6",
     extras_require={
+        "ci": read_req_file("ci"),
         "dev": read_req_file("dev"),
         "testing": read_req_file("testing"),
+        "celery": read_req_file("celery"),
+        "diskcache": read_req_file("diskcache"),
     },
     entry_points={
         "console_scripts": [


Test patch:
diff --git a/dash/testing/plugin.py b/dash/testing/plugin.py
--- a/dash/testing/plugin.py
+++ b/dash/testing/plugin.py
@@ -192,7 +192,5 @@ def diskcache_manager():
     from dash.long_callback import (  # pylint: disable=import-outside-toplevel
         DiskcacheLongCallbackManager,
     )
-    import diskcache  # pylint: disable=import-outside-toplevel
 
-    cache = diskcache.Cache()
-    return DiskcacheLongCallbackManager(cache)
+    return DiskcacheLongCallbackManager()