Skip to content

Commit affe09b

Browse files
committed
Sync with main
2 parents cf37b55 + b5cb375 commit affe09b

File tree

5 files changed

+69
-13
lines changed

5 files changed

+69
-13
lines changed

.github/workflows/downstream.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ jobs:
5050
- uses: jupyterlab/maintainer-tools/.github/actions/downstream-test@v1
5151
with:
5252
package_name: nbconvert
53-
package_spec: pip install -e ".[test]"
53+
package_spec: -e ".[test]"
5454

5555
jupyter_server:
5656
runs-on: ubuntu-latest

.github/workflows/main.yml

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,7 @@ jobs:
7979
fail_under: 78
8080

8181
docs:
82-
runs-on: windows-latest
82+
runs-on: ubuntu-latest
8383
steps:
8484
- uses: actions/checkout@v4
8585
- uses: jupyterlab/maintainer-tools/.github/actions/base-setup@v1
@@ -89,8 +89,9 @@ jobs:
8989
# If this fails run `hatch run docs:api` locally
9090
# and commit.
9191
git status --porcelain
92-
git status -s | grep "A" && exit 1
93-
git status -s | grep "M" && exit 1
92+
if git status -s | grep "^\s*[AM]"; then
93+
exit 1
94+
fi
9495
echo "API docs done"
9596
- run: hatch run docs:build
9697

jupyter_client/jsonutil.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
from datetime import datetime
1212
from typing import Any, Optional, Union
1313

14-
from dateutil.parser import parse as _dateutil_parse
14+
from dateutil.parser import isoparse as _dateutil_parse
1515
from dateutil.tz import tzlocal
1616

1717
next_attr_name = "__next__" # Not sure what downstream library uses this, but left it to be safe
@@ -28,7 +28,7 @@
2828

2929
# holy crap, strptime is not threadsafe.
3030
# Calling it once at import seems to help.
31-
datetime.strptime("1", "%d") # noqa
31+
datetime.strptime("2000-01-01", "%Y-%m-%d") # noqa
3232

3333
# -----------------------------------------------------------------------------
3434
# Classes and functions

jupyter_client/localinterfaces.py

Lines changed: 38 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -151,6 +151,35 @@ def _load_ips_ipconfig() -> None:
151151
_populate_from_list(addrs)
152152

153153

154+
def _load_ips_psutil() -> None:
155+
"""load ip addresses with netifaces"""
156+
import psutil
157+
158+
global LOCALHOST
159+
local_ips = []
160+
public_ips = []
161+
162+
# dict of iface_name: address_list, eg
163+
# {"lo": [snicaddr(family=<AddressFamily.AF_INET>, address="127.0.0.1",
164+
# ...), snicaddr(family=<AddressFamily.AF_INET6>, ...)]}
165+
for iface, ifaddresses in psutil.net_if_addrs().items():
166+
for address_data in ifaddresses:
167+
if address_data.family == socket.AF_INET:
168+
addr = address_data.address
169+
if not (iface.startswith("lo") or addr.startswith("127.")):
170+
public_ips.append(addr)
171+
elif not LOCALHOST:
172+
LOCALHOST = addr
173+
local_ips.append(addr)
174+
if not LOCALHOST:
175+
# we never found a loopback interface (can this ever happen?), assume common default
176+
LOCALHOST = "127.0.0.1"
177+
local_ips.insert(0, LOCALHOST)
178+
local_ips.extend(["0.0.0.0", ""]) # noqa
179+
LOCAL_IPS[:] = _uniq_stable(local_ips)
180+
PUBLIC_IPS[:] = _uniq_stable(public_ips)
181+
182+
154183
def _load_ips_netifaces() -> None:
155184
"""load ip addresses with netifaces"""
156185
import netifaces # type: ignore[import-not-found]
@@ -227,13 +256,20 @@ def _load_ips(suppress_exceptions: bool = True) -> None:
227256
228257
This function will only ever be called once.
229258
230-
It will use netifaces to do it quickly if available.
259+
If will use psutil to do it quickly if available.
260+
If not, it will use netifaces to do it quickly if available.
231261
Then it will fallback on parsing the output of ifconfig / ip addr / ipconfig, as appropriate.
232262
Finally, it will fallback on socket.gethostbyname_ex, which can be slow.
233263
"""
234264

235265
try:
236-
# first priority, use netifaces
266+
# first priority, use psutil
267+
try:
268+
return _load_ips_psutil()
269+
except ImportError:
270+
pass
271+
272+
# second priority, use netifaces
237273
try:
238274
return _load_ips_netifaces()
239275
except ImportError:

tests/signalkernel.py

Lines changed: 24 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ async def shutdown_request(self, stream, ident, parent):
3030
if os.environ.get("NO_SHUTDOWN_REPLY") != "1":
3131
await super().shutdown_request(stream, ident, parent)
3232

33-
def do_execute(
33+
async def do_execute(
3434
self, code, silent, store_history=True, user_expressions=None, allow_stdin=False
3535
):
3636
code = code.strip()
@@ -47,12 +47,31 @@ def do_execute(
4747
elif code == "env":
4848
reply["user_expressions"]["env"] = os.getenv("TEST_VARS", "")
4949
elif code == "sleep":
50-
try:
51-
time.sleep(10)
52-
except KeyboardInterrupt:
53-
reply["user_expressions"]["interrupted"] = True
50+
import ipykernel
51+
52+
if ipykernel.version_info < (7, 0):
53+
# ipykernel before anyio.
54+
try:
55+
time.sleep(10)
56+
except KeyboardInterrupt:
57+
reply["user_expressions"]["interrupted"] = True
58+
else:
59+
reply["user_expressions"]["interrupted"] = False
5460
else:
61+
# ipykernel after anyio.
62+
from anyio import create_task_group, open_signal_receiver, sleep
63+
64+
async def signal_handler(cancel_scope, reply):
65+
with open_signal_receiver(signal.SIGINT) as signals:
66+
async for _ in signals:
67+
reply["user_expressions"]["interrupted"] = True
68+
cancel_scope.cancel()
69+
return
70+
5571
reply["user_expressions"]["interrupted"] = False
72+
async with create_task_group() as tg:
73+
tg.start_soon(signal_handler, tg.cancel_scope, reply)
74+
tg.start_soon(sleep, 10)
5675
else:
5776
reply["status"] = "error"
5877
reply["ename"] = "Error"

0 commit comments

Comments
 (0)