Skip to content

Commit

Permalink
fix/tolerant providers (#683)
Browse files Browse the repository at this point in the history
* 🔧 use correct binder repo url
* 🫴🏼 hooks no longer throw on undefined contexts
* 📕 changeset
* allow custom repoproviderspecs to be added to react server provider
* 🥁don't provide np-ops
* 🙅🏽 throw exception for malformed RepoProviderSpec
* fix/server blatting (#685)
* 📡 add check call for user server
* 👮🏻 prevent reconnect for existing server
* 📕 changeset
  • Loading branch information
stevejpurves authored Sep 6, 2023
1 parent 77137d5 commit 6929b35
Show file tree
Hide file tree
Showing 12 changed files with 77 additions and 46 deletions.
5 changes: 5 additions & 0 deletions .changeset/metal-humans-beg.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'thebe-core': patch
---

Add check call for user server status
6 changes: 6 additions & 0 deletions .changeset/soft-countries-invite.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
'demo-react': patch
'thebe-react': patch
---

ThebeServerProvider will no longer replace an existing server on rerender if that server is ready and has user server URL.
6 changes: 6 additions & 0 deletions .changeset/witty-zoos-smell.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
'demo-react': patch
'thebe-react': patch
---

Hooks no longer throw but return empty or uninitialised objects instead, this allows for much more flexibility in how respective providers are rendered.
2 changes: 1 addition & 1 deletion apps/demo-react/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ function App() {
kernelName: 'python',
},
binderOptions: {
repo: 'curvenote/binder-base',
repo: 'executablebooks/thebe-binder-base',
},
}),
[path],
Expand Down
2 changes: 1 addition & 1 deletion apps/demo-react/src/Connect.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ export function Connect() {
}, [core, load, loading]);

const clickConnect = () => {
if (!core) return;
if (!core || !connect) return;
connect();
};

Expand Down
10 changes: 9 additions & 1 deletion packages/core/src/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,13 @@ class ThebeServer implements ServerRuntime, ServerRestAPI {
return this.sessionManager?.shutdownAll();
}

async check(): Promise<boolean> {
const resp = await ThebeServer.status(
this.sessionManager?.serverSettings ?? this.config.serverSettings,
);
return resp.ok;
}

dispose() {
if (this._isDisposed) return;
if (!this.serviceManager?.isDisposed) this.serviceManager?.dispose();
Expand Down Expand Up @@ -318,6 +325,7 @@ class ThebeServer implements ServerRuntime, ServerRestAPI {
});

return this.sessionManager.ready.then(() => {
this.userServerUrl = `${serverSettings.baseUrl}?token=${serverSettings.token}`;
this.events.triggerStatus({
status: ServerStatusEvent.ready,
message: `Re-connected to binder server`,
Expand Down Expand Up @@ -438,7 +446,7 @@ class ThebeServer implements ServerRuntime, ServerRestAPI {
return url;
}

static status(serverSettings: Required<ServerSettings>): Promise<void | Response> {
static status(serverSettings: ServerSettings) {
return ServerConnection.makeRequest(
`${serverSettings.baseUrl}api/status`,
{},
Expand Down
2 changes: 2 additions & 0 deletions packages/core/src/url.ts
Original file line number Diff line number Diff line change
Expand Up @@ -118,5 +118,7 @@ export function makeBinderUrls(
if (!Object.keys(providerMap).includes(provider))
throw Error(`Unknown provider ${opts.repoProvider}`);

if (!providerMap[provider].makeUrls) throw Error(`No makeUrls function for ${provider}`);

return providerMap[provider].makeUrls(opts);
}
7 changes: 1 addition & 6 deletions packages/react/src/ThebeLoaderProvider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -118,10 +118,5 @@ export function ThebeBundleLoaderProvider({

export function useThebeLoader() {
const context = React.useContext(ThebeLoaderContext);
if (context === undefined) {
throw new Error(
'useThebeLoader must be used inside a ThebeLoaderProvider or ThebeBundleLoaderProvider',
);
}
return context;
return context ?? { loading: false, load: () => ({}) };
}
5 changes: 1 addition & 4 deletions packages/react/src/ThebeRenderMimeRegistryProvider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,5 @@ export function ThebeRenderMimeRegistryProvider({ children }: React.PropsWithChi

export function useRenderMimeRegistry() {
const context = React.useContext(RenderMimeRegistryContext);
if (context === undefined) {
throw new Error('useRenderMimeRegistry must be used within a ThebeRenderMimeRegistry');
}
return context.rendermime;
return context?.rendermime;
}
59 changes: 35 additions & 24 deletions packages/react/src/ThebeServerProvider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import React, { useCallback, useContext, useEffect, useMemo, useState } from 're
import type {
Config,
CoreOptions,
RepoProviderSpec,
ThebeEventCb,
ThebeEventData,
ThebeEvents,
Expand All @@ -13,13 +14,13 @@ type ListenerFn = (data: ThebeEventData) => void;

export const ThebeServerContext = React.createContext<
| {
connecting: boolean;
ready: boolean;
config?: Config;
events?: ThebeEvents;
server?: ThebeServer;
connecting: boolean;
ready: boolean;
connect: () => void;
disconnect: () => Promise<void>;
connect?: () => void;
disconnect?: () => Promise<void>;
}
| undefined
>(undefined);
Expand All @@ -31,6 +32,7 @@ export function ThebeServerProvider({
useBinder,
useJupyterLite,
customConnectFn,
customRepoProviders,
events,
children,
}: React.PropsWithChildren<{
Expand All @@ -41,6 +43,7 @@ export function ThebeServerProvider({
useJupyterLite?: boolean;
events?: ThebeEvents;
customConnectFn?: (server: ThebeServer) => Promise<void>;
customRepoProviders?: RepoProviderSpec[];
}>) {
const { core } = useThebeLoader();
const [doConnect, setDoConnect] = useState(connect);
Expand All @@ -57,17 +60,21 @@ export function ThebeServerProvider({
);

useEffect(() => {
if (!core || !thebeConfig) return;
if (!core || !thebeConfig || server) return;
setServer(new core.ThebeServer(thebeConfig));
}, [core, thebeConfig]);
}, [core, thebeConfig, server]);

// Once the core is loaded, connect to a server
// TODO: this should be an action not a side effect
useEffect(() => {
if (!core || !thebeConfig) return; // TODO is there a better way to keep typescript happy here?
if (!server || !doConnect) return;
// do not reconnect if already connected!
if (server.isReady && server.userServerUrl) return;
// TODO is the user server really still alive? this would be an async call to server.check
setConnecting(true);
if (customConnectFn) customConnectFn(server);
else if (useBinder) server.connectToServerViaBinder();
else if (useBinder) server.connectToServerViaBinder(customRepoProviders);
else if (useJupyterLite)
server.connectToJupyterLiteServer({
litePluginSettings: {
Expand Down Expand Up @@ -140,13 +147,15 @@ export function useDisposeThebeServer() {
}

export function useThebeServer() {
const thebe = useThebeLoader();
const { core } = thebe ?? {};

const serverContext = useContext(ThebeServerContext);
if (serverContext === undefined) {
throw new Error('useThebeServer must be used inside a ThebeServerProvider');
}
const { config, events, server, connecting, ready, connect, disconnect } = serverContext;
const { config, events, server, connecting, ready, connect, disconnect } = serverContext ?? {
ready: false,
connecting: false,
};

const { core } = useThebeLoader();
const [error, setError] = useState<string | undefined>(); // TODO how to handle errors better via the provider
const [eventCallbacks, setEventCallbacks] = useState<ThebeEventCb[]>([]);

Expand Down Expand Up @@ -180,16 +189,18 @@ export function useThebeServer() {
setEventCallbacks([]);
}, [config, server]);

return {
config,
events,
server,
connecting,
ready,
error,
connect,
disconnect,
subscribe,
unsubAll,
};
return serverContext
? {
config,
events,
server,
connecting,
ready,
error,
connect,
disconnect,
subscribe,
unsubAll,
}
: { connecting: false, ready: false };
}
14 changes: 6 additions & 8 deletions packages/react/src/ThebeSessionProvider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,13 @@ import { useThebeServer } from './ThebeServerProvider';
import { useRenderMimeRegistry } from './ThebeRenderMimeRegistryProvider';

interface ThebeSessionContextData {
path: string;
path?: string;
session?: ThebeSession;
error?: string;
starting: boolean;
ready: boolean;
error?: string;
start: () => Promise<void>;
shutdown: () => Promise<void>;
start?: () => Promise<void>;
shutdown?: () => Promise<void>;
}

export const ThebeSessionContext = React.createContext<ThebeSessionContextData | undefined>(
Expand All @@ -36,6 +36,7 @@ export function ThebeSessionProvider({
const [error, setError] = useState<string | undefined>();

const startSession = () => {
if (!rendermime) throw new Error('ThebeSessionProvider requires a RenderMimeRegistryProvider');
setStarting(true);
server
?.startNewSession(rendermime, { ...config?.kernels, path })
Expand Down Expand Up @@ -103,8 +104,5 @@ export function ThebeSessionProvider({

export function useThebeSession(): ThebeSessionContextData {
const sessionContext = useContext(ThebeSessionContext);
if (sessionContext === undefined) {
throw new Error('useThebeSession must be used inside a ThebeSessionProvider');
}
return sessionContext;
return sessionContext ?? { starting: false, ready: false };
}
5 changes: 4 additions & 1 deletion packages/react/src/hooks/notebook.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import { useThebeLoader } from '../ThebeLoaderProvider';
import type { INotebookContent } from '@jupyterlab/nbformat';
import { useThebeSession } from '../ThebeSessionProvider';
import { useRenderMimeRegistry } from '../ThebeRenderMimeRegistryProvider';
import { render } from 'react-dom';

export interface NotebookExecuteOptions {
stopOnError?: boolean;
Expand Down Expand Up @@ -124,6 +123,8 @@ export function useNotebook(
const rendermime = useRenderMimeRegistry();
const [loading, setLoading] = useState<boolean>(false);

if (!rendermime) throw new Error('ThebeSessionProvider requires a RenderMimeRegistryProvider');

const {
ready,
attached,
Expand Down Expand Up @@ -199,6 +200,7 @@ export function useNotebookFromSource(sourceCode: string[], opts = { refsForWidg
const { config } = useThebeConfig();
const rendermime = useRenderMimeRegistry();
const [loading, setLoading] = useState(false);
if (!rendermime) throw new Error('ThebeSessionProvider requires a RenderMimeRegistryProvider');
const {
ready,
attached,
Expand Down Expand Up @@ -262,6 +264,7 @@ export function useNotebookfromSourceLegacy(sourceCode: string[]) {
const { core } = useThebeLoader();
const { config } = useThebeConfig();
const rendermime = useRenderMimeRegistry();
if (!rendermime) throw new Error('ThebeSessionProvider requires a RenderMimeRegistryProvider');

const [busy, setBusy] = useState<boolean>(false);
const [notebook, setNotebook] = useState<ThebeNotebook | undefined>();
Expand Down

0 comments on commit 6929b35

Please sign in to comment.