Click not working in jupyter - python

just found the following . running via notebook
import
.echo("test")
Output:
/home/user/anaconda3/envs/p36/lib/python3.6/site-packages//utils.py in echo(message, file, nl, err, color)
257
258 if message:
--> 259 file.write(message)
260 file.flush()
261
UnsupportedOperation: not writable
Has someone seen this before and knows how to work around? I have to use a lib via that uses . so not is not possible.
Update:
This commit to a jupyter branch of click solves the issue:
https://github.com/elgalu/click/commit/1cb7aaba8c9dd6ec760d3e7e414d0b4e5f788543#diff-d17772ee4f65879b69a53dbc4b3d42bd

I think that Jupyter hijacks and locks the STDOUT/STDERR (at least the one click is trying to use) and if you don't provide a stream to click.echo() it will attempt writing to the STDOUT/STDERR, hence the error.
You can work around it by passing an output stream like STDOUT yourself:
import click
import sys
click.echo("test", sys.stdout)
# test

In my case using Python 3 I wanted to preserve the click styling on my message both in the Jupyter notebook and when the code was run in the terminal. I handled it this way:
from io import UnsupportedOperation
import click
item = 'Your Name'
message = click.style('"{}"'.format(item), fg='red', bold=True)
try:
click.echo(message)
except UnsupportedOperation as err:
print('Error: "{}"'.format(err))
print(message)
The color is preserved in the notebook:

Related

Python macOS builds run from Terminal but crash on Finder launch

My py2app build displays an error message with a Terminate button when the app is launched with a double-click. However, if I open it directly in Terminal then it works perfectly well. All of the following Terminal commands launch the app successfully:
# .MyApp.app/Contents/MacOS/MyApp
# open MyApp.app
# open -a MyApp.app
I've read several posts about similar py2app errors but I can't figure out what could be the issue here. The problem seems to have been around for at least four years, is not specific to py2app and seems related to a general issue with Python on macOS. Users of pyinstaller are reporting the exact same problem with no obvious solution in sight AFAIK.
My setup.py file:
from setuptools import setup
APP = ['myapp.py']
DATA_FILES = [('img', ['img/myapp-logo.png']), ('data', ['data/data.yml'])]
OPTIONS = {'iconfile': 'icon.icns'}
setup(
app=APP,
name='My App',
data_files=DATA_FILES,
options={'py2app': OPTIONS},
setup_requires=['py2app'],
)
The macOS Console shows the following message in system.log: com.apple.xpc.launchd[1] (org.pythonmac.unspecified.MyApp.4952[16783]): Service exited with abnormal code: 255
Suspect 1: file access?
This thread on the pyinstaller GitHub issues page has multiple people reporting the same error with no clear fix. This proposed solution seems related to the working directory issues described in the pyinstaller thread but doesn't solve the problem on my system.
My app is only reading a single yml file from its working directory and doesn't write anything to disk. It is nothing more than a simple file access statement:
file = os.path.realpath('data/data.yml')
with open(file) as f:
# etc
In Catalina, I've added the app to the list of apps allowed Full Disk Access in the Security Preferences but this doesn't solve the issue either (it would have been surprising if it did, though, since the open command works as mentioned above).
Suspect 2: Tkinter?
This thread on pyinstaller GitHub suggests the problem might be related to a Tkinter version. The proposed solution seems to have fixed the issue for some users. However, on my end I have a working app bundle from an earlier version, before adding the open file statement, which launches just fine when double clicked.
Suspect 1 vs 2
I have forked my code into two branches, one completely removing references to Tkinter, the other removing file access in favour of initialisation via variables in the code. The crash does not happen in the second case. This would seem to rule out Tkinter as the source of the problem, although weirdly enough a hack to Tkinter has fixed it for other users.
macOS versions
I've tested both bundle versions (working and not working) on both Catalina 10.15.6 and El Capitan 10.11.6 and the behaviour is identical.
The Firing of the Death Sentinel
Here's what the Console log looks like after launching the app normally through the Finder:
default 21:26:13.836380+0200 runningboardd Acquiring assertion targeting executable<MyPythonApplication(501)> from originator [daemon<com.apple.coreservices.launchservicesd>:165] with description <RBSAssertionDescriptor; frontmost:27280; ID: 391-165-34219; target: 27280> attributes = {
<RBSDomainAttribute: 0x7f9f1a712910; domain: com.apple.launchservicesd; name: RoleUserInteractiveFocal; sourceEnvironment: 0x0>;
}
default 21:26:13.836649+0200 runningboardd Assertion 391-165-34219 (target:executable<MyPythonApplication(501)>) will be created as active
default 21:26:13.838258+0200 runningboardd [executable<MyPythonApplication(501)>:27280] Ignoring jetsam update because this process is not memory-managed
default 21:26:13.839703+0200 runningboardd [executable<MyPythonApplication(501)>:27280] Set darwin role to: UserInteractiveFocal
default 21:26:13.840634+0200 runningboardd [executable<MyPythonApplication(501)>:27280] Ignoring GPU update because this process is not GPU managed
default 21:26:13.840912+0200 runningboardd Finished acquiring assertion 391-165-34219 (target:executable<MyPythonApplication(501)>)
default 21:26:15.166436+0200 hidd Connection removed: IOHIDEventSystemConnection uuid:B1D40AB3-FD55-455C-9E1B-2E4C4C6E4982 pid:27280 process:MyPythonApplication type:Passive entitlements:0x0 caller:HIToolbox: ___GetIOHIDEventSystemClient_block_invoke + 26 attributes:(null) state:0x1 events:0 mask:0x0
default 21:26:15.171128+0200 runningboardd [executable<MyPythonApplication(501)>:27280] Death sentinel fired!
default 21:26:15.174315+0200 runningboardd Invalidating assertion 391-165-34219 (target:executable<MyPythonApplication(501)>) from originator 165
default 21:26:15.176832+0200 loginwindow -[PersistentAppsSupport applicationQuit:] | for app:MyPythonApplication, _appTrackingState = 2
default 21:26:15.176856+0200 loginwindow -[PersistentAppsSupport applicationQuit:] | App: MyPythonApplication, quit, updating active tracking timer
default 21:26:15.179589+0200 runningboardd Invalidating assertion 391-165-34209 (target:executable<MyPythonApplication(501)>) from originator 165
default 21:26:15.281529+0200 runningboardd Removing process: [executable<MyPythonApplication(501)>:27280]
default 21:26:15.282124+0200 runningboardd Removing assertions for terminated process: [executable<MyPythonApplication(501)>:27280]
error 21:26:15.292603+0200 runningboardd RBSStateCapture remove item called for untracked item 391-165-34209 (target:executable<MyPythonApplication(501)>)
error 21:26:15.292622+0200 runningboardd RBSStateCapture remove item called for untracked item 391-165-34219 (target:executable<MyPythonApplication(501)>)
Apparently the error is mentioned in this line, in case someone can make anything out of it:
hidd Connection removed: IOHIDEventSystemConnection uuid:foo pid:bar process:MyPythonApplication type:Passive entitlements:0x0 caller:HIToolbox: ___GetIOHIDEventSystemClient_block_invoke + 26 attributes:(null) state:0x1 events:0 mask:0x0
This is immediately followed by RunningBoard firing the Death Sentinel, whatever this entity might be. The system is described in this article by Howard Oakley but it is way beyond my level of expertise.
Some more info gathered by Ulbow confirms an error with the highly informative message "MacOS error: -67062":
com.apple.runningboard 4941060 391 Received message from [daemon<com.apple.coreservices.launchservicesd>:165] (euid 0): acquireAssertionWithDescriptor:error:
com.apple.launchservices 4940599 802 OSStatus _LSLaunch(LSContext *, FSNode *, LSLaunchFlags, void *, CFArrayRef, const AppleEvent *, const AEDescList *, CFArrayRef, CFDictionaryRef, LSBundleID, const audit_token_t *, const _LSOpen2Options *, ProcessSerialNumber *, Boolean *, NSError **): launching '<private>' result=0
com.apple.securityd 9807 904 MacOS error: -67062
com.apple.runningboard 4941063 391 Received message from [daemon<com.apple.coreservices.launchservicesd>:165] (euid 0): acquireAssertionWithDescriptor:error:
com.apple.sharedfilelist 4940599 802 -[SFLGenericList _insertItem:atIndex:error:]_block_invoke com.apple.LSSharedFileList.RecentApplications
com.apple.securityd 4940933 530 UNIX error exception: 8
4941117 27642 MyPythonApplication Error
com.apple.securityd 4941064 165 UNIX error exception: 22
com.apple.securityd 4941064 165 MacOS error: -67062
com.apple.runningboard 4941002 391 Received message from [daemon<com.apple.coreservices.launchservicesd>:165] (euid 0): acquireAssertionWithDescriptor:error:
com.apple.securityd 4941161 198 MacOS error: -67062
com.apple.TCC 4941161 198 Failed to copy signing info for 27642, responsible for file:///Users/me/Files/Docs/Code/Python/MyPythonApplication/dist/MyPythonApplication.app/Contents/MacOS/Pandemic%20Deck%20Tracker: #-67062: Error Domain=NSOSStatusErrorDomain Code=-67062 "(null)"
com.apple.securityd 4941161 198 MacOS error: -67062
com.apple.securityd 4941161 198 MacOS error: -67062
com.apple.launchservices 4941064 165 CLIENT: 0x7fcec00b33b0/27642 Received XPC_ERROR on connection from our client, so invalidating it and our connection.
com.apple.appleevents 4941120 462 CONNECTION: peer=? peer-pid=27642 got event (Error "Connection invalid")
com.apple.appleevents 4941120 462 CONNECTION: Recevied XPC_ERROR on a connection from 27642, a client of ours, so unregistering any application with that pid.
com.apple.appleevents 4941120 462 CONNECTION: releasing app App:"MyPythonApplication"/"MyPythonApplication"/"org.pythonmac.unspecified.MyPythonApplication" 27642/0x0:0x308d08a ????1010 sess=100020 because we received error on its connection.
com.apple.runningboard 4941002 391 Received message from [daemon<com.apple.coreservices.launchservicesd>:165] (euid 0): acquireAssertionWithDescriptor:error:
Several exchanges on recent tests, trying to pinpoint the cause for the bug, can be found on this GitHub issues page, but it still remains a mystery (and a very bizarre one, too).
An Apple engineer should really take a look at this.
A pretty thorough investigation of this issue has been conducted on this GitHub issue page by the maintainers of pyinstaller. It turns out that if a Python script accesses resources on a macOS file system using the built-in Python os module, the app bundle crashes. The bundle does not crash if the script is run directly from a Terminal command.
The proposed solution is to check if the script is running on macOS and, in that case, use AppKit to open the file. This requires installing the pyobjc module but otherwise it is not a major hassle.
Instead of doing this:
import os
file = os.path.realpath('path/somefile.ext')
with open(file) as f:
# ...
Do this:
import os
import platform
def get_path(filename):
name = os.path.splitext(filename)[0]
ext = os.path.splitext(filename)[1]
if platform.system() == "Darwin":
from AppKit import NSBundle
file = NSBundle.mainBundle().pathForResource_ofType_(name, ext)
return file or os.path.realpath(filename)
else:
return os.path.realpath(filename)
file = get_path('path/somefile.ext')
with open(file) as f:
# ...
I can confirm this works on macOS Catalina with pyinstaller. I haven't had the chance to test this on .exe builds for Windows.
After hours of digging I finally found an answer! Even after every Step I count gain the right /Contents/Resources path.
First I had to get this working:
from AppKit import NSBundle
file = NSBundle.mainBundle().pathForResource_ofType_(name, ext)
For my Python App, finding the "AppKit" module I had to install
pip install pyobjc
But even then, the path for
NSBundle.mainBundle().pathForResource_ofType_(name, ext)
was strange. It was a path in "/private/var/" where I don't have Write Access
The final answer was to run
xattr -d com.apple.quarantine /Applications/YouPythonApp.app/
then I finally got the right path
/Applications/YouPythonApp.app/Contents/Resources/
where I also had write access and now I can save my .yaml file
with open(file, "w") as f:
yaml=YAML()
yaml.default_flow_style = False
yaml.dump(config, f)

Windows10 Python Program Disappear

I wrote a py file called simplifier. Usually when you double click the py file, Windows should give you both a console and the program. Just like the picture below.
However, I clicked my py file and the console showed up and disappeared in a flash and the program did not show up at all. Usually this means there should be some bugs in my code. So I opened the py file in IDLE and ran it, expecting the shell to give me an error. However, it did not report anything. I don't know how to locate the error. The file used to work well and it just started to behave like that. I tested putting "input('')" only in a py file and it worked as expected. And some of my programs also work well, but the rest doesn't.
Could the bug come from my imported modules? I import some module written by myself (ez and eztk). I checked those functions. They are ok.
Here is the code of simplifier.
from tkinter import *
from tkinter import messagebox
from eztk import *
import ez
root=Tk()
root['bg']='MintCream'
t1=Text(root)
def newline():
t=gettxt(t1)
new=''
for i,ch in enumerate(t):
if ch=='\n' and t[i+1:i+3]!='- ':
new+=' '
else:
new+=ch
inst2(new)
w0=Label(root,text='Input:↑')
w1=Button(root,text="\\n",command=newline)
def brackets():
t=gettxt(t1)
new=''
stop=0
d={'[':']','(':')','{':'}',0:None}
for ch in t:
if ch in d:
stop=ch
elif ch==d[stop]:
stop=0
elif not stop:
new+=ch
inst2(new)
w2=Button(root,text='([{}])',command=brackets)
def linecount(event):
count=lambda t:t.count('\n')+(t[-1]!='\n') if t else 0
up=count(gettxt(t1))
down=count(gettxt(t2))
w3['text']=f'LineCount:↑{up}↓{down}'
root.bind('<KeyPress>', linecount)
w3=Label(root,text='LineCount')
def clear():
deltxt(t1)
deltxt(t2)
w4=Button(root,text='Clear',command=clear)
t2=Text(root)
ws=[w0,w1,w2,w3,w4]
t1.grid(row=0,column=0,columnspan=len(ws))
for i,w in enumerate(ws):
w.configure(relief=FLAT,bg='SeaGreen1')
w.grid(row=1,column=i,sticky=NS)
t2.grid(row=2,column=0,columnspan=len(ws))
def inst2(text):
deltxt(t2)
instxt(t2,text)
linecount('<KeyPress>')
try: ez.cpc(text)
except UnicodeError: messagebox.showerror('Error','You need to copy it to your clipboard manually.')
root.mainloop()
These are the functions used in the imported modules:
def copyToClipboard(text):
win32clipboard.OpenClipboard()
win32clipboard.EmptyClipboard()
win32clipboard.SetClipboardText(text)
win32clipboard.CloseClipboard()
## abbreviation
cpc=copyToClipboard
def gettxt(text):
return text.get(1.0,'end').strip()
def deltxt(text):
text.delete(1.0,'end')
def instxt(textwidget,text):
textwidget.insert(1.0,text)
--- Update ---
I just tried copy the 4 functions to the simplifier.py instead of importing them, it worked. However, I also tried importing either of them and both cases failed. And there is still no error after I ran them in the python shell. Actually the bottom 3 functions are the only code in my eztk module. And it still doesn't work as expected when I import eztk alone, which means there shouldn't be any problems with importing.

Save an IPython notebook programmatically from within itself?

I would like to leave an IPython notebook running to do some computation + show some visuals.
Once the IPython notebook has finished, I want the last cell in the IPython notebook to programmatically save the IPython notebook. Then I want to copy the notebook (with all output) to another directory to keep a record of results.
The copying bit I can code up easily, but I am not sure how to get an IPython notebook to programatically save itself? Is this possible? Thanks in advance!
I am taking #Taar's comment and making it an actual answer since it worked for the original person who asked the question and for myself.
from IPython.display import display, Javascript
display(Javascript('IPython.notebook.save_checkpoint();'))
This will create checkpoints - same thing as CTRL-s.
Note: in Jupyter, CTRL-s triggers an async process and the file save is actually completed only a few seconds later. If you want a blocking save operation in a notebook, use this little function (file_path is the path to the notebook file):
import time
from IPython.display import display, Javascript
import hashlib
def save_notebook(file_path):
start_md5 = hashlib.md5(open(file_path,'rb').read()).hexdigest()
display(Javascript('IPython.notebook.save_checkpoint();'))
current_md5 = start_md5
while start_md5 == current_md5:
time.sleep(1)
current_md5 = hashlib.md5(open(file_path,'rb').read()).hexdigest()
The ipython magic command %notebook will help you here. It is shown on this page (search for %notebook).
To save your current notebook history to the file "foo.ipynb" just enter:
%notebook -e foo.ipynb
At the point you want it to happen
In [1]:
import os
import time
import pathlib
from IPython.display import display, HTML, Javascript
html = '''
<script>
IPython.notebook.kernel.execute(
'filename = "' + IPython.notebook.notebook_name + '"'
)
</script>
'''
def save_notebook():
file_path = str(
pathlib.Path(
os.getcwd(),
filename
)
)
start_mtime = os.path.getmtime(file_path)
display(
Javascript(
'IPython.notebook.save_checkpoint();'
)
)
current_mtime = start_mtime
while start_mtime == current_mtime:
time.sleep(1)
current_mtime = os.path.getmtime(file_path)
display(HTML(html))
In [2]:
save_notebook()
This works when you have not made any new changes to the notebook.

win32gui.SetActiveWindow() ERROR : The specified procedure could not be found

I get the active window like so:
window = win32gui.GetForegroundWindow()
which is an Int, say 1053634.
And afterwards I try to set the foreground window back to the specified window:
win32gui.SetForegroundWindow(window)
And I get this error:
win32gui.SetForegroundWindow(window)
error: (127, 'SetForegroundWindow', 'The specified procedure could not be found.')
Sometimes when I do this in the interpreter, I get this error:
win32gui.SetForegroundWindow(1053634)
error: (0, 'SetForegroundWindow', 'No error message is available')
What do you think is the problem?
Thanks!
My program works fine on my desktop with Windows 7, but when I use my laptop with Windows Vista
(even with UAC off), I get the error:
pywintypes.error: (0, 'SetForegroundWindow', 'No error message is available')
The program will flash in the taskbar, but no characters are sent.
I even tried sending 'notepad' SetForegroundWindow and get the same error.
Here is a link with a workaround that combines threads to get the computer to think they work together: http://www.shloemi.com/2012/09/solved-setforegroundwindow-win32-api-not-always-works/
This article has more information about the problem.
UPDATE: I'm sorry that link goes to a C program. I researched some more and found out it will let you SetForegroundWindow, if you send an alt key first.
For example:
import win32gui, win32com.client
shell = win32com.client.Dispatch("WScript.Shell")
shell.SendKeys('%')
win32gui.SetForegroundWindow(window)
This also worked for me
import win32gui, win32com.client
shell = win32com.client.Dispatch("WScript.Shell")
shell.SendKeys('%')
win32gui.SetForegroundWindow(window.hwnd)
What worked it me...
import win32gui, win32com.client
shell = win32com.client.Dispatch("WScript.Shell")
input("Press Enter")
shell.SendKeys(' ') #Undocks my focus from Python IDLE
win32gui.SetForegroundWindow(window) #It works!
shell.SendKeys('%')

How do I get the current IPython / Jupyter Notebook name

I am trying to obtain the current NoteBook name when running the IPython notebook. I know I can see it at the top of the notebook. What I am after something like
currentNotebook = IPython.foo.bar.notebookname()
I need to get the name in a variable.
adding to previous answers,
to get the notebook name run the following in a cell:
%%javascript
IPython.notebook.kernel.execute('nb_name = "' + IPython.notebook.notebook_name + '"')
this gets you the file name in nb_name
then to get the full path you may use the following in a separate cell:
import os
nb_full_path = os.path.join(os.getcwd(), nb_name)
I have the following which works with IPython 2.0. I observed that the name of the notebook is stored as the value of the attribute 'data-notebook-name' in the <body> tag of the page. Thus the idea is first to ask Javascript to retrieve the attribute --javascripts can be invoked from a codecell thanks to the %%javascript magic. Then it is possible to access to the Javascript variable through a call to the Python Kernel, with a command which sets a Python variable. Since this last variable is known from the kernel, it can be accessed in other cells as well.
%%javascript
var kernel = IPython.notebook.kernel;
var body = document.body,
attribs = body.attributes;
var command = "theNotebook = " + "'"+attribs['data-notebook-name'].value+"'";
kernel.execute(command);
From a Python code cell
print(theNotebook)
Out[ ]: HowToGetTheNameOfTheNoteBook.ipynb
A defect in this solution is that when one changes the title (name) of a notebook, then this name seems to not be updated immediately (there is probably some kind of cache) and it is necessary to reload the notebook to get access to the new name.
[Edit] On reflection, a more efficient solution is to look for the input field for notebook's name instead of the <body> tag. Looking into the source, it appears that this field has id "notebook_name". It is then possible to catch this value by a document.getElementById() and then follow the same approach as above. The code becomes, still using the javascript magic
%%javascript
var kernel = IPython.notebook.kernel;
var thename = window.document.getElementById("notebook_name").innerHTML;
var command = "theNotebook = " + "'"+thename+"'";
kernel.execute(command);
Then, from a ipython cell,
In [11]: print(theNotebook)
Out [11]: HowToGetTheNameOfTheNoteBookSolBis
Contrary to the first solution, modifications of notebook's name are updated immediately and there is no need to refresh the notebook.
As already mentioned you probably aren't really supposed to be able to do this, but I did find a way. It's a flaming hack though so don't rely on this at all:
import json
import os
import urllib2
import IPython
from IPython.lib import kernel
connection_file_path = kernel.get_connection_file()
connection_file = os.path.basename(connection_file_path)
kernel_id = connection_file.split('-', 1)[1].split('.')[0]
# Updated answer with semi-solutions for both IPython 2.x and IPython < 2.x
if IPython.version_info[0] < 2:
## Not sure if it's even possible to get the port for the
## notebook app; so just using the default...
notebooks = json.load(urllib2.urlopen('http://127.0.0.1:8888/notebooks'))
for nb in notebooks:
if nb['kernel_id'] == kernel_id:
print nb['name']
break
else:
sessions = json.load(urllib2.urlopen('http://127.0.0.1:8888/api/sessions'))
for sess in sessions:
if sess['kernel']['id'] == kernel_id:
print sess['notebook']['name']
break
I updated my answer to include a solution that "works" in IPython 2.0 at least with a simple test. It probably isn't guaranteed to give the correct answer if there are multiple notebooks connected to the same kernel, etc.
It seems I cannot comment, so I have to post this as an answer.
The accepted solution by #iguananaut and the update by #mbdevpl appear not to be working with recent versions of the Notebook.
I fixed it as shown below. I checked it on Python v3.6.1 + Notebook v5.0.0 and on Python v3.6.5 and Notebook v5.5.0.
import jupyterlab
if jupyterlab.__version__.split(".")[0] == "3":
from jupyter_server import serverapp as app
key_srv_directory = 'root_dir'
else :
from notebook import notebookapp as app
key_srv_directory = 'notebook_dir'
import urllib
import json
import os
import ipykernel
def notebook_path(key_srv_directory, ):
"""Returns the absolute path of the Notebook or None if it cannot be determined
NOTE: works only when the security is token-based or there is also no password
"""
connection_file = os.path.basename(ipykernel.get_connection_file())
kernel_id = connection_file.split('-', 1)[1].split('.')[0]
for srv in app.list_running_servers():
try:
if srv['token']=='' and not srv['password']: # No token and no password, ahem...
req = urllib.request.urlopen(srv['url']+'api/sessions')
else:
req = urllib.request.urlopen(srv['url']+'api/sessions?token='+srv['token'])
sessions = json.load(req)
for sess in sessions:
if sess['kernel']['id'] == kernel_id:
return os.path.join(srv[key_srv_directory],sess['notebook']['path'])
except:
pass # There may be stale entries in the runtime directory
return None
As stated in the docstring, this works only when either there is no authentication or the authentication is token-based.
Note that, as also reported by others, the Javascript-based method does not seem to work when executing a "Run all cells" (but works when executing cells "manually"), which was a deal-breaker for me.
The ipyparams package can do this pretty easily.
import ipyparams
currentNotebook = ipyparams.notebook_name
On Jupyter 3.0 the following works. Here I'm showing the entire path on the Jupyter server, not just the notebook name:
To store the NOTEBOOK_FULL_PATH on the current notebook front end:
%%javascript
var nb = IPython.notebook;
var kernel = IPython.notebook.kernel;
var command = "NOTEBOOK_FULL_PATH = '" + nb.base_url + nb.notebook_path + "'";
kernel.execute(command);
To then display it:
print("NOTEBOOK_FULL_PATH:\n", NOTEBOOK_FULL_PATH)
Running the first Javascript cell produces no output.
Running the second Python cell produces something like:
NOTEBOOK_FULL_PATH:
/user/zeph/GetNotebookName.ipynb
Yet another hacky solution since my notebook server can change. Basically you print a random string, save it and then search for a file containing that string in the working directory. The while is needed because save_checkpoint is asynchronous.
from time import sleep
from IPython.display import display, Javascript
import subprocess
import os
import uuid
def get_notebook_path_and_save():
magic = str(uuid.uuid1()).replace('-', '')
print(magic)
# saves it (ctrl+S)
display(Javascript('IPython.notebook.save_checkpoint();'))
nb_name = None
while nb_name is None:
try:
sleep(0.1)
nb_name = subprocess.check_output(f'grep -l {magic} *.ipynb', shell=True).decode().strip()
except:
pass
return os.path.join(os.getcwd(), nb_name)
There is no real way yet to do this in Jupyterlab. But there is an official way that's now under active discussion/development as of August 2021:
https://github.com/jupyter/jupyter_client/pull/656
In the meantime, hitting the api/sessions REST endpoint of jupyter_server seems like the best bet. Here's a cleaned-up version of that approach:
from jupyter_server import serverapp
from jupyter_server.utils import url_path_join
from pathlib import Path
import re
import requests
kernelIdRegex = re.compile(r"(?<=kernel-)[\w\d\-]+(?=\.json)")
def getNotebookPath():
kernelId = kernelIdRegex.search(get_ipython().config["IPKernelApp"]["connection_file"])[0]
for jupServ in serverapp.list_running_servers():
for session in requests.get(url_path_join(jupServ["url"], "api/sessions"), params={"token": jupServ["token"]}).json():
if kernelId == session["kernel"]["id"]:
return Path(jupServ["root_dir"]) / session["notebook"]['path']
Tested working with
python==3.9
jupyter_server==1.8.0
jupyterlab==4.0.0a7
Modifying #jfb method, gives the function below which worked fine on ipykernel-5.3.4.
def getNotebookName():
display(Javascript('IPython.notebook.kernel.execute("NotebookName = " + "\'"+window.document.getElementById("notebook_name").innerHTML+"\'");'))
try:
_ = type(NotebookName)
return NotebookName
except:
return None
Note that the display javascript will take some time to reach the browser, and it will take some time to execute the JS and get back to the kernel. I know it may sound stupid, but it's better to run the function in two cells, like this:
nb_name = getNotebookName()
and in the following cell:
for i in range(10):
nb_name = getNotebookName()
if nb_name is not None:
break
However, if you don't need to define a function, the wise method is to run display(Javascript(..)) in one cell, and check the notebook name in another cell. In this way, the browser has enough time to execute the code and return the notebook name.
If you don't mind to use a library, the most robust way is:
import ipynbname
nb_name = ipynbname.name()
If you are using Visual Studio Code:
import IPython ; IPython.extract_module_locals()[1]['__vsc_ipynb_file__']
Assuming you have the Jupyter Notebook server's host, port, and authentication token, this should work for you. It's based off of this answer.
import os
import json
import posixpath
import subprocess
import urllib.request
import psutil
def get_notebook_path(host, port, token):
process_id = os.getpid();
notebooks = get_running_notebooks(host, port, token)
for notebook in notebooks:
if process_id in notebook['process_ids']:
return notebook['path']
def get_running_notebooks(host, port, token):
sessions_url = posixpath.join('http://%s:%d' % (host, port), 'api', 'sessions')
sessions_url += f'?token={token}'
response = urllib.request.urlopen(sessions_url).read()
res = json.loads(response)
notebooks = [{'kernel_id': notebook['kernel']['id'],
'path': notebook['notebook']['path'],
'process_ids': get_process_ids(notebook['kernel']['id'])} for notebook in res]
return notebooks
def get_process_ids(name):
child = subprocess.Popen(['pgrep', '-f', name], stdout=subprocess.PIPE, shell=False)
response = child.communicate()[0]
return [int(pid) for pid in response.split()]
Example usage:
get_notebook_path('127.0.0.1', 17004, '344eb91bee5742a8501cc8ee84043d0af07d42e7135bed90')
To realize why you can't get notebook name using these JS-based solutions, run this code and notice the delay it takes for the message box to appear after python has finished execution of the cell / entire notebook:
%%javascript
function sayHello() {
alert('Hello world!');
}
setTimeout(sayHello, 1000);
More info
Javascript calls are async and hence not guaranteed to complete before python starts running another cell containing the code expecting this notebook name variable to be already created... resulting in NameError when trying to access non-existing variables that should contain notebook name.
I suspect some upvotes on this page became locked before voters could discover that all %%javascript-based solutions ultimately don't work... when the producer and consumer notebook cells are executed together (or in a quick succession).
All Json based solutions fail if we execute more than one cell at a time
because the result will not be ready until after the end of the execution
(its not a matter of using sleep or waiting any time, check it yourself but remember to restart kernel and run all every test)
Based on previous solutions, this avoids using the %% magic in case you need to put it in the middle of some other code:
from IPython.display import display, Javascript
# can have comments here :)
js_cmd = 'IPython.notebook.kernel.execute(\'nb_name = "\' + IPython.notebook.notebook_name + \'"\')'
display(Javascript(js_cmd))
For python 3, the following based on the answer by #Iguananaut and updated for latest python and possibly multiple servers will work:
import os
import json
try:
from urllib2 import urlopen
except:
from urllib.request import urlopen
import ipykernel
connection_file_path = ipykernel.get_connection_file()
connection_file = os.path.basename(connection_file_path)
kernel_id = connection_file.split('-', 1)[1].split('.')[0]
running_servers = !jupyter notebook list
running_servers = [s.split('::')[0].strip() for s in running_servers[1:]]
nb_name = '???'
for serv in running_servers:
uri_parts = serv.split('?')
uri_parts[0] += 'api/sessions'
sessions = json.load(urlopen('?'.join(uri_parts)))
for sess in sessions:
if sess['kernel']['id'] == kernel_id:
nb_name = os.path.basename(sess['notebook']['path'])
break
if nb_name != '???':
break
print (f'[{nb_name}]')
just use ipynbname , which is practical
import ipynbname
nb_fname = ipynbname.name()
nb_path = ipynbname.path()
print(f"{nb_fname=}")
print(f"{nb_path=}")
I found this in https://stackoverflow.com/a/65907473/15497427

Categories

Resources