2828
2929DEFAULT_HOST = "localhost"
3030DEFAULT_PORT = 9753
31+ DEFAULT_IFRAME_WIDTH = "100%"
3132DEFAULT_IFRAME_HEIGHT = 400
3233
3334_servers : dict [str , uvicorn .Server ] = {}
@@ -41,7 +42,8 @@ def serve(
4142 app : FastAPI | None = None ,
4243 open_browser : bool | None = None ,
4344 open_iframe : bool | None = None ,
44- iframe_height : int = DEFAULT_IFRAME_HEIGHT ,
45+ width : int | str = DEFAULT_IFRAME_WIDTH ,
46+ height : int | str = DEFAULT_IFRAME_HEIGHT ,
4547 # --- Uvicorn
4648 host : str = DEFAULT_HOST ,
4749 port : int = DEFAULT_PORT ,
@@ -59,12 +61,13 @@ def serve(
5961 `fastapi.staticfiles.StaticFiles` object or a directory path.
6062 app: A FastAPI instance to use. If not provided,
6163 a new instance is created and passed to `Service.init_app(app)`
62- so that it can by initialized by the user.
64+ so that it can be initialized by the user.
6365 open_browser: Open the UI in the default browser after starting.
6466 Defaults to True when not running in Jupyter.
6567 open_iframe: Render the UI as an IFrame in the Jupyter notebook.
6668 Defaults to True when running in Jupyter.
67- iframe_height: Height of the IFrame in pixels.
69+ width: Width of the IFrame.
70+ height: Height of the IFrame.
6871 host: Host to bind the server to.
6972 port: Port to bind the server to.
7073 uvicorn_settings: Additional [uvicorn settings]((https://uvicorn.dev/settings/)
@@ -93,10 +96,14 @@ def serve(
9396 else :
9497 mounts_ ["/" ] = StaticFiles (directory = ui_dist , html = True )
9598
96- remotestate_server = Server (service = service , mounts = mounts_ , app = app )
99+ rs_server = Server (service = service , mounts = mounts_ , app = app )
97100
98101 uvicorn_settings .update (host = host , port = port )
99- uvicorn_config = uvicorn .Config (remotestate_server .app , ** uvicorn_settings )
102+ if "log_config" not in uvicorn_settings :
103+ # disable uvicorn's default logging setup
104+ uvicorn_settings .update (log_config = _get_log_config ())
105+
106+ uvicorn_config = uvicorn .Config (rs_server .app , ** uvicorn_settings )
100107 uvicorn_server = uvicorn .Server (uvicorn_config )
101108 _servers [registry_key ] = uvicorn_server
102109
@@ -119,7 +126,7 @@ def serve(
119126 if should_open_iframe :
120127 from IPython .display import IFrame , display
121128
122- display (IFrame (src = ui_dist_url , width = "100%" , height = iframe_height ))
129+ display (IFrame (src = ui_dist_url , width = width , height = height ))
123130 elif should_open_browser :
124131 webbrowser .open (ui_dist_url )
125132
@@ -158,6 +165,7 @@ def _add_ui_url_params(ui_dist_url: str, *, host: str, port: int) -> str:
158165 ("ws" , f"ws://{ host } :{ port } /ws" ),
159166 ]
160167 )
168+ # noinspection PyTypeChecker
161169 return urlunsplit (url_parts ._replace (query = urlencode (query )))
162170
163171
@@ -179,3 +187,35 @@ def _wait_for_port_free(host: str, port: int, timeout: float = 5.0) -> None:
179187 pass # port still in use
180188 time .sleep (0.05 )
181189 raise TimeoutError (f"Port { port } did not become free within { timeout } s" )
190+
191+
192+ def _get_log_config (log_file : str | PathLike = "server.log" ) -> dict [str , Any ]:
193+ return {
194+ "version" : 1 ,
195+ "disable_existing_loggers" : False ,
196+ "handlers" : {
197+ "file" : {
198+ "class" : "logging.FileHandler" ,
199+ "filename" : str (log_file ),
200+ "formatter" : "default" ,
201+ }
202+ },
203+ "formatters" : {
204+ "default" : {
205+ "format" : "%(asctime)s %(levelname)s %(name)s: %(message)s" ,
206+ }
207+ },
208+ "loggers" : {
209+ "uvicorn" : {"handlers" : ["file" ], "level" : "INFO" , "propagate" : False },
210+ "uvicorn.error" : {
211+ "handlers" : ["file" ],
212+ "level" : "INFO" ,
213+ "propagate" : False ,
214+ },
215+ "uvicorn.access" : {
216+ "handlers" : ["file" ],
217+ "level" : "INFO" ,
218+ "propagate" : False ,
219+ },
220+ },
221+ }
0 commit comments