Script 'mail_helper' called by obssrc
Hello community,

here is the log from the commit of package python-jupyter-server for 
openSUSE:Factory checked in at 2021-08-23 10:07:55
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:Factory/python-jupyter-server (Old)
 and      /work/SRC/openSUSE:Factory/.python-jupyter-server.new.1899 (New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Package is "python-jupyter-server"

Mon Aug 23 10:07:55 2021 rev:15 rq:912675 version:1.10.2

Changes:
--------
--- 
/work/SRC/openSUSE:Factory/python-jupyter-server/python-jupyter-server.changes  
    2021-07-18 23:45:14.118913973 +0200
+++ 
/work/SRC/openSUSE:Factory/.python-jupyter-server.new.1899/python-jupyter-server.changes
    2021-08-23 10:08:34.540218004 +0200
@@ -1,0 +2,21 @@
+Mon Aug 16 12:39:42 UTC 2021 - Ben Greiner <[email protected]>
+
+- Update to 1.10.2
+  * fix: make command line aliases work again #564
+  * decode bytes from secure cookie #562
+  * Maintenance and upkeep improvements
+  * Add the needed space in the welcome message #561
+  * Update check-release workflow #558
+  * Fix typo in allow_password_change help #559
+- Release notes for v1.10.1
+  * Protect against unset spec #556
+- Release notes for v1.10.0
+  * PR: Add a new preferred-dir traitlet #549
+  * stop hook for extensions #526
+  * extensions: allow extensions in namespace packages #523
+  * Fix examples/simple test execution #552
+  * Rebuild package-lock, fixing local setup #548
+  * small test changes #541
+- Add conftest.py missing from release tarball
+
+-------------------------------------------------------------------

Old:
----
  jupyter_server-1.9.0.tar.gz

New:
----
  conftest.py
  jupyter_server-1.10.2.tar.gz

++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Other differences:
------------------
++++++ python-jupyter-server.spec ++++++
--- /var/tmp/diff_new_pack.Yv75CQ/_old  2021-08-23 10:08:35.080217375 +0200
+++ /var/tmp/diff_new_pack.Yv75CQ/_new  2021-08-23 10:08:35.080217375 +0200
@@ -19,13 +19,16 @@
 %{?!python_module:%define python_module() python3-%{**}}
 %define         skip_python2 1
 Name:           python-jupyter-server
-Version:        1.9.0
+Version:        1.10.2
 Release:        0
 Summary:        The backend to Jupyter web applications
 License:        BSD-3-Clause
 Group:          Development/Languages/Python
 URL:            https://github.com/jupyter-server/jupyter_server
+# need the release tarball for the static stylesheets
 Source:         
https://github.com/jupyter-server/jupyter_server/releases/download/v%{version}/jupyter_server-%{version}.tar.gz
+# conftest.py is not in release tarball but required for tests
+Source1:        
https://github.com/jupyter-server/jupyter_server/raw/v%{version}/conftest.py
 BuildRequires:  %{python_module Jinja2}
 BuildRequires:  %{python_module Send2Trash}
 BuildRequires:  %{python_module anyio >= 3.1.0}
@@ -70,10 +73,10 @@
 Obsoletes:      python-jupyter_server < %{version}-%{release}
 # SECTION extras_require test
 BuildRequires:  %{python_module ipykernel}
+BuildRequires:  %{python_module pytest >= 6}
 BuildRequires:  %{python_module pytest-console-scripts}
 BuildRequires:  %{python_module pytest-mock}
 BuildRequires:  %{python_module pytest-tornasync}
-BuildRequires:  %{python_module pytest}
 BuildRequires:  %{python_module requests}
 # /SECTION
 %if "%{python_flavor}" == "python3" || "%{python_provides}" == "python3"
@@ -91,6 +94,7 @@
 
 %prep
 %setup -q -n jupyter_server-%{version}
+cp %{SOURCE1} ./
 
 %build
 %python_build

++++++ conftest.py ++++++
import pytest


pytest_plugins = [
    "jupyter_server.pytest_plugin"
]


def pytest_addoption(parser):
    parser.addoption(
        "--integration_tests",
        default=False,
        type=bool,
        help="only run tests with the 'integration_test' pytest mark.",
    )


def pytest_configure(config):
    # register an additional marker
    config.addinivalue_line(
        "markers", "integration_test"
    )


def pytest_runtest_setup(item):
    is_integration_test = any(mark for mark in 
item.iter_markers(name="integration_test"))

    if item.config.getoption("--integration_tests") is True:
        if not is_integration_test:
            pytest.skip("Only running tests marked as 'integration_test'.")
    else:
        if is_integration_test:
            pytest.skip("Skipping this test because it's marked 
'integration_test'. Run integration tests using the `--integration_tests` 
flag.")
++++++ jupyter_server-1.9.0.tar.gz -> jupyter_server-1.10.2.tar.gz ++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/jupyter_server-1.9.0/CHANGELOG.md 
new/jupyter_server-1.10.2/CHANGELOG.md
--- old/jupyter_server-1.9.0/CHANGELOG.md       2021-06-24 17:42:30.000000000 
+0200
+++ new/jupyter_server-1.10.2/CHANGELOG.md      2021-08-02 23:35:30.000000000 
+0200
@@ -4,6 +4,71 @@
 
 <!-- <START NEW CHANGELOG ENTRY> -->
 
+## 1.10.2
+
+([Full 
Changelog](https://github.com/jupyter-server/jupyter_server/compare/v1.10.1...7956dc51d8239b7b9e8de3b22ceb4473bbf1d4e5))
+
+### Bugs fixed
+
+- fix: make command line aliases work again 
[#564](https://github.com/jupyter-server/jupyter_server/pull/564) 
([@mariobuikhuizen](https://github.com/mariobuikhuizen))
+- decode bytes from secure cookie 
[#562](https://github.com/jupyter-server/jupyter_server/pull/562) 
([@oliver-sanders](https://github.com/oliver-sanders))
+
+### Maintenance and upkeep improvements
+
+- Add the needed space in the welcome message 
[#561](https://github.com/jupyter-server/jupyter_server/pull/561) 
([@echarles](https://github.com/echarles))
+- Update check-release workflow 
[#558](https://github.com/jupyter-server/jupyter_server/pull/558) 
([@afshin](https://github.com/afshin))
+
+### Documentation improvements
+
+- Fix typo in allow_password_change help 
[#559](https://github.com/jupyter-server/jupyter_server/pull/559) 
([@manics](https://github.com/manics))
+
+### Contributors to this release
+
+([GitHub contributors page for this 
release](https://github.com/jupyter-server/jupyter_server/graphs/contributors?from=2021-07-23&to=2021-08-02&type=c))
+
+[@afshin](https://github.com/search?q=repo%3Ajupyter-server%2Fjupyter_server+involves%3Aafshin+updated%3A2021-07-23..2021-08-02&type=Issues)
 | 
[@codecov-commenter](https://github.com/search?q=repo%3Ajupyter-server%2Fjupyter_server+involves%3Acodecov-commenter+updated%3A2021-07-23..2021-08-02&type=Issues)
 | 
[@echarles](https://github.com/search?q=repo%3Ajupyter-server%2Fjupyter_server+involves%3Aecharles+updated%3A2021-07-23..2021-08-02&type=Issues)
 | 
[@manics](https://github.com/search?q=repo%3Ajupyter-server%2Fjupyter_server+involves%3Amanics+updated%3A2021-07-23..2021-08-02&type=Issues)
 | 
[@mariobuikhuizen](https://github.com/search?q=repo%3Ajupyter-server%2Fjupyter_server+involves%3Amariobuikhuizen+updated%3A2021-07-23..2021-08-02&type=Issues)
 | 
[@oliver-sanders](https://github.com/search?q=repo%3Ajupyter-server%2Fjupyter_server+involves%3Aoliver-sanders+updated%3A2021-07-23..2021-08-02&type=Issues)
 | 
[@welcome](https://github.com/search?q=repo%3Ajupyter-server%2Fjupyter_server+i
 nvolves%3Awelcome+updated%3A2021-07-23..2021-08-02&type=Issues) | 
[@Zsailer](https://github.com/search?q=repo%3Ajupyter-server%2Fjupyter_server+involves%3AZsailer+updated%3A2021-07-23..2021-08-02&type=Issues)
+
+<!-- <END NEW CHANGELOG ENTRY> -->
+
+## 1.10.1
+
+([Full 
Changelog](https://github.com/jupyter-server/jupyter_server/compare/v1.10.0...42a195665aa8ae218fce4ec8165f19e734a9edaf))
+
+### Bugs fixed
+
+- Protect against unset spec 
[#556](https://github.com/jupyter-server/jupyter_server/pull/556) 
([@fcollonval](https://github.com/fcollonval))
+
+### Contributors to this release
+
+([GitHub contributors page for this 
release](https://github.com/jupyter-server/jupyter_server/graphs/contributors?from=2021-07-22&to=2021-07-23&type=c))
+
+[@fcollonval](https://github.com/search?q=repo%3Ajupyter-server%2Fjupyter_server+involves%3Afcollonval+updated%3A2021-07-22..2021-07-23&type=Issues)
+
+## 1.10.0
+
+([Full 
Changelog](https://github.com/jupyter-server/jupyter_server/compare/v1.9.0...c9ee2a45e9a8f04215c2f3901f90cdc7b8fdc9c6))
+
+### Enhancements made
+
+- PR: Add a new preferred-dir traitlet 
[#549](https://github.com/jupyter-server/jupyter_server/pull/549) 
([@goanpeca](https://github.com/goanpeca))
+- stop hook for extensions 
[#526](https://github.com/jupyter-server/jupyter_server/pull/526) 
([@oliver-sanders](https://github.com/oliver-sanders))
+- extensions: allow extensions in namespace packages 
[#523](https://github.com/jupyter-server/jupyter_server/pull/523) 
([@oliver-sanders](https://github.com/oliver-sanders))
+
+### Bugs fixed
+
+- Fix examples/simple test execution 
[#552](https://github.com/jupyter-server/jupyter_server/pull/552) 
([@davidbrochart](https://github.com/davidbrochart))
+- Rebuild package-lock, fixing local setup 
[#548](https://github.com/jupyter-server/jupyter_server/pull/548) 
([@martinRenou](https://github.com/martinRenou))
+
+### Maintenance and upkeep improvements
+
+- small test changes 
[#541](https://github.com/jupyter-server/jupyter_server/pull/541) 
([@oliver-sanders](https://github.com/oliver-sanders))
+
+### Contributors to this release
+
+([GitHub contributors page for this 
release](https://github.com/jupyter-server/jupyter_server/graphs/contributors?from=2021-06-24&to=2021-07-21&type=c))
+
+[@blink1073](https://github.com/search?q=repo%3Ajupyter-server%2Fjupyter_server+involves%3Ablink1073+updated%3A2021-06-24..2021-07-21&type=Issues)
 | 
[@codecov-commenter](https://github.com/search?q=repo%3Ajupyter-server%2Fjupyter_server+involves%3Acodecov-commenter+updated%3A2021-06-24..2021-07-21&type=Issues)
 | 
[@davidbrochart](https://github.com/search?q=repo%3Ajupyter-server%2Fjupyter_server+involves%3Adavidbrochart+updated%3A2021-06-24..2021-07-21&type=Issues)
 | 
[@goanpeca](https://github.com/search?q=repo%3Ajupyter-server%2Fjupyter_server+involves%3Agoanpeca+updated%3A2021-06-24..2021-07-21&type=Issues)
 | 
[@kevin-bates](https://github.com/search?q=repo%3Ajupyter-server%2Fjupyter_server+involves%3Akevin-bates+updated%3A2021-06-24..2021-07-21&type=Issues)
 | 
[@martinRenou](https://github.com/search?q=repo%3Ajupyter-server%2Fjupyter_server+involves%3AmartinRenou+updated%3A2021-06-24..2021-07-21&type=Issues)
 | [@oliver-sanders](https://github.com/search?q=repo%3Ajupyter-server%2Fjup
 
yter_server+involves%3Aoliver-sanders+updated%3A2021-06-24..2021-07-21&type=Issues)
 | 
[@welcome](https://github.com/search?q=repo%3Ajupyter-server%2Fjupyter_server+involves%3Awelcome+updated%3A2021-06-24..2021-07-21&type=Issues)
 | 
[@Zsailer](https://github.com/search?q=repo%3Ajupyter-server%2Fjupyter_server+involves%3AZsailer+updated%3A2021-06-24..2021-07-21&type=Issues)
+
 ## 1.9.0
 
 ([Full 
Changelog](https://github.com/jupyter-server/jupyter_server/compare/v1.8.0...f712734c4f7005f6a844abec9f57b993e7b004b0))
@@ -35,8 +100,6 @@
 
 
[@blink1073](https://github.com/search?q=repo%3Ajupyter-server%2Fjupyter_server+involves%3Ablink1073+updated%3A2021-05-20..2021-06-24&type=Issues)
 | 
[@codecov-commenter](https://github.com/search?q=repo%3Ajupyter-server%2Fjupyter_server+involves%3Acodecov-commenter+updated%3A2021-05-20..2021-06-24&type=Issues)
 | 
[@davidbrochart](https://github.com/search?q=repo%3Ajupyter-server%2Fjupyter_server+involves%3Adavidbrochart+updated%3A2021-05-20..2021-06-24&type=Issues)
 | 
[@eastonsuo](https://github.com/search?q=repo%3Ajupyter-server%2Fjupyter_server+involves%3Aeastonsuo+updated%3A2021-05-20..2021-06-24&type=Issues)
 | 
[@icankeep](https://github.com/search?q=repo%3Ajupyter-server%2Fjupyter_server+involves%3Aicankeep+updated%3A2021-05-20..2021-06-24&type=Issues)
 | 
[@jtpio](https://github.com/search?q=repo%3Ajupyter-server%2Fjupyter_server+involves%3Ajtpio+updated%3A2021-05-20..2021-06-24&type=Issues)
 | 
[@kevin-bates](https://github.com/search?q=repo%3Ajupyter-server%2Fjupyter_server+involve
 s%3Akevin-bates+updated%3A2021-05-20..2021-06-24&type=Issues) | 
[@krassowski](https://github.com/search?q=repo%3Ajupyter-server%2Fjupyter_server+involves%3Akrassowski+updated%3A2021-05-20..2021-06-24&type=Issues)
 | 
[@telamonian](https://github.com/search?q=repo%3Ajupyter-server%2Fjupyter_server+involves%3Atelamonian+updated%3A2021-05-20..2021-06-24&type=Issues)
 | 
[@vidartf](https://github.com/search?q=repo%3Ajupyter-server%2Fjupyter_server+involves%3Avidartf+updated%3A2021-05-20..2021-06-24&type=Issues)
 | 
[@welcome](https://github.com/search?q=repo%3Ajupyter-server%2Fjupyter_server+involves%3Awelcome+updated%3A2021-05-20..2021-06-24&type=Issues)
 | 
[@Zsailer](https://github.com/search?q=repo%3Ajupyter-server%2Fjupyter_server+involves%3AZsailer+updated%3A2021-05-20..2021-06-24&type=Issues)
 
-<!-- <END NEW CHANGELOG ENTRY> -->
-
 ## 1.8.0
 
 ([Full 
Changelog](https://github.com/jupyter-server/jupyter_server/compare/v1.7.0...b063117a3a48ea67371c62e492f4637e44157586))
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/jupyter_server-1.9.0/CONTRIBUTING.rst 
new/jupyter_server-1.10.2/CONTRIBUTING.rst
--- old/jupyter_server-1.9.0/CONTRIBUTING.rst   2021-06-24 17:42:30.000000000 
+0200
+++ new/jupyter_server-1.10.2/CONTRIBUTING.rst  2021-08-02 23:35:30.000000000 
+0200
@@ -58,10 +58,12 @@
 Install dependencies::
 
     pip install -e .[test]
+    pip install -e examples/simple
 
 To run the Python tests, use::
 
     pytest
+    pytest examples/simple --confcutdir=$PWD
 
 Building the Docs
 =================
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/jupyter_server-1.9.0/PKG-INFO 
new/jupyter_server-1.10.2/PKG-INFO
--- old/jupyter_server-1.9.0/PKG-INFO   2021-06-24 17:45:56.836275000 +0200
+++ new/jupyter_server-1.10.2/PKG-INFO  2021-08-02 23:35:56.407143000 +0200
@@ -1,6 +1,6 @@
 Metadata-Version: 2.1
 Name: jupyter_server
-Version: 1.9.0
+Version: 1.10.2
 Summary: The backend???i.e. core services, APIs, and REST endpoints???to 
Jupyter web applications.
 Home-page: https://jupyter.org
 Author: Jupyter Development Team
@@ -61,10 +61,7 @@
 
 ### Testing
 
-To test an installed `jupyter_server`, run the following:
-
-    pip install jupyter_server[test]
-    pytest jupyter_server
+See 
[CONTRIBUTING](https://github.com/jupyter-server/jupyter_server/blob/master/CONTRIBUTING.rst#running-tests).
 
 ## Contributing
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/jupyter_server-1.9.0/README.md 
new/jupyter_server-1.10.2/README.md
--- old/jupyter_server-1.9.0/README.md  2021-06-24 17:42:30.000000000 +0200
+++ new/jupyter_server-1.10.2/README.md 2021-08-02 23:35:30.000000000 +0200
@@ -35,10 +35,7 @@
 
 ### Testing
 
-To test an installed `jupyter_server`, run the following:
-
-    pip install jupyter_server[test]
-    pytest jupyter_server
+See 
[CONTRIBUTING](https://github.com/jupyter-server/jupyter_server/blob/master/CONTRIBUTING.rst#running-tests).
 
 ## Contributing
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/jupyter_server-1.9.0/docs/source/developers/extensions.rst 
new/jupyter_server-1.10.2/docs/source/developers/extensions.rst
--- old/jupyter_server-1.9.0/docs/source/developers/extensions.rst      
2021-06-24 17:42:30.000000000 +0200
+++ new/jupyter_server-1.10.2/docs/source/developers/extensions.rst     
2021-08-02 23:35:30.000000000 +0200
@@ -156,14 +156,19 @@
             ...
             # Change the jinja templating environment
 
+        async def stop_extension(self):
+            ...
+            # Perform any required shut down steps
+
 
 The ``ExtensionApp`` uses the following methods and properties to connect your 
extension to the Jupyter server. You do not need to define a 
``_load_jupyter_server_extension`` function for these apps. Instead, overwrite 
the pieces below to add your custom settings, handlers and templates:
 
 Methods
 
-* ``initialize_setting()``: adds custom settings to the Tornado Web 
Application.
+* ``initialize_settings()``: adds custom settings to the Tornado Web 
Application.
 * ``initialize_handlers()``: appends handlers to the Tornado Web Application.
 * ``initialize_templates()``: initialize the templating engine (e.g. jinja2) 
for your frontend.
+* ``stop_extension()``: called on server shut down.
 
 Properties
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/jupyter_server-1.9.0/docs/source/other/full-config.rst 
new/jupyter_server-1.10.2/docs/source/other/full-config.rst
--- old/jupyter_server-1.9.0/docs/source/other/full-config.rst  2021-06-24 
17:42:30.000000000 +0200
+++ new/jupyter_server-1.10.2/docs/source/other/full-config.rst 2021-08-02 
23:35:30.000000000 +0200
@@ -135,7 +135,7 @@
 
     Allow password to be changed at login for the Jupyter server.
 
-    While loggin in with a token, the Jupyter server UI will give the 
opportunity to
+    While logging in with a token, the Jupyter server UI will give the 
opportunity to
     the user to enter a new password at the same time that will replace
     the token login mechanism.
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/jupyter_server-1.9.0/examples/simple/tests/conftest.py 
new/jupyter_server-1.10.2/examples/simple/tests/conftest.py
--- old/jupyter_server-1.9.0/examples/simple/tests/conftest.py  2021-06-24 
17:42:30.000000000 +0200
+++ new/jupyter_server-1.10.2/examples/simple/tests/conftest.py 1970-01-01 
01:00:00.000000000 +0100
@@ -1,3 +0,0 @@
-pytest_plugins = [
-    'jupyter_server.pytest_plugin'
-]
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/jupyter_server-1.9.0/jupyter_server/_version.py 
new/jupyter_server-1.10.2/jupyter_server/_version.py
--- old/jupyter_server-1.9.0/jupyter_server/_version.py 2021-06-24 
17:42:45.000000000 +0200
+++ new/jupyter_server-1.10.2/jupyter_server/_version.py        2021-08-02 
23:35:45.000000000 +0200
@@ -5,7 +5,7 @@
 import re
 
 # Version string must appear intact for tbump versioning
-__version__ = '1.9.0'
+__version__ = '1.10.2'
 
 # Build up version_info tuple for backwards compatibility
 pattern = r'(?P<major>\d+).(?P<minor>\d+).(?P<patch>\d+)(?P<rest>.*)'
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/jupyter_server-1.9.0/jupyter_server/auth/login.py 
new/jupyter_server-1.10.2/jupyter_server/auth/login.py
--- old/jupyter_server-1.9.0/jupyter_server/auth/login.py       2021-06-24 
17:42:30.000000000 +0200
+++ new/jupyter_server-1.10.2/jupyter_server/auth/login.py      2021-08-02 
23:35:30.000000000 +0200
@@ -172,6 +172,8 @@
         if user_id is None:
             get_secure_cookie_kwargs  = 
handler.settings.get('get_secure_cookie_kwargs', {})
             user_id = handler.get_secure_cookie(handler.cookie_name, 
**get_secure_cookie_kwargs )
+            if user_id:
+                user_id = user_id.decode()
         else:
             cls.set_login_cookie(handler, user_id)
             # Record that the current request has been authenticated with a 
token.
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/jupyter_server-1.9.0/jupyter_server/auth/security.py 
new/jupyter_server-1.10.2/jupyter_server/auth/security.py
--- old/jupyter_server-1.9.0/jupyter_server/auth/security.py    2021-06-24 
17:42:30.000000000 +0200
+++ new/jupyter_server-1.10.2/jupyter_server/auth/security.py   2021-08-02 
23:35:30.000000000 +0200
@@ -43,8 +43,8 @@
 
     Examples
     --------
-    >>> passwd('mypassword')
-    'sha1:7cf3:b7d6da294ea9592a9480c8f52e63cd42cfb9dd12'
+    >>> passwd('mypassword')  # doctest: +ELLIPSIS
+    'argon2:...'
 
     """
     if passphrase is None:
@@ -94,11 +94,11 @@
 
     Examples
     --------
-    >>> from jupyter_server.auth.security import passwd_check
-    >>> passwd_check('argon2:...', 'mypassword')
+    >>> myhash = passwd('mypassword')
+    >>> passwd_check(myhash, 'mypassword')
     True
 
-    >>> passwd_check('argon2:...', 'otherpassword')
+    >>> passwd_check(myhash, 'otherpassword')
     False
 
     >>> 
passwd_check('sha1:0e112c3ddfce:a68df677475c2b47b6e86d0467eec97ac5f4b85a',
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/jupyter_server-1.9.0/jupyter_server/extension/application.py 
new/jupyter_server-1.10.2/jupyter_server/extension/application.py
--- old/jupyter_server-1.9.0/jupyter_server/extension/application.py    
2021-06-24 17:42:30.000000000 +0200
+++ new/jupyter_server-1.10.2/jupyter_server/extension/application.py   
2021-08-02 23:35:30.000000000 +0200
@@ -20,7 +20,7 @@
 
 from jupyter_server.serverapp import ServerApp
 from jupyter_server.transutils import _i18n
-from jupyter_server.utils import url_path_join
+from jupyter_server.utils import url_path_join, is_namespace_package
 from .handler import ExtensionHandlerMixin
 
 # -----------------------------------------------------------------------------
@@ -174,7 +174,11 @@
 
     @classmethod
     def get_extension_package(cls):
-        return cls.__module__.split('.')[0]
+        parts = cls.__module__.split('.')
+        if is_namespace_package(parts[0]):
+            # in this case the package name is `<namespace>.<package>`.
+            return '.'.join(parts[0:2])
+        return parts[0]
 
     @classmethod
     def get_extension_point(cls):
@@ -416,6 +420,9 @@
         # Start the server.
         self.serverapp.start()
 
+    async def stop_extension(self):
+        """Cleanup any resources managed by this extension."""
+
     def stop(self):
         """Stop the underlying Jupyter server.
         """
@@ -485,6 +492,7 @@
             find_extensions = False
         serverapp = ServerApp.instance(
             jpserver_extensions=jpserver_extensions, **kwargs)
+        serverapp.aliases.update(cls.aliases)
         serverapp.initialize(
             argv=argv,
             starter_extension=cls.name,
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/jupyter_server-1.9.0/jupyter_server/extension/manager.py 
new/jupyter_server-1.10.2/jupyter_server/extension/manager.py
--- old/jupyter_server-1.9.0/jupyter_server/extension/manager.py        
2021-06-24 17:42:30.000000000 +0200
+++ new/jupyter_server-1.10.2/jupyter_server/extension/manager.py       
2021-08-02 23:35:30.000000000 +0200
@@ -2,6 +2,8 @@
 import sys
 import traceback
 
+from tornado.gen import multi
+
 from traitlets.config import LoggingConfigurable
 
 from traitlets import (
@@ -230,15 +232,17 @@
 
     def load_point(self, point_name, serverapp):
         point = self.extension_points[point_name]
-        point.load(serverapp)
+        return point.load(serverapp)
 
     def link_all_points(self, serverapp):
         for point_name in self.extension_points:
             self.link_point(point_name, serverapp)
 
     def load_all_points(self, serverapp):
-        for point_name in self.extension_points:
+        return [
             self.load_point(point_name, serverapp)
+            for point_name in self.extension_points
+        ]
 
 
 class ExtensionManager(LoggingConfigurable):
@@ -291,11 +295,25 @@
     )
 
     @property
+    def extension_apps(self):
+        """Return mapping of extension names and sets of ExtensionApp objects.
+        """
+        return {
+            name: {
+                point.app
+                for point in extension.extension_points.values()
+                if point.app
+            }
+            for name, extension in self.extensions.items()
+        }
+
+    @property
     def extension_points(self):
-        extensions = self.extensions
+        """Return mapping of extension point names and ExtensionPoint objects.
+        """
         return {
             name: point
-            for value in extensions.values()
+            for value in self.extensions.values()
             for name, point in value.extension_points.items()
         }
 
@@ -341,13 +359,22 @@
 
     def load_extension(self, name, serverapp):
         extension = self.extensions.get(name)
+
         if extension.enabled:
             try:
-                extension.load_all_points(serverapp)
-                self.log.info("{name} | extension was successfully 
loaded.".format(name=name))
+                points = extension.load_all_points(serverapp)
             except Exception as e:
                 
self.log.debug("".join(traceback.format_exception(*sys.exc_info())))
                 self.log.warning("{name} | extension failed loading with 
message: {error}".format(name=name,error=str(e)))
+            else:
+                self.log.info("{name} | extension was successfully 
loaded.".format(name=name))
+
+    async def stop_extension(self, name, apps):
+        """Call the shutdown hooks in the specified apps."""
+        for app in apps:
+            self.log.debug('{} | extension app "{}" stopping'.format(name, 
app.name))
+            await app.stop_extension()
+            self.log.debug('{} | extension app "{}" stopped'.format(name, 
app.name))
 
     def link_all_extensions(self, serverapp):
         """Link all enabled extensions
@@ -366,3 +393,10 @@
         # order.
         for name in self.sorted_extensions.keys():
             self.load_extension(name, serverapp)
+
+    async def stop_all_extensions(self, serverapp):
+        """Call the shutdown hooks in all extensions."""
+        await multi([
+            self.stop_extension(name, apps)
+            for name, apps in sorted(dict(self.extension_apps).items())
+        ])
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/jupyter_server-1.9.0/jupyter_server/pytest_plugin.py 
new/jupyter_server-1.10.2/jupyter_server/pytest_plugin.py
--- old/jupyter_server-1.9.0/jupyter_server/pytest_plugin.py    2021-06-24 
17:42:30.000000000 +0200
+++ new/jupyter_server-1.10.2/jupyter_server/pytest_plugin.py   2021-08-02 
23:35:30.000000000 +0200
@@ -18,7 +18,7 @@
 
 from jupyter_server.extension import serverextension
 from jupyter_server.serverapp import ServerApp
-from jupyter_server.utils import url_path_join
+from jupyter_server.utils import url_path_join, run_sync
 from jupyter_server.services.contents.filemanager import FileContentsManager
 from jupyter_server.services.contents.largefilemanager import LargeFileManager
 
@@ -284,7 +284,7 @@
     """Starts a Jupyter Server instance based on the established configuration 
values."""
     app = jp_configurable_serverapp(config=jp_server_config, argv=jp_argv)
     yield app
-    app._cleanup()
+    run_sync(app._cleanup())
 
 
 @pytest.fixture
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/jupyter_server-1.9.0/jupyter_server/serverapp.py 
new/jupyter_server-1.10.2/jupyter_server/serverapp.py
--- old/jupyter_server-1.9.0/jupyter_server/serverapp.py        2021-06-24 
17:42:30.000000000 +0200
+++ new/jupyter_server-1.10.2/jupyter_server/serverapp.py       2021-08-02 
23:35:30.000000000 +0200
@@ -43,7 +43,7 @@
 
 from jupyter_core.paths import secure_write
 from jupyter_server.transutils import trans, _i18n
-from jupyter_server.utils import run_sync
+from jupyter_server.utils import run_sync_in_loop
 
 # the minimum viable tornado version: needs to be kept in sync with setup.py
 MIN_TORNADO = (6, 1, 0)
@@ -610,6 +610,7 @@
     'certfile': 'ServerApp.certfile',
     'client-ca': 'ServerApp.client_ca',
     'notebook-dir': 'ServerApp.root_dir',
+    'preferred-dir': 'ServerApp.preferred_dir',
     'browser': 'ServerApp.browser',
     'pylab': 'ServerApp.pylab',
     'gateway-url': 'GatewayClient.url',
@@ -972,7 +973,7 @@
     allow_password_change = Bool(True, config=True,
                     help="""Allow password to be changed at login for the 
Jupyter server.
 
-                    While loggin in with a token, the Jupyter server UI will 
give the opportunity to
+                    While logging in with a token, the Jupyter server UI will 
give the opportunity to
                     the user to enter a new password at the same time that 
will replace
                     the token login mechanism.
 
@@ -1355,9 +1356,7 @@
         else:
             return os.getcwd()
 
-    @validate('root_dir')
-    def _root_dir_validate(self, proposal):
-        value = proposal['value']
+    def _normalize_dir(self, value):
         # Strip any trailing slashes
         # *except* if it's root
         _, path = os.path.splitdrive(value)
@@ -1367,13 +1366,41 @@
         if not os.path.isabs(value):
             # If we receive a non-absolute path, make it absolute.
             value = os.path.abspath(value)
+        return value
+
+    @validate('root_dir')
+    def _root_dir_validate(self, proposal):
+        value = self._normalize_dir(proposal['value'])
         if not os.path.isdir(value):
             raise TraitError(trans.gettext("No such directory: '%r'") % value)
         return value
 
+    preferred_dir = Unicode(config=True,
+        help=trans.gettext("Preferred starting directory to use for notebooks 
and kernels.")
+    )
+
+    @default('preferred_dir')
+    def _default_prefered_dir(self):
+        return self.root_dir
+
+    @validate('preferred_dir')
+    def _preferred_dir_validate(self, proposal):
+        value = self._normalize_dir(proposal['value'])
+        if not os.path.isdir(value):
+            raise TraitError(trans.gettext("No such preferred dir: '%r'") % 
value)
+
+        # preferred_dir must be equal or a subdir of root_dir
+        if not value.startswith(self.root_dir):
+            raise TraitError(trans.gettext("preferred_dir must be equal or a 
subdir of root_dir: '%r'") % value)
+
+        return value
+
     @observe('root_dir')
     def _root_dir_changed(self, change):
         self._root_dir_set = True
+        if not self.preferred_dir.startswith(change['new']):
+            self.log.warning(trans.gettext("Value of preferred_dir updated to 
use value of root_dir"))
+            self.preferred_dir = change['new']
 
     @observe('server_extensions')
     def _update_server_extensions(self, change):
@@ -1750,7 +1777,7 @@
             self.log.critical(_i18n("Shutting down..."))
             # schedule stop on the main thread,
             # since this might be called from a signal handler
-            self.io_loop.add_callback_from_signal(self.io_loop.stop)
+            self.stop(from_signal=True)
             return
         print(self.running_server_info())
         yes = _i18n('y')
@@ -1764,7 +1791,7 @@
                 self.log.critical(_i18n("Shutdown confirmed"))
                 # schedule stop on the main thread,
                 # since this might be called from a signal handler
-                self.io_loop.add_callback_from_signal(self.io_loop.stop)
+                self.stop(from_signal=True)
                 return
         else:
             print(_i18n("No answer for 5s:"), end=' ')
@@ -1777,7 +1804,7 @@
 
     def _signal_stop(self, sig, frame):
         self.log.critical(_i18n("received signal %s, stopping"), sig)
-        self.io_loop.add_callback_from_signal(self.io_loop.stop)
+        self.stop(from_signal=True)
 
     def _signal_info(self, sig, frame):
         print(self.running_server_info())
@@ -2059,7 +2086,7 @@
         if new_httpserver:
             self.init_httpserver()
 
-    def cleanup_kernels(self):
+    async def cleanup_kernels(self):
         """Shutdown all kernels.
 
         The kernels will shutdown themselves when this process no longer 
exists,
@@ -2068,9 +2095,9 @@
         n_kernels = len(self.kernel_manager.list_kernel_ids())
         kernel_msg = trans.ngettext('Shutting down %d kernel', 'Shutting down 
%d kernels', n_kernels)
         self.log.info(kernel_msg % n_kernels)
-        run_sync(self.kernel_manager.shutdown_all())
+        await run_sync_in_loop(self.kernel_manager.shutdown_all())
 
-    def cleanup_terminals(self):
+    async def cleanup_terminals(self):
         """Shutdown all terminals.
 
         The terminals will shutdown themselves when this process no longer 
exists,
@@ -2083,7 +2110,20 @@
         n_terminals = len(terminal_manager.list())
         terminal_msg = trans.ngettext('Shutting down %d terminal', 'Shutting 
down %d terminals', n_terminals)
         self.log.info(terminal_msg % n_terminals)
-        run_sync(terminal_manager.terminate_all())
+        await run_sync_in_loop(terminal_manager.terminate_all())
+
+    async def cleanup_extensions(self):
+        """Call shutdown hooks in all extensions."""
+        n_extensions = len(self.extension_manager.extension_apps)
+        extension_msg = trans.ngettext(
+            'Shutting down %d extension',
+            'Shutting down %d extensions',
+            n_extensions
+        )
+        self.log.info(extension_msg % n_extensions)
+        await run_sync_in_loop(
+            self.extension_manager.stop_all_extensions(self)
+        )
 
     def running_server_info(self, kernel_count=True):
         "Return the current working directory and the server url information"
@@ -2289,7 +2329,7 @@
             info(_i18n("Welcome to Project Jupyter! Explore the various tools 
available"
                  " and their corresponding documentation. If you are 
interested"
                  " in contributing to the platform, please visit the community"
-                 "resources section at https://jupyter.org/community.html.";))
+                 " resources section at https://jupyter.org/community.html.";))
 
         self.write_server_info_file()
         self.write_browser_open_files()
@@ -2321,14 +2361,15 @@
                     '    %s' % self.display_url,
                 ]))
 
-    def _cleanup(self):
-        """General cleanup of files and kernels created
+    async def _cleanup(self):
+        """General cleanup of files, extensions and kernels created
         by this instance ServerApp.
         """
         self.remove_server_info_file()
         self.remove_browser_open_files()
-        self.cleanup_kernels()
-        self.cleanup_terminals()
+        await self.cleanup_extensions()
+        await self.cleanup_kernels()
+        await self.cleanup_terminals()
 
     def start_ioloop(self):
         """Start the IO Loop."""
@@ -2341,8 +2382,6 @@
             self.io_loop.start()
         except KeyboardInterrupt:
             self.log.info(_i18n("Interrupted..."))
-        finally:
-            self._cleanup()
 
     def init_ioloop(self):
         """init self.io_loop so that an extension can use it by 
io_loop.call_later() to create background tasks"""
@@ -2356,13 +2395,23 @@
         self.start_app()
         self.start_ioloop()
 
-    def stop(self):
-        def _stop():
+    async def _stop(self):
+        """Cleanup resources and stop the IO Loop."""
+        await self._cleanup()
+        self.io_loop.stop()
+
+    def stop(self, from_signal=False):
+        """Cleanup resources and stop the server."""
+        if hasattr(self, '_http_server'):
             # Stop a server if its set.
-            if hasattr(self, '_http_server'):
-                self.http_server.stop()
-            self.io_loop.stop()
-        self.io_loop.add_callback(_stop)
+            self.http_server.stop()
+        if getattr(self, 'io_loop', None):
+            # use IOLoop.add_callback because signal.signal must be called
+            # from main thread
+            if from_signal:
+                self.io_loop.add_callback_from_signal(self._stop)
+            else:
+                self.io_loop.add_callback(self._stop)
 
 
 def list_running_servers(runtime_dir=None):
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/jupyter_server-1.9.0/jupyter_server/tests/conftest.py 
new/jupyter_server-1.10.2/jupyter_server/tests/conftest.py
--- old/jupyter_server-1.9.0/jupyter_server/tests/conftest.py   2021-06-24 
17:42:30.000000000 +0200
+++ new/jupyter_server-1.10.2/jupyter_server/tests/conftest.py  1970-01-01 
01:00:00.000000000 +0100
@@ -1,36 +0,0 @@
-import pytest
-
-
-pytest_plugins = [
-    "jupyter_server.pytest_plugin"
-]
-
-
-import pytest
-
-
-def pytest_addoption(parser):
-    parser.addoption(
-        "--integration_tests",
-        default=False,
-        type=bool,
-        help="only run tests with the 'integration_test' pytest mark.",
-    )
-
-
-def pytest_configure(config):
-    # register an additional marker
-    config.addinivalue_line(
-        "markers", "integration_test"
-    )
-
-
-def pytest_runtest_setup(item):
-    is_integration_test = any([mark for mark in 
item.iter_markers(name="integration_test")])
-
-    if item.config.getoption("--integration_tests") is True:
-        if not is_integration_test:
-            pytest.skip("Only running tests marked as 'integration_test'.")
-    else:
-        if is_integration_test:
-            pytest.skip("Skipping this test because it's marked 
'integration_test'. Run integration tests using the `--integration_tests` 
flag.")
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/jupyter_server-1.9.0/jupyter_server/tests/extension/test_app.py 
new/jupyter_server-1.10.2/jupyter_server/tests/extension/test_app.py
--- old/jupyter_server-1.9.0/jupyter_server/tests/extension/test_app.py 
2021-06-24 17:42:30.000000000 +0200
+++ new/jupyter_server-1.10.2/jupyter_server/tests/extension/test_app.py        
2021-08-02 23:35:30.000000000 +0200
@@ -1,6 +1,7 @@
 import pytest
 from traitlets.config import Config
 from jupyter_server.serverapp import ServerApp
+from jupyter_server.utils import run_sync
 from .mockextensions.app import MockExtensionApp
 
 
@@ -101,3 +102,42 @@
     exts = serverapp.jpserver_extensions
     assert exts['jupyter_server.tests.extension.mockextensions.mock1']
     assert exts['jupyter_server.tests.extension.mockextensions']
+
+
+def test_stop_extension(jp_serverapp, caplog):
+    """Test the stop_extension method.
+
+    This should be fired by ServerApp.cleanup_extensions.
+    """
+    calls = 0
+
+    # load extensions (make sure we only have the one extension loaded
+    jp_serverapp.extension_manager.load_all_extensions(jp_serverapp)
+    extension_name = 'jupyter_server.tests.extension.mockextensions'
+    assert list(jp_serverapp.extension_manager.extension_apps) == [
+        extension_name
+    ]
+
+    # add a stop_extension method for the extension app
+    async def _stop(*args):
+        nonlocal calls
+        calls += 1
+    for apps in jp_serverapp.extension_manager.extension_apps.values():
+        for app in apps:
+            if app:
+                app.stop_extension = _stop
+
+    # call cleanup_extensions, check the logging is correct
+    caplog.clear()
+    run_sync(jp_serverapp.cleanup_extensions())
+    assert [
+        msg
+        for *_, msg in caplog.record_tuples
+    ] == [
+        'Shutting down 1 extension',
+        '{} | extension app "mockextension" stopping'.format(extension_name),
+        '{} | extension app "mockextension" stopped'.format(extension_name),
+    ]
+
+    # check the shutdown method was called once
+    assert calls == 1
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/jupyter_server-1.9.0/jupyter_server/tests/namespace-package-test/README.md 
new/jupyter_server-1.10.2/jupyter_server/tests/namespace-package-test/README.md
--- 
old/jupyter_server-1.9.0/jupyter_server/tests/namespace-package-test/README.md  
    1970-01-01 01:00:00.000000000 +0100
+++ 
new/jupyter_server-1.10.2/jupyter_server/tests/namespace-package-test/README.md 
    2021-08-02 23:35:30.000000000 +0200
@@ -0,0 +1,3 @@
+Blank namespace package for use in testing.
+
+https://www.python.org/dev/peps/pep-0420/
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/jupyter_server-1.9.0/jupyter_server/tests/namespace-package-test/setup.cfg 
new/jupyter_server-1.10.2/jupyter_server/tests/namespace-package-test/setup.cfg
--- 
old/jupyter_server-1.9.0/jupyter_server/tests/namespace-package-test/setup.cfg  
    1970-01-01 01:00:00.000000000 +0100
+++ 
new/jupyter_server-1.10.2/jupyter_server/tests/namespace-package-test/setup.cfg 
    2021-08-02 23:35:30.000000000 +0200
@@ -0,0 +1,5 @@
+[metadata]
+name = namespace-package-test
+
+[options]
+packages = find_namespace:
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/jupyter_server-1.9.0/jupyter_server/tests/test_serverapp.py 
new/jupyter_server-1.10.2/jupyter_server/tests/test_serverapp.py
--- old/jupyter_server-1.9.0/jupyter_server/tests/test_serverapp.py     
2021-06-24 17:42:30.000000000 +0200
+++ new/jupyter_server-1.10.2/jupyter_server/tests/test_serverapp.py    
2021-08-02 23:35:30.000000000 +0200
@@ -286,3 +286,93 @@
     assert serverapp.connection_url == connection_url
     # Cleanup singleton after test.
     ServerApp.clear_instance()
+
+
+# Preferred dir tests
+# ----------------------------------------------------------------------------
+def test_valid_preferred_dir(tmp_path, jp_configurable_serverapp):
+    path = str(tmp_path)
+    app = jp_configurable_serverapp(root_dir=path, preferred_dir=path)
+    assert app.root_dir == path
+    assert app.preferred_dir == path
+    assert app.root_dir == app.preferred_dir
+
+
+def test_valid_preferred_dir_is_root_subdir(tmp_path, 
jp_configurable_serverapp):
+    path = str(tmp_path)
+    path_subdir = str(tmp_path / 'subdir')
+    os.makedirs(path_subdir, exist_ok=True)
+    app = jp_configurable_serverapp(root_dir=path, preferred_dir=path_subdir)
+    assert app.root_dir == path
+    assert app.preferred_dir == path_subdir
+    assert app.preferred_dir.startswith(app.root_dir)
+
+
+def test_valid_preferred_dir_does_not_exist(tmp_path, 
jp_configurable_serverapp):
+    path = str(tmp_path)
+    path_subdir = str(tmp_path / 'subdir')
+    with pytest.raises(TraitError) as error:
+        app = jp_configurable_serverapp(root_dir=path, 
preferred_dir=path_subdir)
+
+    assert "No such preferred dir:" in str(error)
+
+
+def test_invalid_preferred_dir_does_not_exist(tmp_path, 
jp_configurable_serverapp):
+    path = str(tmp_path)
+    path_subdir = str(tmp_path / 'subdir')
+    with pytest.raises(TraitError) as error:
+        app = jp_configurable_serverapp(root_dir=path, 
preferred_dir=path_subdir)
+
+    assert "No such preferred dir:" in str(error)
+
+
+def test_invalid_preferred_dir_does_not_exist_set(tmp_path, 
jp_configurable_serverapp):
+    path = str(tmp_path)
+    path_subdir = str(tmp_path / 'subdir')
+
+    app = jp_configurable_serverapp(root_dir=path)
+    with pytest.raises(TraitError) as error:
+        app.preferred_dir = path_subdir
+
+    assert "No such preferred dir:" in str(error)
+
+
+def test_invalid_preferred_dir_not_root_subdir(tmp_path, 
jp_configurable_serverapp):
+    path = str(tmp_path / 'subdir')
+    os.makedirs(path, exist_ok=True)
+    not_subdir_path = str(tmp_path)
+
+    with pytest.raises(TraitError) as error:
+        app = jp_configurable_serverapp(root_dir=path, 
preferred_dir=not_subdir_path)
+
+    assert "preferred_dir must be equal or a subdir of root_dir:" in str(error)
+
+
+def test_invalid_preferred_dir_not_root_subdir_set(tmp_path, 
jp_configurable_serverapp):
+    path = str(tmp_path / 'subdir')
+    os.makedirs(path, exist_ok=True)
+    not_subdir_path = str(tmp_path)
+
+    app = jp_configurable_serverapp(root_dir=path)
+    with pytest.raises(TraitError) as error:
+        app.preferred_dir = not_subdir_path
+
+    assert "preferred_dir must be equal or a subdir of root_dir:" in str(error)
+
+
+def test_observed_root_dir_updates_preferred_dir(tmp_path, 
jp_configurable_serverapp):
+    path = str(tmp_path)
+    new_path = str(tmp_path / 'subdir')
+    os.makedirs(new_path, exist_ok=True)
+
+    app = jp_configurable_serverapp(root_dir=path, preferred_dir=path)
+    app.root_dir = new_path
+    assert app.preferred_dir == new_path
+
+
+def test_observed_root_dir_does_not_update_preferred_dir(tmp_path, 
jp_configurable_serverapp):
+    path = str(tmp_path)
+    new_path = str(tmp_path.parent)
+    app = jp_configurable_serverapp(root_dir=path, preferred_dir=path)
+    app.root_dir = new_path
+    assert app.preferred_dir == path
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/jupyter_server-1.9.0/jupyter_server/tests/test_utils.py 
new/jupyter_server-1.10.2/jupyter_server/tests/test_utils.py
--- old/jupyter_server-1.9.0/jupyter_server/tests/test_utils.py 2021-06-24 
17:42:30.000000000 +0200
+++ new/jupyter_server-1.10.2/jupyter_server/tests/test_utils.py        
2021-08-02 23:35:30.000000000 +0200
@@ -1,19 +1,25 @@
+from pathlib import Path
+from unittest.mock import patch
+
 import pytest
 
 from traitlets.tests.utils import check_help_all_output
-from jupyter_server.utils import url_escape, url_unescape
+from jupyter_server.utils import (
+    url_escape,
+    url_unescape,
+    is_namespace_package
+)
 
 
 def test_help_output():
     check_help_all_output('jupyter_server')
 
 
-
 @pytest.mark.parametrize(
     'unescaped,escaped',
     [
         (
-            '/this is a test/for spaces/', 
+            '/this is a test/for spaces/',
             '/this%20is%20a%20test/for%20spaces/'
         ),
         (
@@ -37,3 +43,29 @@
     # Test unescaping.
     path = url_unescape(escaped)
     assert path == unescaped
+
+
[email protected](
+    '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()
+
+        assert is_namespace_package('dummy') is None
+        mocked_spec.assert_called_once_with('dummy')
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/jupyter_server-1.9.0/jupyter_server/traittypes.py 
new/jupyter_server-1.10.2/jupyter_server/traittypes.py
--- old/jupyter_server-1.9.0/jupyter_server/traittypes.py       2021-06-24 
17:42:30.000000000 +0200
+++ new/jupyter_server-1.10.2/jupyter_server/traittypes.py      2021-08-02 
23:35:30.000000000 +0200
@@ -50,13 +50,15 @@
         'an object'
         >>> describe("a", type(object))
         'a type'
+
         Definite description:
-        >>> describe("the", object())
-        "the object at '0x10741f1b0'"
+        >>> describe("the", object())  # doctest: +ELLIPSIS
+        "the object at '0x...'"
         >>> describe("the", object)
-        "the type 'object'"
+        'the object object'
         >>> describe("the", type(object))
-        "the type 'type'"
+        'the type type'
+
         Definitely named description:
         >>> describe("the", object(), "I made")
         'the object I made'
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/jupyter_server-1.9.0/jupyter_server/utils.py 
new/jupyter_server-1.10.2/jupyter_server/utils.py
--- old/jupyter_server-1.9.0/jupyter_server/utils.py    2021-06-24 
17:42:30.000000000 +0200
+++ new/jupyter_server-1.10.2/jupyter_server/utils.py   2021-08-02 
23:35:30.000000000 +0200
@@ -3,8 +3,10 @@
 # Copyright (c) Jupyter Development Team.
 # Distributed under the terms of the Modified BSD License.
 
+from _frozen_importlib_external import _NamespacePath
 import asyncio
 import errno
+import importlib.util
 import inspect
 import os
 import socket
@@ -230,6 +232,29 @@
     return wrapped()
 
 
+async def run_sync_in_loop(maybe_async):
+    """Runs a function synchronously whether it is an async function or not.
+
+    If async, runs maybe_async and blocks until it has executed.
+
+    If not async, just returns maybe_async as it is the result of something
+    that has already executed.
+
+    Parameters
+    ----------
+    maybe_async : async or non-async object
+        The object to be executed, if it is async.
+
+    Returns
+    -------
+    result
+        Whatever the async object returns, or the object itself.
+    """
+    if not inspect.isawaitable(maybe_async):
+        return maybe_async
+    return await maybe_async
+
+
 def urlencode_unix_socket_path(socket_path):
     """Encodes a UNIX socket path string from a socket path for the 
`http+unix` URI form."""
     return socket_path.replace('/', '%2F')
@@ -352,3 +377,23 @@
     with _request_for_tornado_client(urlstring) as request:
         response = await AsyncHTTPClient(io_loop).fetch(request)
     return response
+
+
+def is_namespace_package(namespace):
+    """Is the provided namespace a Python Namespace Package (PEP420).
+
+    https://www.python.org/dev/peps/pep-0420/#specification
+
+    Returns `None` if module is not importable.
+
+    """
+    # NOTE: using submodule_search_locations because the loader can be None
+    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
+    return isinstance(spec.submodule_search_locations, _NamespacePath)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/jupyter_server-1.9.0/jupyter_server.egg-info/PKG-INFO 
new/jupyter_server-1.10.2/jupyter_server.egg-info/PKG-INFO
--- old/jupyter_server-1.9.0/jupyter_server.egg-info/PKG-INFO   2021-06-24 
17:45:56.000000000 +0200
+++ new/jupyter_server-1.10.2/jupyter_server.egg-info/PKG-INFO  2021-08-02 
23:35:56.000000000 +0200
@@ -1,6 +1,6 @@
 Metadata-Version: 2.1
 Name: jupyter-server
-Version: 1.9.0
+Version: 1.10.2
 Summary: The backend???i.e. core services, APIs, and REST endpoints???to 
Jupyter web applications.
 Home-page: https://jupyter.org
 Author: Jupyter Development Team
@@ -61,10 +61,7 @@
 
 ### Testing
 
-To test an installed `jupyter_server`, run the following:
-
-    pip install jupyter_server[test]
-    pytest jupyter_server
+See 
[CONTRIBUTING](https://github.com/jupyter-server/jupyter_server/blob/master/CONTRIBUTING.rst#running-tests).
 
 ## Contributing
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/jupyter_server-1.9.0/jupyter_server.egg-info/SOURCES.txt 
new/jupyter_server-1.10.2/jupyter_server.egg-info/SOURCES.txt
--- old/jupyter_server-1.9.0/jupyter_server.egg-info/SOURCES.txt        
2021-06-24 17:45:56.000000000 +0200
+++ new/jupyter_server-1.10.2/jupyter_server.egg-info/SOURCES.txt       
2021-08-02 23:35:56.000000000 +0200
@@ -86,7 +86,6 @@
 examples/simple/simple_ext2/templates/simple_ext2.html
 examples/simple/src/index.ts
 examples/simple/src/tsconfig.json
-examples/simple/tests/conftest.py
 examples/simple/tests/test_handlers.py
 jupyter_server/__init__.py
 jupyter_server/__main__.py
@@ -199,7 +198,6 @@
 jupyter_server/terminal/handlers.py
 jupyter_server/terminal/terminalmanager.py
 jupyter_server/tests/__init__.py
-jupyter_server/tests/conftest.py
 jupyter_server/tests/test_config_manager.py
 jupyter_server/tests/test_files.py
 jupyter_server/tests/test_gateway.py
@@ -235,6 +233,9 @@
 jupyter_server/tests/extension/mockextensions/mockext_sys.py
 jupyter_server/tests/extension/mockextensions/mockext_user.py
 jupyter_server/tests/extension/mockextensions/static/mock.txt
+jupyter_server/tests/namespace-package-test/README.md
+jupyter_server/tests/namespace-package-test/setup.cfg
+jupyter_server/tests/namespace-package-test/test_namespace/test_package/__init__.py
 jupyter_server/tests/nbconvert/__init__.py
 jupyter_server/tests/nbconvert/test_handlers.py
 jupyter_server/tests/services/__init__.py
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/jupyter_server-1.9.0/jupyter_server.egg-info/requires.txt 
new/jupyter_server-1.10.2/jupyter_server.egg-info/requires.txt
--- old/jupyter_server-1.9.0/jupyter_server.egg-info/requires.txt       
2021-06-24 17:45:56.000000000 +0200
+++ new/jupyter_server-1.10.2/jupyter_server.egg-info/requires.txt      
2021-08-02 23:35:56.000000000 +0200
@@ -17,7 +17,7 @@
 
 [test]
 coverage
-pytest
+pytest>=6.0
 pytest-cov
 pytest-mock
 requests
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/jupyter_server-1.9.0/pyproject.toml 
new/jupyter_server-1.10.2/pyproject.toml
--- old/jupyter_server-1.9.0/pyproject.toml     2021-06-24 17:42:45.000000000 
+0200
+++ new/jupyter_server-1.10.2/pyproject.toml    2021-08-02 23:35:45.000000000 
+0200
@@ -6,14 +6,16 @@
 factory = "jupyter_packaging.npm_builder"
 
 [tool.check-manifest]
-ignore = ["tbump.toml", ".*", "*.yml", "package-lock.json", "bootstrap*"]
+ignore = ["tbump.toml", ".*", "*.yml", "package-lock.json", "bootstrap*", 
"conftest.py"]
 
 [tool.pytest.ini_options]
-# Exclude the example tests.
-norecursedirs = "examples/*"
+addopts = "--doctest-modules"
+testpaths = [
+    "jupyter_server"
+]
 
 [tool.tbump.version]
-current = "1.9.0"
+current = "1.10.2"
 regex = '''
   (?P<major>\d+)\.(?P<minor>\d+)\.(?P<patch>\d+)
   ((?P<channel>a|b|rc|.dev)(?P<release>\d+))?
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/jupyter_server-1.9.0/setup.cfg 
new/jupyter_server-1.10.2/setup.cfg
--- old/jupyter_server-1.9.0/setup.cfg  2021-06-24 17:45:56.836275000 +0200
+++ new/jupyter_server-1.10.2/setup.cfg 2021-08-02 23:35:56.407143000 +0200
@@ -45,7 +45,15 @@
        requests-unixsocket
 
 [options.extras_require]
-test = coverage; pytest; pytest-cov; pytest-mock; requests; pytest-tornasync; 
pytest-console-scripts; ipykernel
+test = 
+       coverage
+       pytest>=6.0
+       pytest-cov
+       pytest-mock
+       requests
+       pytest-tornasync
+       pytest-console-scripts
+       ipykernel
 
 [options.entry_points]
 console_scripts = 

Reply via email to