Repository: jupyter-server/jupyter_server

Issue:
v1.10.0 is breaking JupyterLab CI
The error arises from:

https://github.com/jupyter-server/jupyter_server/blob/b4b151710336aa3f18ee7dc54857c82b238cbf05/jupyter_server/utils.py#L391

From the documentation

> Find the spec for a module, optionally relative to the specified package name. If the module is in sys.modules, then sys.modules[name].__spec__ is returned (unless the spec would be None or **is not set, in which case ValueError is raised**).

If the application is defined in a Python file not belonging to a package, `__spec__` won't be set - this is the case of the broken JupyterLab CI test.

Xref: https://github.com/jupyterlab/jupyterlab/issues/10682

Discussion:
codecov-commenter: # [Codecov](https://codecov.io/gh/jupyter-server/jupyter_server/pull/556?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=jupyter-server) Report
> Merging [#556](https://codecov.io/gh/jupyter-server/jupyter_server/pull/556?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=jupyter-server) (674949c) into [master](https://codecov.io/gh/jupyter-server/jupyter_server/commit/b4b151710336aa3f18ee7dc54857c82b238cbf05?el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=jupyter-server) (b4b1517) will **decrease** coverage by `0.60%`.
> The diff coverage is `50.00%`.

[![Impacted file tree graph](https://codecov.io/gh/jupyter-server/jupyter_server/pull/556/graphs/tree.svg?width=650&height=150&src=pr&token=S9WiBg2iL0&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=jupyter-server)](https://codecov.io/gh/jupyter-server/jupyter_server/pull/556?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=jupyter-server)

```diff
@@            Coverage Diff             @@
##           master     #556      +/-   ##
==========================================
- Coverage   76.98%   76.37%   -0.61%     
==========================================
  Files         109      109              
  Lines        9945     9944       -1     
  Branches     1078     1078              
==========================================
- Hits         7656     7595      -61     
- Misses       1909     1971      +62     
+ Partials      380      378       -2     
```


| [Impacted Files](https://codecov.io/gh/jupyter-server/jupyter_server/pull/556?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=jupyter-server) | Coverage Δ | |
|---|---|---|
| [jupyter\_server/utils.py](https://codecov.io/gh/jupyter-server/jupyter_server/pull/556/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=jupyter-server#diff-anVweXRlcl9zZXJ2ZXIvdXRpbHMucHk=) | `63.85% <50.00%> (-0.57%)` | :arrow_down: |
| [jupyter\_server/traittypes.py](https://codecov.io/gh/jupyter-server/jupyter_server/pull/556/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=jupyter-server#diff-anVweXRlcl9zZXJ2ZXIvdHJhaXR0eXBlcy5weQ==) | `47.02% <0.00%> (-16.67%)` | :arrow_down: |
| [...yter\_server/tests/services/kernelspecs/test\_api.py](https://codecov.io/gh/jupyter-server/jupyter_server/pull/556/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=jupyter-server#diff-anVweXRlcl9zZXJ2ZXIvdGVzdHMvc2VydmljZXMva2VybmVsc3BlY3MvdGVzdF9hcGkucHk=) | `96.49% <0.00%> (-3.51%)` | :arrow_down: |
| [jupyter\_server/services/kernels/handlers.py](https://codecov.io/gh/jupyter-server/jupyter_server/pull/556/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=jupyter-server#diff-anVweXRlcl9zZXJ2ZXIvc2VydmljZXMva2VybmVscy9oYW5kbGVycy5weQ==) | `60.05% <0.00%> (-2.92%)` | :arrow_down: |
| [jupyter\_server/config\_manager.py](https://codecov.io/gh/jupyter-server/jupyter_server/pull/556/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=jupyter-server#diff-anVweXRlcl9zZXJ2ZXIvY29uZmlnX21hbmFnZXIucHk=) | `94.52% <0.00%> (-2.74%)` | :arrow_down: |
| [jupyter\_server/tests/extension/test\_launch.py](https://codecov.io/gh/jupyter-server/jupyter_server/pull/556/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=jupyter-server#diff-anVweXRlcl9zZXJ2ZXIvdGVzdHMvZXh0ZW5zaW9uL3Rlc3RfbGF1bmNoLnB5) | `93.84% <0.00%> (-1.61%)` | :arrow_down: |
| [jupyter\_server/services/kernels/kernelmanager.py](https://codecov.io/gh/jupyter-server/jupyter_server/pull/556/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=jupyter-server#diff-anVweXRlcl9zZXJ2ZXIvc2VydmljZXMva2VybmVscy9rZXJuZWxtYW5hZ2VyLnB5) | `79.34% <0.00%> (-1.52%)` | :arrow_down: |
| [jupyter\_server/services/contents/filemanager.py](https://codecov.io/gh/jupyter-server/jupyter_server/pull/556/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=jupyter-server#diff-anVweXRlcl9zZXJ2ZXIvc2VydmljZXMvY29udGVudHMvZmlsZW1hbmFnZXIucHk=) | `65.48% <0.00%> (-0.84%)` | :arrow_down: |
| [jupyter\_server/base/zmqhandlers.py](https://codecov.io/gh/jupyter-server/jupyter_server/pull/556/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=jupyter-server#diff-anVweXRlcl9zZXJ2ZXIvYmFzZS96bXFoYW5kbGVycy5weQ==) | `59.31% <0.00%> (-0.69%)` | :arrow_down: |
| [jupyter\_server/services/contents/fileio.py](https://codecov.io/gh/jupyter-server/jupyter_server/pull/556/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=jupyter-server#diff-anVweXRlcl9zZXJ2ZXIvc2VydmljZXMvY29udGVudHMvZmlsZWlvLnB5) | `74.73% <0.00%> (-0.53%)` | :arrow_down: |
| ... and [4 more](https://codecov.io/gh/jupyter-server/jupyter_server/pull/556/diff?src=pr&el=tree-more&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=jupyter-server) | |

------

[Continue to review full report at Codecov](https://codecov.io/gh/jupyter-server/jupyter_server/pull/556?src=pr&el=continue&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=jupyter-server).
> **Legend** - [Click here to learn more](https://docs.codecov.io/docs/codecov-delta?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=jupyter-server)
> `Δ = absolute <relative> (impact)`, `ø = not affected`, `? = missing data`
> Powered by [Codecov](https://codecov.io/gh/jupyter-server/jupyter_server/pull/556?src=pr&el=footer&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=jupyter-server). Last update [b4b1517...674949c](https://codecov.io/gh/jupyter-server/jupyter_server/pull/556?src=pr&el=lastupdated&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=jupyter-server). Read the [comment docs](https://docs.codecov.io/docs/pull-request-comments?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=jupyter-server).


Patch:
diff --git a/jupyter_server/utils.py b/jupyter_server/utils.py
index 28d507fde..7af3c3a52 100644
--- a/jupyter_server/utils.py
+++ b/jupyter_server/utils.py
@@ -388,7 +388,11 @@ def is_namespace_package(namespace):
 
     """
     # NOTE: using submodule_search_locations because the loader can be None
-    spec = importlib.util.find_spec(namespace)
+    try:
+        spec = importlib.util.find_spec(namespace)
+    except ValueError:  # spec is not set - see https://docs.python.org/3/library/importlib.html#importlib.util.find_spec
+        return None
+
     if not spec:
         # e.g. module not installed
         return None


Test patch:
diff --git a/jupyter_server/tests/test_utils.py b/jupyter_server/tests/test_utils.py
index 3de7336b6..cf04d743e 100644
--- a/jupyter_server/tests/test_utils.py
+++ b/jupyter_server/tests/test_utils.py
@@ -1,5 +1,5 @@
 from pathlib import Path
-import sys
+from unittest.mock import patch
 
 import pytest
 
@@ -45,28 +45,27 @@ def test_url_escaping(unescaped, escaped):
     assert path == unescaped
 
 
-@pytest.fixture
-def namespace_package_test(monkeypatch):
-    """Adds a blank namespace package into the PYTHONPATH for testing.
-
-    Yields the name of the importable namespace.
-    """
-    monkeypatch.setattr(
-        sys,
-        'path',
-        [
-            str(Path(__file__).parent / 'namespace-package-test'),
-            *sys.path
-        ]
-    )
-    yield 'test_namespace'
+@pytest.mark.parametrize(
+    'name, expected',
+    [
+        # returns True if it is a namespace package
+        ('test_namespace', True),
+        # returns False if it isn't a namespace package
+        ('sys', False),
+        ('jupyter_server', False),
+        # returns None if it isn't importable
+        ('not_a_python_namespace', None)
+    ]
+)
+def test_is_namespace_package(monkeypatch, name, expected):
+    monkeypatch.syspath_prepend(Path(__file__).parent / 'namespace-package-test')
+    
+    assert is_namespace_package(name) is expected
+    
 
+def test_is_namespace_package_no_spec():
+    with patch("importlib.util.find_spec") as mocked_spec:
+        mocked_spec.side_effect = ValueError()
 
-def test_is_namespace_package(namespace_package_test):
-    # returns True if it is a namespace package
-    assert is_namespace_package(namespace_package_test)
-    # returns False if it isn't a namespace package
-    assert not is_namespace_package('sys')
-    assert not is_namespace_package('jupyter_server')
-    # returns None if it isn't importable
-    assert is_namespace_package('not_a_python_namespace') is None
+        assert is_namespace_package('dummy') is None
+        mocked_spec.assert_called_once_with('dummy')