Skip to content

Commit 387ead8

Browse files
committed
Added async functionality to non-git archives
This includes moving most of the functions in plugin_helper.py to async generators. The GitSyncView also opens the console view whenever anything is written to it -- error or just progress output.
1 parent 10385bb commit 387ead8

22 files changed

+707
-320
lines changed

MANIFEST.in

-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
include *.md
22
include LICENSE
33
include setup.cfg
4-
recursive-include nbgitpuller/plugins *
54
recursive-include nbgitpuller/static *
65
recursive-include nbgitpuller/templates *

nbgitpuller/__init__.py

+10
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,16 @@
44
from notebook.utils import url_path_join
55
from tornado.web import StaticFileHandler
66
import os
7+
import nest_asyncio
8+
9+
REPO_PARENT_DIR = None
10+
TEMP_DOWNLOAD_REPO_DIR = "/tmp/temp_download_repo"
11+
CACHED_ORIGIN_NON_GIT_REPO = ".nbgitpuller/targets/"
12+
13+
# this allows us to nest usage of the event_loop from asyncio
14+
# being used by tornado in jupyter distro
15+
# Ref: https://medium.com/@vyshali.enukonda/how-to-get-around-runtimeerror-this-event-loop-is-already-running-3f26f67e762e
16+
nest_asyncio.apply()
717

818

919
def _jupyter_server_extension_paths():

nbgitpuller/handlers.py

+78-49
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
from tornado import gen, web, locks
22
import traceback
33
import urllib.parse
4-
54
from notebook.base.handlers import IPythonHandler
65
import threading
76
import json
@@ -11,11 +10,9 @@
1110

1211
from .pull import GitPuller
1312
from .version import __version__
14-
from .hookspecs import handle_files
15-
from .plugins.zip_puller import ZipSourceGoogleDriveDownloader
16-
from .plugins.zip_puller import ZipSourceDropBoxDownloader
17-
from .plugins.zip_puller import ZipSourceWebDownloader
13+
from . import hookspecs
1814
import pluggy
15+
import nbgitpuller
1916

2017

2118
class SyncHandler(IPythonHandler):
@@ -43,17 +40,53 @@ def emit(self, data):
4340
self.write('data: {}\n\n'.format(serialized_data))
4441
yield self.flush()
4542

46-
def setup_plugins(self, repo):
43+
def setup_plugins(self, provider):
4744
pm = pluggy.PluginManager("nbgitpuller")
48-
pm.add_hookspecs(handle_files)
49-
if "drive.google.com" in repo:
50-
pm.register(ZipSourceGoogleDriveDownloader())
51-
elif "dropbox.com" in repo:
52-
pm.register(ZipSourceDropBoxDownloader())
53-
else:
54-
pm.register(ZipSourceWebDownloader())
45+
pm.add_hookspecs(hookspecs)
46+
pm.load_setuptools_entrypoints("nbgitpuller", name=provider)
5547
return pm
5648

49+
<<<<<<< HEAD
50+
def handle_provider_zip(self, provider):
51+
pm = self.setup_plugins(provider)
52+
req_args = {k: v[0].decode() for k, v in self.request.arguments.items()}
53+
download_q = Queue()
54+
req_args["download_q"] = download_q
55+
hf_args = {"query_line_args": req_args}
56+
dl_thread = ThreadWithResult(target=pm.hook.handle_files, kwargs=hf_args)
57+
dl_thread.start()
58+
self.progress_loop(download_q)
59+
dl_thread.join()
60+
return dl_thread.result
61+
62+
=======
63+
>>>>>>> 9b46037... Added async functionality to non-git archives
64+
@gen.coroutine
65+
def progress_loop(self, queue):
66+
while True:
67+
try:
68+
progress = queue.get_nowait()
69+
except Empty:
70+
yield gen.sleep(0.1)
71+
continue
72+
if progress is None:
73+
yield gen.sleep(5)
74+
return
75+
if isinstance(progress, Exception):
76+
self.emit({
77+
'phase': 'error',
78+
'message': str(progress),
79+
'output': '\n'.join([
80+
line.strip()
81+
for line in traceback.format_exception(
82+
type(progress), progress, progress.__traceback__
83+
)
84+
])
85+
})
86+
return
87+
88+
self.emit({'output': progress, 'phase': 'syncing'})
89+
5790
@web.authenticated
5891
@gen.coroutine
5992
def get(self):
@@ -69,7 +102,7 @@ def get(self):
69102
try:
70103
repo = self.get_argument('repo')
71104
branch = self.get_argument('branch', None)
72-
compressed = self.get_argument('compressed', "false")
105+
provider = self.get_argument('provider', None)
73106
depth = self.get_argument('depth', None)
74107
if depth:
75108
depth = int(depth)
@@ -82,22 +115,31 @@ def get(self):
82115
# so that all repos are always in scope after cloning. Sometimes
83116
# server_root_dir will include things like `~` and so the path
84117
# must be expanded.
85-
repo_parent_dir = os.path.join(os.path.expanduser(self.settings['server_root_dir']),
86-
os.getenv('NBGITPULLER_PARENTPATH', ''))
87-
repo_dir = os.path.join(repo_parent_dir, self.get_argument('targetpath', repo.split('/')[-1]))
118+
repo_parent_dir = os.path.join(os.path.expanduser(self.settings['server_root_dir']), os.getenv('NBGITPULLER_PARENTPATH', ''))
119+
nbgitpuller.REPO_PARENT_DIR = repo_parent_dir
120+
121+
repo_dir = os.path.join(
122+
repo_parent_dir,
123+
self.get_argument('targetpath', repo.split('/')[-1]))
88124

89125
# We gonna send out event streams!
90126
self.set_header('content-type', 'text/event-stream')
91127
self.set_header('cache-control', 'no-cache')
92128

93-
if compressed == 'true':
94-
pm = self.setup_plugins(repo)
95-
results = pm.hook.handle_files(repo=repo, repo_parent_dir=repo_parent_dir)[0]
129+
# if provider is specified then we are dealing with compressed
130+
# archive and not a git repo
131+
if provider is not None:
132+
pm = self.setup_plugins(provider)
133+
req_args = {k: v[0].decode() for k, v in self.request.arguments.items()}
134+
download_q = Queue()
135+
req_args["progress_func"] = lambda: self.progress_loop(download_q)
136+
req_args["download_q"] = download_q
137+
hf_args = {"query_line_args": req_args}
138+
results = pm.hook.handle_files(**hf_args)
96139
repo_dir = repo_parent_dir + results["unzip_dir"]
97140
repo = "file://" + results["origin_repo_path"]
98141

99142
gp = GitPuller(repo, repo_dir, branch=branch, depth=depth, parent=self.settings['nbapp'])
100-
101143
q = Queue()
102144

103145
def pull():
@@ -110,33 +152,11 @@ def pull():
110152
q.put_nowait(e)
111153
raise e
112154
self.gp_thread = threading.Thread(target=pull)
113-
114155
self.gp_thread.start()
115-
116-
while True:
117-
try:
118-
progress = q.get_nowait()
119-
except Empty:
120-
yield gen.sleep(0.5)
121-
continue
122-
if progress is None:
123-
break
124-
if isinstance(progress, Exception):
125-
self.emit({
126-
'phase': 'error',
127-
'message': str(progress),
128-
'output': '\n'.join([
129-
line.strip()
130-
for line in traceback.format_exception(
131-
type(progress), progress, progress.__traceback__
132-
)
133-
])
134-
})
135-
return
136-
137-
self.emit({'output': progress, 'phase': 'syncing'})
138-
156+
self.progress_loop(q)
157+
yield gen.sleep(3)
139158
self.emit({'phase': 'finished'})
159+
140160
except Exception as e:
141161
self.emit({
142162
'phase': 'error',
@@ -170,11 +190,10 @@ def initialize(self):
170190
@gen.coroutine
171191
def get(self):
172192
app_env = os.getenv('NBGITPULLER_APP', default='notebook')
173-
174193
repo = self.get_argument('repo')
175194
branch = self.get_argument('branch', None)
176195
depth = self.get_argument('depth', None)
177-
compressed = self.get_argument('compressed', "false")
196+
provider = self.get_argument('provider', None)
178197
urlPath = self.get_argument('urlpath', None) or \
179198
self.get_argument('urlPath', None)
180199
subPath = self.get_argument('subpath', None) or \
@@ -195,14 +214,17 @@ def get(self):
195214
else:
196215
path = 'tree/' + path
197216

217+
if provider is not None:
218+
path = "tree/"
219+
198220
self.write(
199221
self.render_template(
200222
'status.html',
201223
repo=repo,
202224
branch=branch,
203-
compressed=compressed,
204225
path=path,
205226
depth=depth,
227+
provider=provider,
206228
targetpath=targetpath,
207229
version=__version__
208230
))
@@ -239,3 +261,10 @@ def get(self):
239261
)
240262

241263
self.redirect(new_url)
264+
265+
266+
class ThreadWithResult(threading.Thread):
267+
def __init__(self, group=None, target=None, name=None, args=(), kwargs={}, *, daemon=None):
268+
def function():
269+
self.result = target(*args, **kwargs)
270+
super().__init__(group=group, target=function, name=name, daemon=daemon)

nbgitpuller/hookspecs.py

+12-11
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,22 @@
11
import pluggy
22

33
hookspec = pluggy.HookspecMarker("nbgitpuller")
4+
hookimpl = pluggy.HookimplMarker("nbgitpuller")
45

56

6-
@hookspec
7-
def handle_files(self, repo, repo_parent_dir):
7+
@hookspec(firstresult=True)
8+
def handle_files(query_line_args):
89
"""
9-
:param str repo: download url to source
10-
:param str repo_parent_dir: where we will store the downloaded repo
10+
:param json query_line_args: this includes any argument you put on the url
1111
:return two parameter json unzip_dir and origin_repo_path
1212
:rtype json object
13-
This handles the downloading of non-git source
14-
files into the user directory. Once downloaded,
15-
the files are merged into a local git repository.
1613
17-
Once the local git repository is updated(or created
18-
the first time), git puller can then handle this
19-
directory as it would sources coming from a
20-
git repository.
14+
The developer uses this function to download, un-compress and save the
15+
source files to the TEMP_DOWNLOAD_REPO_DIR folder.
16+
17+
The parameter, query_line_args, is any argument you put on the URL
18+
19+
Once the files are saved to the directly, git puller can handle all the
20+
standard functions needed to make sure source files are updated or created
21+
as needed.
2122
"""

0 commit comments

Comments
 (0)