66from pycrdt import Map
77
88from traitlets .config import LoggingConfigurable
9- from traitlets import (
10- Dict ,
11- Instance ,
12- Int ,
13- default
14- )
9+ from traitlets import Dict , Instance , Int , default
1510
1611from jupyter_core .paths import jupyter_runtime_dir
1712
18- class OutputsManager (LoggingConfigurable ):
1913
14+ class OutputsManager (LoggingConfigurable ):
2015 _last_output_index = Dict (default_value = {})
2116 _stream_count = Dict (default_value = {})
2217
@@ -26,7 +21,7 @@ class OutputsManager(LoggingConfigurable):
2621 @default ("outputs_path" )
2722 def _default_outputs_path (self ):
2823 return Path (jupyter_runtime_dir ()) / "outputs"
29-
24+
3025 def _ensure_path (self , file_id , cell_id ):
3126 nested_dir = self .outputs_path / file_id / cell_id
3227 nested_dir .mkdir (parents = True , exist_ok = True )
@@ -38,16 +33,42 @@ def _build_path(self, file_id, cell_id=None, output_index=None):
3833 if output_index is not None :
3934 path = path / f"{ output_index } .output"
4035 return path
41-
36+
4237 def get_output (self , file_id , cell_id , output_index ):
43- """Get an outputs by file_id, cell_id, and output_index."""
38+ """Get an output by file_id, cell_id, and output_index."""
4439 path = self ._build_path (file_id , cell_id , output_index )
4540 if not os .path .isfile (path ):
4641 raise FileNotFoundError (f"The output file doesn't exist: { path } " )
4742 with open (path , "r" , encoding = "utf-8" ) as f :
4843 output = json .loads (f .read ())
4944 return output
5045
46+ def get_outputs (self , file_id , cell_id ):
47+ """Get all outputs by file_id, cell_id."""
48+ path = self ._build_path (file_id , cell_id )
49+ if not os .path .isdir (path ):
50+ raise FileNotFoundError (f"The output dir doesn't exist: { path } " )
51+
52+ outputs = []
53+
54+ output_files = [(f , int (f .stem )) for f in path .glob ("*.output" )]
55+ output_files .sort (key = lambda x : x [1 ])
56+ output_files = output_files [: self .stream_limit ]
57+ has_more_files = len (output_files ) >= self .stream_limit
58+
59+ outputs = []
60+ for file_path , _ in output_files :
61+ with open (file_path , "r" , encoding = "utf-8" ) as f :
62+ output = f .read ()
63+ outputs .append (output )
64+
65+ if has_more_files :
66+ url = create_output_url (file_id , cell_id )
67+ placeholder = create_placeholder_dict ("display_data" , url , full = True )
68+ outputs .append (json .dumps (placeholder ))
69+
70+ return outputs
71+
5172 def get_stream (self , file_id , cell_id ):
5273 "Get the stream output for a cell by file_id and cell_id."
5374 path = self ._build_path (file_id , cell_id ) / "stream"
@@ -59,7 +80,7 @@ def get_stream(self, file_id, cell_id):
5980
6081 def write (self , file_id , cell_id , output ):
6182 """Write a new output for file_id and cell_id.
62-
83+
6384 Returns a placeholder output (pycrdt.Map) or None if no placeholder
6485 output should be written to the ydoc.
6586 """
@@ -77,10 +98,10 @@ def write_output(self, file_id, cell_id, output):
7798 data = json .dumps (output , ensure_ascii = False )
7899 with open (path , "w" , encoding = "utf-8" ) as f :
79100 f .write (data )
80- url = f"/api/outputs/ { file_id } / { cell_id } / { index } .output"
101+ url = create_output_url ( file_id , cell_id , index )
81102 self .log .info (f"Wrote output: { url } " )
82103 return create_placeholder_output (output ["output_type" ], url )
83-
104+
84105 def write_stream (self , file_id , cell_id , output , placeholder ) -> Map :
85106 # How many stream outputs have been written for this cell previously
86107 count = self ._stream_count .get (cell_id , 0 )
@@ -89,12 +110,10 @@ def write_stream(self, file_id, cell_id, output, placeholder) -> Map:
89110 self ._ensure_path (file_id , cell_id )
90111 path = self ._build_path (file_id , cell_id ) / "stream"
91112 text = output ["text" ]
92- mode = 'a' if os .path .isfile (path ) else 'w'
93113 with open (path , "a" , encoding = "utf-8" ) as f :
94114 f .write (text )
95- url = f"/api/outputs/ { file_id } / { cell_id } /stream"
115+ url = create_output_url ( file_id , cell_id )
96116 self .log .info (f"Wrote stream: { url } " )
97-
98117 # Increment the count
99118 count = count + 1
100119 self ._stream_count [cell_id ] = count
@@ -105,12 +124,7 @@ def write_stream(self, file_id, cell_id, output, placeholder) -> Map:
105124 placeholder = placeholder
106125 elif count == self .stream_limit :
107126 # Return a link to the full stream output
108- placeholder = Map ({
109- "output_type" : "display_data" ,
110- "data" : {
111- 'text/html' : f'<a href="{ url } ">Click this link to see the full stream output</a>'
112- }
113- })
127+ placeholder = create_placeholder_output ("display_data" , url , full = True )
114128 elif count > self .stream_limit :
115129 # Return None to indicate that no placeholder should be written to the ydoc
116130 placeholder = None
@@ -133,27 +147,71 @@ def clear(self, file_id, cell_id=None):
133147 pass
134148
135149
136- def create_placeholder_output (output_type : str , url : str ):
150+ def create_output_url (file_id : str , cell_id : str , output_index : int = None ) -> str :
151+ """
152+ Create the URL for an output or stream.
153+
154+ Parameters:
155+ - file_id (str): The ID of the file.
156+ - cell_id (str): The ID of the cell.
157+ - output_index (int, optional): The index of the output. If None, returns the stream URL.
158+
159+ Returns:
160+ - str: The URL string for the output or stream.
161+ """
162+ if output_index is None :
163+ return f"/api/outputs/{ file_id } /{ cell_id } /stream"
164+ else :
165+ return f"/api/outputs/{ file_id } /{ cell_id } /{ output_index } .output"
166+
167+ def create_placeholder_dict (output_type : str , url : str , full : bool = False ):
168+ """
169+ Build a placeholder output dict for the given output_type and url.
170+ If full is True and output_type is "display_data", returns a display_data output
171+ with an HTML link to the full stream output.
172+
173+ Parameters:
174+ - output_type (str): The type of the output.
175+ - url (str): The URL associated with the output.
176+ - full (bool): Whether to create a full output placeholder with a link.
177+
178+ Returns:
179+ - dict: The placeholder output dictionary.
180+
181+ Raises:
182+ - ValueError: If the output_type is unknown.
183+ """
137184 metadata = dict (url = url )
185+ if full and output_type == "display_data" :
186+ return {
187+ "output_type" : "display_data" ,
188+ "data" : {
189+ "text/html" : f'<a href="{ url } ">Click this link to see the full stream output</a>'
190+ },
191+ }
138192 if output_type == "stream" :
139- output = Map ({
140- "output_type" : "stream" ,
141- "text" : "" ,
142- "metadata" : metadata
143- })
193+ return {"output_type" : "stream" , "text" : "" , "metadata" : metadata }
144194 elif output_type == "display_data" :
145- output = Map ({
146- "output_type" : "display_data" ,
147- "metadata" : metadata
148- })
195+ return {"output_type" : "display_data" , "metadata" : metadata }
149196 elif output_type == "execute_result" :
150- output = Map ({
151- "output_type" : "execute_result" ,
152- "metadata" : metadata
153- })
197+ return {"output_type" : "execute_result" , "metadata" : metadata }
154198 elif output_type == "error" :
155- output = Map ({
156- "output_type" : "error" ,
157- "metadata" : metadata
158- })
159- return output
199+ return {"output_type" : "error" , "metadata" : metadata }
200+ else :
201+ raise ValueError (f"Unknown output_type: { output_type } " )
202+
203+ def create_placeholder_output (output_type : str , url : str , full : bool = False ):
204+ """
205+ Creates a placeholder output Map for the given output_type and url.
206+ If full is True and output_type is "display_data", creates a display_data output with an HTML link.
207+
208+ Parameters:
209+ - output_type (str): The type of the output.
210+ - url (str): The URL associated with the output.
211+ - full (bool): Whether to create a full output placeholder with a link.
212+
213+ Returns:
214+ - Map: The placeholder output `ycrdt.Map`.
215+ """
216+ output_dict = create_placeholder_dict (output_type , url , full = full )
217+ return Map (output_dict )
0 commit comments