Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

serialize & deserialize from connect server #2

Open
wants to merge 35 commits into
base: main
Choose a base branch
from
Open
Changes from 1 commit
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
57cd768
initial stab at ts connect server
hotpocket Nov 6, 2024
d103974
use schemas from buf registry instead of locally generated ones
hotpocket Nov 6, 2024
2eaed62
remove unnecessary cas4ts
hotpocket Nov 7, 2024
88d34d7
readme with working examples
hotpocket Nov 7, 2024
a3017f7
simplify: remove unused server code
hotpocket Nov 7, 2024
c7a1fae
a better description
hotpocket Nov 7, 2024
a8f668a
remove unnecessary tsconfig
hotpocket Nov 7, 2024
e40a98e
remove leftover proto gen config
hotpocket Nov 7, 2024
29f6779
connect serializer v0 implementation
hotpocket Nov 7, 2024
6b641df
seems correct. may not be.
hotpocket Nov 7, 2024
1f45b63
added questions section to address unknowns.
hotpocket Nov 7, 2024
6e09283
3.9.3-edge.2
Nov 7, 2024
ffe3fff
Merge branch 'stateful:main' into v1
hotpocket Nov 7, 2024
65133fe
test server being called from ConnectSerializer.
hotpocket Nov 7, 2024
4e2819f
Merge branch 'stateful:main' into v1
hotpocket Nov 8, 2024
4129af5
deserialize now using test server
hotpocket Nov 8, 2024
6da075b
verify address is not empty
hotpocket Nov 12, 2024
5a272d1
Merge branch 'main' into v1
hotpocket Nov 13, 2024
f3b977a
wrong port#
hotpocket Nov 14, 2024
6dbf112
refactor transport and catch connect errors
hotpocket Nov 16, 2024
020d20f
attempt to connect using a grpc transport. tried with and without ce…
hotpocket Nov 20, 2024
07c4b94
actually attempt to use the grpcTransport and correct address.
hotpocket Nov 20, 2024
5c05076
sort out how to connect using tls and start the server with the prope…
hotpocket Nov 24, 2024
4105664
Merge branch 'main' into v1
hotpocket Nov 24, 2024
a0b2645
ca is not needed for successful connection.
hotpocket Nov 24, 2024
ba89a69
more detail on recent tls issue.
hotpocket Nov 24, 2024
d3362e5
committing this prior to Grpc feature extractions
hotpocket Nov 25, 2024
9a413c9
beginning of refactor of tyhpe specific functionality.
hotpocket Nov 26, 2024
1a00cdb
replace marshal functionality in Connect and Grpc serializers.
hotpocket Nov 26, 2024
e8a29f3
using a manually started server for now - subject to change.
hotpocket Nov 26, 2024
7c96341
commit to save state before refactor of caching components of grpcSer…
hotpocket Nov 27, 2024
c3f1cdc
move caching into base class so ConnectSerializer gets it too.
hotpocket Nov 27, 2024
58ab0ba
need to fix the error about a missing toBinary method then this will …
hotpocket Nov 27, 2024
28270d5
attempting to track down frontmatter property not having toBinary fun…
hotpocket Nov 28, 2024
44c42f9
explicitly cast proto.frontmatter so it will have necessary serilizat…
hotpocket Nov 28, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
246 changes: 86 additions & 160 deletions src/extension/serializer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -749,7 +749,8 @@ export class GrpcSerializer extends SerializerBase {
): Promise<Uint8Array> {
const marshalFrontmatter = this.lifecycleIdentity === RunmeIdentity.ALL

const notebook = GrpcSerializer.marshalNotebook(data, { marshalFrontmatter })
// const notebook = GrpcSerializer.marshalNotebook(data, { marshalFrontmatter })
const notebook = Marshal.notebook(data, 'Notebook', { marshalFrontmatter }) as Notebook

if (marshalFrontmatter) {
data.metadata ??= {}
Expand Down Expand Up @@ -861,137 +862,14 @@ export class GrpcSerializer extends SerializerBase {
kernel?: Kernel
},
): Notebook {
// the bulk copies cleanly except for what's below
const notebook = Notebook.clone(data as any)

// cannot gurantee it wasn't changed
if (notebook.metadata[RUNME_FRONTMATTER_PARSED]) {
delete notebook.metadata[RUNME_FRONTMATTER_PARSED]
}

if (config?.marshalFrontmatter) {
const metadata = notebook.metadata as unknown as {
['runme.dev/frontmatter']: string
}
notebook.frontmatter = this.marshalFrontmatter(metadata, config.kernel)
}

notebook.cells.forEach(async (cell, cellIdx) => {
const dataExecSummary = data.cells[cellIdx].executionSummary
cell.executionSummary = this.marshalCellExecutionSummary(dataExecSummary)
const dataOutputs = data.cells[cellIdx].outputs
cell.outputs = this.marshalCellOutputs(cell.outputs, dataOutputs)
})

return notebook
return Marshal.notebook(data, 'Notebook', config) as Notebook
}

static marshalFrontmatter(
metadata: { ['runme.dev/frontmatter']?: string },
kernel?: Kernel,
): Frontmatter {
if (
!metadata.hasOwnProperty('runme.dev/frontmatter') ||
typeof metadata['runme.dev/frontmatter'] !== 'string'
) {
log.warn('no frontmatter found in metadata')
return {
category: '',
tag: '',
cwd: '',
runme: {
id: '',
version: '',
},
shell: '',
skipPrompts: false,
terminalRows: '',
}
}

const rawFrontmatter = metadata['runme.dev/frontmatter']
let data: {
runme: {
id?: string
version?: string
}
} = { runme: {} }

if (rawFrontmatter) {
try {
const yamlDocs = YAML.parseAllDocuments(metadata['runme.dev/frontmatter'])
data = (yamlDocs[0].toJS?.() || {}) as typeof data
} catch (error: any) {
log.warn('failed to parse frontmatter, reason: ', error.message)
}
}

return {
runme: {
id: data.runme?.id || '',
version: data.runme?.version || '',
session: { id: kernel?.getRunnerEnvironment()?.getSessionId() || '' },
},
category: '',
tag: '',
cwd: '',
shell: '',
skipPrompts: false,
terminalRows: '',
}
}

private static marshalCellOutputs(
outputs: CellOutput[],
dataOutputs: NotebookCellOutput[] | undefined,
): CellOutput[] {
if (!dataOutputs) {
return []
}

outputs.forEach((out, outIdx) => {
const dataOut: NotebookCellOutputWithProcessInfo = dataOutputs[outIdx]
// todo(sebastian): consider sending error state too
if (dataOut.processInfo?.exitReason?.type === 'exit') {
if (dataOut.processInfo.exitReason.code) {
out.processInfo!.exitReason!.code!.value = dataOut.processInfo.exitReason.code
} else {
out.processInfo!.exitReason!.code = undefined
}

if (dataOut.processInfo?.pid !== undefined) {
out.processInfo!.pid = { value: dataOut.processInfo.pid.toString() }
} else {
out.processInfo!.pid = undefined
}
}
out.items.forEach((item) => {
item.type = item.data.buffer ? 'Buffer' : typeof item.data
})
})

return outputs
}

private static marshalCellExecutionSummary(
executionSummary: NotebookCellExecutionSummary | undefined,
) {
if (!executionSummary) {
return undefined
}

const { success, timing } = executionSummary
if (success === undefined || timing === undefined) {
return undefined
}

return {
success: { value: success },
timing: {
endTime: { value: timing!.endTime.toString() },
startTime: { value: timing!.startTime.toString() },
},
}
return Marshal.frontmatter(metadata, kernel)
}

protected async reviveNotebook(
Expand Down Expand Up @@ -1090,18 +968,10 @@ export class ConnectSerializer extends SerializerBase {
// eslint-disable-next-line @typescript-eslint/no-unused-vars
token: CancellationToken,
): Promise<Uint8Array> {
const notebook = SerialHelper.marshalNotebook(data, es_proto.Notebook)

// console.log('marshalled notebook:', mn)
// const notebook = new es_proto.Notebook({
// cells: [{ value: `The notebook has ${data.cells.length} cell(s)`, kind: 1 }],
// metadata: {},
// frontmatter: {},
// })
const req = new es_proto.SerializeRequest({ notebook })
// req.notebook = notebook
const sr = await this.client.serialize(req)
return sr.result
const notebook = Marshal.notebook(data, 'es_proto') as es_proto.Notebook
const request = new es_proto.SerializeRequest({ notebook })
const response = await this.client.serialize(request)
return response.result
}

protected async reviveNotebook(
Expand Down Expand Up @@ -1156,25 +1026,23 @@ export class ConnectSerializer extends SerializerBase {
}
}

class SerialHelper {
public static myTypeOf(someThing: any) {
const match = new Object().toString.call(someThing).match(/\[object (.*?)\]/)
return match ? match[1] : ''
}

// convert vscode.NotebookData to a runme Notebook type as generated by protoc-gen-es
public static marshalNotebook<T extends es_proto.Notebook | Notebook>(
class Marshal {
Copy link
Owner Author

@hotpocket hotpocket Nov 29, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

playing with abstraction via composition. referenced here to compliment prior comments made

// convert vscode.NotebookData to one of two runme Notebook proto type bindings
public static notebook(
data: NotebookData,
cls: new (dat: any) => T,
cls: string,
config?: {
marshalFrontmatter?: boolean
kernel?: Kernel
},
): T {
// the bulk copies cleanly except for what's below
const notebook = new cls(data as any)
const myType = SerialHelper.myTypeOf(notebook)
console.log('myType:', myType)
): es_proto.Notebook | Notebook {
// is there no better way than this ?
let notebook
if (cls === 'es_proto') {
notebook = new es_proto.Notebook(data as any)
} else {
notebook = Notebook.clone(data as any)
}

// cannot gurantee it wasn't changed
if (notebook.metadata[RUNME_FRONTMATTER_PARSED]) {
Expand All @@ -1185,20 +1053,20 @@ class SerialHelper {
const metadata = notebook.metadata as unknown as {
['runme.dev/frontmatter']: string
}
notebook.frontmatter = this.marshalFrontmatter(metadata, config.kernel)
notebook.frontmatter = Marshal.frontmatter(metadata, config.kernel)
}

// notebook.cells.forEach(async (cell, cellIdx) => {
// const dataExecSummary = data.cells[cellIdx].executionSummary
// cell.executionSummary = this.marshalCellExecutionSummary(dataExecSummary)
// const dataOutputs = data.cells[cellIdx].outputs
// cell.outputs = this.marshalCellOutputs(cell.outputs, dataOutputs)
// })
notebook.cells.forEach(async (cell, cellIdx) => {
const dataExecSummary = data.cells[cellIdx].executionSummary
cell.executionSummary = Marshal.cellExecutionSummary(dataExecSummary)
const dataOutputs = data.cells[cellIdx].outputs
cell.outputs = Marshal.cellOutputs(cell.outputs, dataOutputs)
})

return notebook
}

static marshalFrontmatter(metadata: { ['runme.dev/frontmatter']?: string }, kernel?: Kernel) {
static frontmatter(metadata: { ['runme.dev/frontmatter']?: string }, kernel?: Kernel) {
if (
!metadata.hasOwnProperty('runme.dev/frontmatter') ||
typeof metadata['runme.dev/frontmatter'] !== 'string'
Expand Down Expand Up @@ -1249,4 +1117,62 @@ class SerialHelper {
terminalRows: '',
}
}

// if we have execution information from vscode, return it so we can augment the Cell proto
private static cellExecutionSummary(executionSummary: NotebookCellExecutionSummary | undefined) {
if (!executionSummary) {
return undefined
}

const { success, timing } = executionSummary
if (success === undefined || timing === undefined) {
return undefined
}

return {
success: { value: success },
timing: {
endTime: { value: timing!.endTime.toString() },
startTime: { value: timing!.startTime.toString() },
},
}
}

private static cellOutputs(
outputs: CellOutput[] | es_proto.CellOutput[],
dataOutputs: NotebookCellOutput[] | undefined,
): CellOutput[] | es_proto.CellOutput[] {
if (!dataOutputs) {
return []
}

outputs.forEach((out, outIdx) => {
const dataOut: NotebookCellOutputWithProcessInfo = dataOutputs[outIdx]
// todo(sebastian): consider sending error state too
if (dataOut.processInfo?.exitReason?.type === 'exit') {
if (dataOut.processInfo.exitReason.code) {
if (typeof out.processInfo!.exitReason!.code === 'number') {
// code is a number for protoc-gen-es generated types
out.processInfo!.exitReason!.code = dataOut.processInfo.exitReason.code
} else {
// code is a UInt32Value for protobuf-ts generated types (and subsequently has a value property)
out.processInfo!.exitReason!.code!.value = dataOut.processInfo.exitReason.code
}
} else {
out.processInfo!.exitReason!.code = undefined
}

if (dataOut.processInfo?.pid !== undefined) {
out.processInfo!.pid = { value: dataOut.processInfo.pid.toString() }
} else {
out.processInfo!.pid = undefined
}
}
out.items.forEach((item) => {
item.type = item.data.buffer ? 'Buffer' : typeof item.data
})
})

return outputs
}
}