Repository: jupyter/jupyter_core

Issue:
Please consider using appdirs
[appdirs](https://pypi.org/project/appdirs/) takes care of OS dependent directories.  Using it would be a small change to [this file](https://github.com/jupyter/jupyter_core/blob/2dc34f70806b6668f965bfaa39623143cb2cda59/jupyter_core/paths.py#L56).

Discussion:
blink1073: I don't think we can make this change, I'm running into https://github.com/ActiveState/appdirs/issues/167.


```
  File "C:\hostedtoolcache\windows\Python\3.10.7\x64\lib\site-packages\appdirs.py", line 134, in site_data_dir

    path = os.path.normpath(_get_win_folder("CSIDL_COMMON_APPDATA"))

  File "C:\hostedtoolcache\windows\Python\3.10.7\x64\lib\site-packages\appdirs.py", line 481, in _get_win_folder_with_pywin32

    dir = shell.SHGetFolderPath(0, getattr(shellcon, csidl_name), 0, 0)

pywintypes.com_error: (-2147024893, 'The system cannot find the path specified.', None, None)
```

blink1073: Looks like `platformdirs` is a drop-in replacement and is maintained.
jasongrout: > Looks like `platformdirs` is a drop-in replacement and is maintained.

And maintained by Bernat, at that. 👍 
jasongrout: How should we manage the transition? I can imagine this will be a big pain to people that have configs in our hardcoded places. At the very least, it seems like we should have a deprecation period for the old directories. Probably even better is to have an opt-in to the new behavior on this next major version (with deprecation of old directories), and an opt-out in the major version after that, then final removal of old directories in the major version after that?

blink1073: > Probably even better is to have an opt-in to the new behavior on this next major version (with deprecation of old directories), and an opt-out in the major version after that, then final removal of old directories in the major version after that?

Yeah, that sounds reasonable.
jasongrout: To elaborate more, here's a proposal: we have a `JUPYTER_PLATFORM_DIRS` environment variable (or a better name?)? If JUPYTER_PLATFORM_DIRS is truthy, we apply this new logic, and if it is falsey we apply the old logic.

- In version 5, if JUPYTER_PLATFORM_DIRS is not defined, we assume it is falsey. We also introduce a deprecation warning when using the old logic. (However, there is a question about how we might make sure users get the deprecation warning. We might use logic similar to what we introduced in ipywidgets to bubble the deprecation from https://github.com/jupyter-widgets/ipywidgets/pull/3569)
- In version 6, we flip the default so that if JUPYTER_PLATFORM_DIRS is not defined, we assume it is truthy
- In version 7, we remove the environment variable checks and old logic.

jasongrout: @blink1073 - I pushed a commit consolidating the environment checking to a single function that also uses our envset utility function.
jasongrout: When you want to pause for a bit, there are a few more cleanups of the test file I'd like to do (convenience decorators and context managers for the platforms and platformdirs patch, etc.).
blink1073: > When you want to pause for a bit, there are a few more cleanups of the test file I'd like to do (convenience decorators and context managers for the platforms and platformdirs patch, etc.).

I'm done with my changes
jasongrout: @blink1073 - this all looks good to me now. If it looks good to you, I think we can merge. Thanks!

Patch:
diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
index a7a75c1..8723446 100644
--- a/.pre-commit-config.yaml
+++ b/.pre-commit-config.yaml
@@ -60,7 +60,7 @@ repos:
       - id: mypy
         args: ["--config-file", "pyproject.toml"]
         stages: [manual]
-        additional_dependencies: [pytest]
+        additional_dependencies: [pytest, platformdirs]
         exclude: |
           exclude: |
             (?x)^(
diff --git a/jupyter_core/command.py b/jupyter_core/command.py
index 33804c9..3a954b7 100644
--- a/jupyter_core/command.py
+++ b/jupyter_core/command.py
@@ -231,6 +231,15 @@ def main():
                 if args.debug:
                     env = os.environ
 
+                    if paths.use_platform_dirs():
+                        print(
+                            "JUPYTER_PLATFORM_DIRS is set to a true value, so we use platformdirs to find platform-specific directories"
+                        )
+                    else:
+                        print(
+                            "JUPYTER_PLATFORM_DIRS is set to a false value, or is not set, so we use hardcoded legacy paths for platform-specific directories"
+                        )
+
                     if paths.prefer_environment_over_user():
                         print(
                             "JUPYTER_PREFER_ENV_PATH is set to a true value, or JUPYTER_PREFER_ENV_PATH is not set and we detected a virtual environment, making the environment-level path preferred over the user-level path for data and config"
diff --git a/jupyter_core/paths.py b/jupyter_core/paths.py
index f9d4625..5769034 100644
--- a/jupyter_core/paths.py
+++ b/jupyter_core/paths.py
@@ -19,8 +19,15 @@ from contextlib import contextmanager
 from pathlib import Path
 from typing import Optional
 
+import platformdirs
+
+from jupyter_core.utils import deprecation
+
 pjoin = os.path.join
 
+# Capitalize Jupyter in paths only on Windows and MacOS
+APPNAME = "Jupyter" if sys.platform in ("win32", "darwin") else "jupyter"
+
 # UF_HIDDEN is a stat flag not defined in the stat module.
 # It is used by BSD to indicate hidden files.
 UF_HIDDEN = getattr(stat, "UF_HIDDEN", 32768)
@@ -40,6 +47,15 @@ def envset(name, default=False):
     return os.environ[name].lower() not in ["no", "n", "false", "off", "0", "0.0"]
 
 
+def use_platform_dirs():
+    """Determine if platformdirs should be used for system-specific paths.
+
+    We plan for this to default to False in jupyter_core version 5 and to True
+    in jupyter_core version 6.
+    """
+    return envset("JUPYTER_PLATFORM_DIRS", False)
+
+
 def get_home_dir():
     """Get the real path of the home directory"""
     homedir = os.path.expanduser("~")
@@ -85,7 +101,8 @@ def _mkdtemp_once(name):
 def jupyter_config_dir():
     """Get the Jupyter config directory for this platform and user.
 
-    Returns JUPYTER_CONFIG_DIR if defined, else ~/.jupyter
+    Returns JUPYTER_CONFIG_DIR if defined, otherwise the appropriate
+    directory for the platform.
     """
 
     env = os.environ
@@ -95,6 +112,9 @@ def jupyter_config_dir():
     if env.get("JUPYTER_CONFIG_DIR"):
         return env["JUPYTER_CONFIG_DIR"]
 
+    if use_platform_dirs():
+        return platformdirs.user_config_dir(APPNAME, appauthor=False)
+
     home_dir = get_home_dir()
     return pjoin(home_dir, ".jupyter")
 
@@ -111,6 +131,9 @@ def jupyter_data_dir():
     if env.get("JUPYTER_DATA_DIR"):
         return env["JUPYTER_DATA_DIR"]
 
+    if use_platform_dirs():
+        return platformdirs.user_data_dir(APPNAME, appauthor=False)
+
     home = get_home_dir()
 
     if sys.platform == "darwin":
@@ -145,17 +168,29 @@ def jupyter_runtime_dir():
     return pjoin(jupyter_data_dir(), "runtime")
 
 
-if os.name == "nt":
-    programdata = os.environ.get("PROGRAMDATA", None)
-    if programdata:
-        SYSTEM_JUPYTER_PATH = [pjoin(programdata, "jupyter")]
-    else:  # PROGRAMDATA is not defined by default on XP.
-        SYSTEM_JUPYTER_PATH = [os.path.join(sys.prefix, "share", "jupyter")]
+if use_platform_dirs():
+    SYSTEM_JUPYTER_PATH = platformdirs.site_data_dir(
+        APPNAME, appauthor=False, multipath=True
+    ).split(os.pathsep)
 else:
-    SYSTEM_JUPYTER_PATH = [
-        "/usr/local/share/jupyter",
-        "/usr/share/jupyter",
-    ]
+    deprecation(
+        "Jupyter is migrating its paths to use standard platformdirs\n"  # noqa
+        + "given by the platformdirs library.  To remove this warning and\n"
+        + "see the appropriate new directories, set the environment variable\n"
+        + "`JUPYTER_PLATFORM_DIRS=1` and then run `jupyter --paths`.\n"
+        + "The use of platformdirs will be the default in `jupyter_core` v6"
+    )
+    if os.name == "nt":
+        programdata = os.environ.get("PROGRAMDATA", None)
+        if programdata:
+            SYSTEM_JUPYTER_PATH = [pjoin(programdata, "jupyter")]
+        else:  # PROGRAMDATA is not defined by default on XP.
+            SYSTEM_JUPYTER_PATH = [os.path.join(sys.prefix, "share", "jupyter")]
+    else:
+        SYSTEM_JUPYTER_PATH = [
+            "/usr/local/share/jupyter",
+            "/usr/share/jupyter",
+        ]
 
 ENV_JUPYTER_PATH = [os.path.join(sys.prefix, "share", "jupyter")]
 
@@ -222,18 +257,22 @@ def jupyter_path(*subdirs):
     return paths
 
 
-if os.name == "nt":
-    programdata = os.environ.get("PROGRAMDATA", None)
-    if programdata:
-        SYSTEM_CONFIG_PATH = [os.path.join(programdata, "jupyter")]
-    else:  # PROGRAMDATA is not defined by default on XP.
-        SYSTEM_CONFIG_PATH = []
+if use_platform_dirs():
+    SYSTEM_CONFIG_PATH = platformdirs.site_config_dir(
+        APPNAME, appauthor=False, multipath=True
+    ).split(os.pathsep)
 else:
-    SYSTEM_CONFIG_PATH = [
-        "/usr/local/etc/jupyter",
-        "/etc/jupyter",
-    ]
-
+    if os.name == "nt":
+        programdata = os.environ.get("PROGRAMDATA", None)
+        if programdata:
+            SYSTEM_CONFIG_PATH = [os.path.join(programdata, "jupyter")]
+        else:  # PROGRAMDATA is not defined by default on XP.
+            SYSTEM_CONFIG_PATH = []
+    else:
+        SYSTEM_CONFIG_PATH = [
+            "/usr/local/etc/jupyter",
+            "/etc/jupyter",
+        ]
 ENV_CONFIG_PATH = [os.path.join(sys.prefix, "etc", "jupyter")]
 
 
diff --git a/jupyter_core/utils/__init__.py b/jupyter_core/utils/__init__.py
index ff33137..fe844bd 100644
--- a/jupyter_core/utils/__init__.py
+++ b/jupyter_core/utils/__init__.py
@@ -1,5 +1,12 @@
+# Copyright (c) Jupyter Development Team.
+# Distributed under the terms of the Modified BSD License.
+
 import errno
+import inspect
 import os
+import sys
+import warnings
+from pathlib import Path
 
 
 def ensure_dir_exists(path, mode=0o777):
@@ -15,3 +22,62 @@ def ensure_dir_exists(path, mode=0o777):
             raise
     if not os.path.isdir(path):
         raise OSError("%r exists but is not a directory" % path)
+
+
+def _get_frame(level):
+    """Get the frame at the given stack level."""
+    # sys._getframe is much faster than inspect.stack, but isn't guaranteed to
+    # exist in all python implementations, so we fall back to inspect.stack()
+
+    # We need to add one to level to account for this get_frame call.
+    if hasattr(sys, "_getframe"):
+        frame = sys._getframe(level + 1)
+    else:
+        frame = inspect.stack(context=0)[level + 1].frame
+    return frame
+
+
+# This function is from https://github.com/python/cpython/issues/67998
+# (https://bugs.python.org/file39550/deprecated_module_stacklevel.diff) and
+# calculates the appropriate stacklevel for deprecations to target the
+# deprecation for the caller, no matter how many internal stack frames we have
+# added in the process. For example, with the deprecation warning in the
+# __init__ below, the appropriate stacklevel will change depending on how deep
+# the inheritance hierarchy is.
+def _external_stacklevel(internal):
+    """Find the stacklevel of the first frame that doesn't contain any of the given internal strings
+
+    The depth will be 1 at minimum in order to start checking at the caller of
+    the function that called this utility method.
+    """
+    # Get the level of my caller's caller
+    level = 2
+    frame = _get_frame(level)
+
+    # Normalize the path separators:
+    normalized_internal = [str(Path(s)) for s in internal]
+
+    # climb the stack frames while we see internal frames
+    while frame and any(s in str(Path(frame.f_code.co_filename)) for s in normalized_internal):
+        level += 1
+        frame = frame.f_back
+
+    # Return the stack level from the perspective of whoever called us (i.e., one level up)
+    return level - 1
+
+
+def deprecation(message, internal="jupyter_core/"):
+    """Generate a deprecation warning targeting the first frame that is not 'internal'
+
+    internal is a string or list of strings, which if they appear in filenames in the
+    frames, the frames will be considered internal. Changing this can be useful if, for examnple,
+    we know that our internal code is calling out to another library.
+    """
+    if isinstance(internal, str):
+        internal = [internal]
+
+    # stack level of the first external frame from here
+    stacklevel = _external_stacklevel(internal)
+
+    # The call to .warn adds one frame, so bump the stacklevel up by one
+    warnings.warn(message, DeprecationWarning, stacklevel=stacklevel + 1)
diff --git a/pyproject.toml b/pyproject.toml
index 2c3a2a1..da77339 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -17,6 +17,7 @@ classifiers = [
 ]
 requires-python = ">=3.7"
 dependencies = [
+  "platformdirs",
   "traitlets",
   "pywin32>=1.0 ; sys_platform == 'win32' and platform_python_implementation != 'PyPy'"
 ]
@@ -92,4 +93,6 @@ testpaths = [
 filterwarnings= [
   # Fail on warnings
   "error",
+  # Expected internal warnings
+  "module:Jupyter is migrating its paths to use standard platformdirs:DeprecationWarning",
 ]


Test patch:
diff --git a/jupyter_core/tests/test_paths.py b/jupyter_core/tests/test_paths.py
index 7e0fa37..793db0d 100644
--- a/jupyter_core/tests/test_paths.py
+++ b/jupyter_core/tests/test_paths.py
@@ -29,10 +29,11 @@ from jupyter_core.paths import (
     secure_write,
 )
 
-from .mocking import darwin, linux
-
 pjoin = os.path.join
 
+macos = pytest.mark.skipif(sys.platform != "darwin", reason="only run on macos")
+windows = pytest.mark.skipif(sys.platform != "win32", reason="only run on windows")
+linux = pytest.mark.skipif(sys.platform != "linux", reason="only run on linux")
 
 xdg_env = {
     "XDG_CONFIG_HOME": "/tmp/xdg/config",
@@ -42,16 +43,13 @@ xdg_env = {
 xdg = patch.dict("os.environ", xdg_env)
 no_xdg = patch.dict(
     "os.environ",
-    {
-        "XDG_CONFIG_HOME": "",
-        "XDG_DATA_HOME": "",
-        "XDG_RUNTIME_DIR": "",
-    },
+    {},
 )
 
 no_config_env = patch.dict(
     "os.environ",
     {
+        "JUPYTER_PLATFORM_DIRS": "",
         "JUPYTER_CONFIG_DIR": "",
         "JUPYTER_DATA_DIR": "",
         "JUPYTER_RUNTIME_DIR": "",
@@ -59,6 +57,8 @@ no_config_env = patch.dict(
     },
 )
 
+use_platformdirs = patch.dict("os.environ", {"JUPYTER_PLATFORM_DIRS": "1"})
+
 jupyter_config_env = "/jupyter-cfg"
 config_env = patch.dict("os.environ", {"JUPYTER_CONFIG_DIR": jupyter_config_env})
 prefer_env = patch.dict("os.environ", {"JUPYTER_PREFER_ENV_PATH": "True"})
@@ -99,159 +99,211 @@ def test_envset():
         assert paths.envset("THIS_VARIABLE_SHOULD_NOT_BE_SET", None) is None
 
 
-@pytest.mark.skipif(sys.platform == "win32", reason="does not run on windows")
-def test_config_dir_darwin():
-    with darwin, no_config_env:
-        config = jupyter_config_dir()
+def test_config_dir():
+    config = jupyter_config_dir()
     assert config == home_jupyter
 
-    with darwin, config_env:
-        config = jupyter_config_dir()
-    assert config == jupyter_config_env
+
+@macos
+@use_platformdirs
+def test_config_dir_darwin():
+    config = jupyter_config_dir()
+    assert config == realpath("~/Library/Preferences/Jupyter")
 
 
-@pytest.mark.skipif(sys.platform != "win32", reason="only run on windows")
+@windows
+@use_platformdirs
 def test_config_dir_windows():
-    with no_config_env:
-        config = jupyter_config_dir()
-    assert config == home_jupyter
+    config = jupyter_config_dir()
+    assert config == realpath(pjoin(os.environ.get("LOCALAPPDATA", ""), "Jupyter"))
+
+
+@linux
+@use_platformdirs
+def test_config_dir_linux():
+    config = jupyter_config_dir()
+    assert config == realpath("~/.config/jupyter")
 
+
+def test_config_env_legacy():
     with config_env:
         config = jupyter_config_dir()
-    assert config == jupyter_config_env
+        assert config == jupyter_config_env
 
 
-@pytest.mark.skipif(sys.platform == "win32", reason="does not run on windows")
-def test_config_dir_linux():
-    with linux, no_config_env:
+@use_platformdirs
+def test_config_env():
+    with config_env:
         config = jupyter_config_dir()
-    assert config == home_jupyter
+        assert config == jupyter_config_env
 
-    with linux, config_env:
-        config = jupyter_config_dir()
-    assert config == jupyter_config_env
+
+def test_data_dir_env_legacy():
+    data_env = "runtime-dir"
+    with patch.dict("os.environ", {"JUPYTER_DATA_DIR": data_env}):
+        data = jupyter_data_dir()
+        assert data == data_env
 
 
+@use_platformdirs
 def test_data_dir_env():
     data_env = "runtime-dir"
     with patch.dict("os.environ", {"JUPYTER_DATA_DIR": data_env}):
         data = jupyter_data_dir()
-    assert data == data_env
+        assert data == data_env
 
 
-@pytest.mark.skipif(sys.platform == "win32", reason="does not run on windows")
-def test_data_dir_darwin():
-    with darwin:
-        data = jupyter_data_dir()
+@macos
+def test_data_dir_darwin_legacy():
+    data = jupyter_data_dir()
     assert data == realpath("~/Library/Jupyter")
 
-    with darwin, xdg:
-        # darwin should ignore xdg
-        data = jupyter_data_dir()
-    assert data == realpath("~/Library/Jupyter")
 
+@macos
+@use_platformdirs
+def test_data_dir_darwin():
+    data = jupyter_data_dir()
+    assert data == realpath("~/Library/Application Support/Jupyter")
 
-@pytest.mark.skipif(sys.platform != "win32", reason="only run on windows")
-def test_data_dir_windows():
+
+@windows
+def test_data_dir_windows_legacy():
     data = jupyter_data_dir()
     assert data == realpath(pjoin(os.environ.get("APPDATA", ""), "jupyter"))
 
+
+@windows
+@use_platformdirs
+def test_data_dir_windows():
+    data = jupyter_data_dir()
+    assert data == realpath(pjoin(os.environ.get("LOCALAPPDATA", ""), "Jupyter"))
+
+
+@linux
+def test_data_dir_linux_legacy():
+    with no_xdg:
+        data = jupyter_data_dir()
+        assert data == realpath("~/.local/share/jupyter")
+
     with xdg:
-        # windows should ignore xdg
         data = jupyter_data_dir()
-    assert data == realpath(pjoin(os.environ.get("APPDATA", ""), "jupyter"))
+        assert data == pjoin(xdg_env["XDG_DATA_HOME"], "jupyter")
 
 
-@pytest.mark.skipif(sys.platform == "win32", reason="does not run on windows")
+@linux
+@use_platformdirs
 def test_data_dir_linux():
-    with linux, no_xdg:
+    with no_xdg:
         data = jupyter_data_dir()
-    assert data == realpath("~/.local/share/jupyter")
+        assert data == realpath("~/.local/share/jupyter")
 
-    with linux, xdg:
+    with xdg:
         data = jupyter_data_dir()
-    assert data == pjoin(xdg_env["XDG_DATA_HOME"], "jupyter")
+        assert data == pjoin(xdg_env["XDG_DATA_HOME"], "jupyter")
 
 
-def test_runtime_dir_env():
+def test_runtime_dir_env_legacy():
     rtd_env = "runtime-dir"
     with patch.dict("os.environ", {"JUPYTER_RUNTIME_DIR": rtd_env}):
         runtime = jupyter_runtime_dir()
-    assert runtime == rtd_env
+        assert runtime == rtd_env
 
 
-@pytest.mark.skipif(sys.platform == "win32", reason="does not run on windows")
-def test_runtime_dir_darwin():
-    with darwin:
+@use_platformdirs
+def test_runtime_dir_env():
+    rtd_env = "runtime-dir"
+    with patch.dict("os.environ", {"JUPYTER_RUNTIME_DIR": rtd_env}):
         runtime = jupyter_runtime_dir()
-    assert runtime == realpath("~/Library/Jupyter/runtime")
+        assert runtime == rtd_env
 
-    with darwin, xdg:
-        # darwin should ignore xdg
-        runtime = jupyter_runtime_dir()
+
+@macos
+def test_runtime_dir_darwin_legacy():
+    runtime = jupyter_runtime_dir()
     assert runtime == realpath("~/Library/Jupyter/runtime")
 
 
-@pytest.mark.skipif(sys.platform != "win32", reason="only run on windows")
-def test_runtime_dir_windows():
+@macos
+@use_platformdirs
+def test_runtime_dir_darwin():
+    runtime = jupyter_runtime_dir()
+    assert runtime == realpath("~/Library/Application Support/Jupyter/runtime")
+
+
+@windows
+def test_runtime_dir_windows_legacy():
     runtime = jupyter_runtime_dir()
     assert runtime == realpath(pjoin(os.environ.get("APPDATA", ""), "jupyter", "runtime"))
 
+
+@windows
+@use_platformdirs
+def test_runtime_dir_windows():
+    runtime = jupyter_runtime_dir()
+    assert runtime == realpath(pjoin(os.environ.get("LOCALAPPDATA", ""), "Jupyter", "runtime"))
+
+
+@linux
+def test_runtime_dir_linux_legacy():
+    with no_xdg:
+        runtime = jupyter_runtime_dir()
+        assert runtime == realpath("~/.local/share/jupyter/runtime")
+
     with xdg:
-        # windows should ignore xdg
         runtime = jupyter_runtime_dir()
-    assert runtime == realpath(pjoin(os.environ.get("APPDATA", ""), "jupyter", "runtime"))
+        assert runtime == pjoin(xdg_env["XDG_DATA_HOME"], "jupyter", "runtime")
 
 
-@pytest.mark.skipif(sys.platform == "win32", reason="does not run on windows")
+@linux
+@use_platformdirs
 def test_runtime_dir_linux():
-    with linux, no_xdg:
+    with no_xdg:
         runtime = jupyter_runtime_dir()
-    assert runtime == realpath("~/.local/share/jupyter/runtime")
+        assert runtime == realpath("~/.local/share/jupyter/runtime")
 
-    with linux, xdg:
+    with xdg:
         runtime = jupyter_runtime_dir()
-    assert runtime == pjoin(xdg_env["XDG_DATA_HOME"], "jupyter", "runtime")
+        assert runtime == pjoin(xdg_env["XDG_DATA_HOME"], "jupyter", "runtime")
 
 
 def test_jupyter_path():
     system_path = ["system", "path"]
     with no_config_env, patch.object(paths, "SYSTEM_JUPYTER_PATH", system_path):
         path = jupyter_path()
-    assert path[0] == jupyter_data_dir()
-    assert path[-2:] == system_path
+        assert path[0] == jupyter_data_dir()
+        assert path[-2:] == system_path
 
 
 def test_jupyter_path_user_site():
     with no_config_env, patch.object(site, "ENABLE_USER_SITE", True):
         path = jupyter_path()
 
-    # deduplicated expected values
-    values = list(
-        dict.fromkeys(
-            [
-                jupyter_data_dir(),
-                os.path.join(site.getuserbase(), "share", "jupyter"),
-                paths.ENV_JUPYTER_PATH[0],
-            ]
+        # deduplicated expected values
+        values = list(
+            dict.fromkeys(
+                [
+                    jupyter_data_dir(),
+                    os.path.join(site.getuserbase(), "share", "jupyter"),
+                    paths.ENV_JUPYTER_PATH[0],
+                ]
+            )
         )
-    )
-    for p, v in zip(path, values):
-        assert p == v
+        for p, v in zip(path, values):
+            assert p == v
 
 
 def test_jupyter_path_no_user_site():
     with no_config_env, patch.object(site, "ENABLE_USER_SITE", False):
         path = jupyter_path()
-    assert path[0] == jupyter_data_dir()
-    assert path[1] == paths.ENV_JUPYTER_PATH[0]
+        assert path[0] == jupyter_data_dir()
+        assert path[1] == paths.ENV_JUPYTER_PATH[0]
 
 
 def test_jupyter_path_prefer_env():
     with prefer_env:
         path = jupyter_path()
-    assert path[0] == paths.ENV_JUPYTER_PATH[0]
-    assert path[1] == jupyter_data_dir()
+        assert path[0] == paths.ENV_JUPYTER_PATH[0]
+        assert path[1] == jupyter_data_dir()
 
 
 def test_jupyter_path_env():