de-shell 0.3.0__tar.gz → 0.4.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (77) hide show
  1. {de_shell-0.3.0 → de_shell-0.4.0}/PKG-INFO +1 -1
  2. {de_shell-0.3.0 → de_shell-0.4.0}/de_shell/__init__.py +1 -1
  3. de_shell-0.4.0/de_shell/js/main/framing.test.ts +125 -0
  4. de_shell-0.4.0/de_shell/js/main/framing.ts +34 -0
  5. {de_shell-0.3.0 → de_shell-0.4.0}/de_shell/js/main/index.ts +4 -0
  6. de_shell-0.4.0/de_shell/js/main/relay.test.ts +815 -0
  7. de_shell-0.4.0/de_shell/js/main/relay.ts +258 -0
  8. de_shell-0.4.0/de_shell/remote_client.py +199 -0
  9. {de_shell-0.3.0 → de_shell-0.4.0}/de_shell.egg-info/PKG-INFO +1 -1
  10. {de_shell-0.3.0 → de_shell-0.4.0}/de_shell.egg-info/SOURCES.txt +6 -0
  11. {de_shell-0.3.0 → de_shell-0.4.0}/tests/test_boundary.py +1 -0
  12. de_shell-0.4.0/tests/test_remote_client.py +408 -0
  13. {de_shell-0.3.0 → de_shell-0.4.0}/LICENSE +0 -0
  14. {de_shell-0.3.0 → de_shell-0.4.0}/README.md +0 -0
  15. {de_shell-0.3.0 → de_shell-0.4.0}/de_shell/actions/__init__.py +0 -0
  16. {de_shell-0.3.0 → de_shell-0.4.0}/de_shell/actions/context.py +0 -0
  17. {de_shell-0.3.0 → de_shell-0.4.0}/de_shell/actions/figure_registry.py +0 -0
  18. {de_shell-0.3.0 → de_shell-0.4.0}/de_shell/actions/lifecycle.py +0 -0
  19. {de_shell-0.3.0 → de_shell-0.4.0}/de_shell/actions/registry.py +0 -0
  20. {de_shell-0.3.0 → de_shell-0.4.0}/de_shell/actions/wizard.py +0 -0
  21. {de_shell-0.3.0 → de_shell-0.4.0}/de_shell/app.py +0 -0
  22. {de_shell-0.3.0 → de_shell-0.4.0}/de_shell/compute.py +0 -0
  23. {de_shell-0.3.0 → de_shell-0.4.0}/de_shell/debug_flags.py +0 -0
  24. {de_shell-0.3.0 → de_shell-0.4.0}/de_shell/ipc.py +0 -0
  25. {de_shell-0.3.0 → de_shell-0.4.0}/de_shell/js/__init__.py +0 -0
  26. {de_shell-0.3.0 → de_shell-0.4.0}/de_shell/js/__main__.py +0 -0
  27. {de_shell-0.3.0 → de_shell-0.4.0}/de_shell/js/main/backendProcess.test.ts +0 -0
  28. {de_shell-0.3.0 → de_shell-0.4.0}/de_shell/js/main/backendProcess.ts +0 -0
  29. {de_shell-0.3.0 → de_shell-0.4.0}/de_shell/js/main/config.ts +0 -0
  30. {de_shell-0.3.0 → de_shell-0.4.0}/de_shell/js/main/dialogs.ts +0 -0
  31. {de_shell-0.3.0 → de_shell-0.4.0}/de_shell/js/main/envProgress.ts +0 -0
  32. {de_shell-0.3.0 → de_shell-0.4.0}/de_shell/js/main/errorReport.ts +0 -0
  33. {de_shell-0.3.0 → de_shell-0.4.0}/de_shell/js/main/problemLog.ts +0 -0
  34. {de_shell-0.3.0 → de_shell-0.4.0}/de_shell/js/main/pythonEnv.test.ts +0 -0
  35. {de_shell-0.3.0 → de_shell-0.4.0}/de_shell/js/main/pythonEnv.ts +0 -0
  36. {de_shell-0.3.0 → de_shell-0.4.0}/de_shell/js/main/sentryEnvelope.test.ts +0 -0
  37. {de_shell-0.3.0 → de_shell-0.4.0}/de_shell/js/main/sentryEnvelope.ts +0 -0
  38. {de_shell-0.3.0 → de_shell-0.4.0}/de_shell/js/main/stdoutDemux.test.ts +0 -0
  39. {de_shell-0.3.0 → de_shell-0.4.0}/de_shell/js/main/stdoutDemux.ts +0 -0
  40. {de_shell-0.3.0 → de_shell-0.4.0}/de_shell/js/main/updater.ts +0 -0
  41. {de_shell-0.3.0 → de_shell-0.4.0}/de_shell/js/main/updaterErrors.test.ts +0 -0
  42. {de_shell-0.3.0 → de_shell-0.4.0}/de_shell/js/main/updaterErrors.ts +0 -0
  43. {de_shell-0.3.0 → de_shell-0.4.0}/de_shell/js/main/window.ts +0 -0
  44. {de_shell-0.3.0 → de_shell-0.4.0}/de_shell/js/package.json +0 -0
  45. {de_shell-0.3.0 → de_shell-0.4.0}/de_shell/js/preload/index.ts +0 -0
  46. {de_shell-0.3.0 → de_shell-0.4.0}/de_shell/js/renderer/FigureFrame.tsx +0 -0
  47. {de_shell-0.3.0 → de_shell-0.4.0}/de_shell/js/renderer/figureBridge.react.ts +0 -0
  48. {de_shell-0.3.0 → de_shell-0.4.0}/de_shell/js/renderer/figureBridge.test.ts +0 -0
  49. {de_shell-0.3.0 → de_shell-0.4.0}/de_shell/js/renderer/figureBridge.ts +0 -0
  50. {de_shell-0.3.0 → de_shell-0.4.0}/de_shell/js/renderer/index.ts +0 -0
  51. {de_shell-0.3.0 → de_shell-0.4.0}/de_shell/js/renderer/protocol.ts +0 -0
  52. {de_shell-0.3.0 → de_shell-0.4.0}/de_shell/js/renderer/shellState.test.ts +0 -0
  53. {de_shell-0.3.0 → de_shell-0.4.0}/de_shell/js/renderer/shellState.ts +0 -0
  54. {de_shell-0.3.0 → de_shell-0.4.0}/de_shell/js/renderer/sizeReporter.test.ts +0 -0
  55. {de_shell-0.3.0 → de_shell-0.4.0}/de_shell/js/renderer/sizeReporter.ts +0 -0
  56. {de_shell-0.3.0 → de_shell-0.4.0}/de_shell/js/testing/harness.cjs +0 -0
  57. {de_shell-0.3.0 → de_shell-0.4.0}/de_shell/js/testing/harness.test.cjs +0 -0
  58. {de_shell-0.3.0 → de_shell-0.4.0}/de_shell/log_stream.py +0 -0
  59. {de_shell-0.3.0 → de_shell-0.4.0}/de_shell/plotting/__init__.py +0 -0
  60. {de_shell-0.3.0 → de_shell-0.4.0}/de_shell/plotting/colormaps.py +0 -0
  61. {de_shell-0.3.0 → de_shell-0.4.0}/de_shell/plotting/figure.py +0 -0
  62. {de_shell-0.3.0 → de_shell-0.4.0}/de_shell/plotting/selectors/__init__.py +0 -0
  63. {de_shell-0.3.0 → de_shell-0.4.0}/de_shell/plotting/selectors/utils.py +0 -0
  64. {de_shell-0.3.0 → de_shell-0.4.0}/de_shell/plotting/stream.py +0 -0
  65. {de_shell-0.3.0 → de_shell-0.4.0}/de_shell/process_guard.py +0 -0
  66. {de_shell-0.3.0 → de_shell-0.4.0}/de_shell/session.py +0 -0
  67. {de_shell-0.3.0 → de_shell-0.4.0}/de_shell/testing/__init__.py +0 -0
  68. {de_shell-0.3.0 → de_shell-0.4.0}/de_shell/timing.py +0 -0
  69. {de_shell-0.3.0 → de_shell-0.4.0}/de_shell.egg-info/dependency_links.txt +0 -0
  70. {de_shell-0.3.0 → de_shell-0.4.0}/de_shell.egg-info/requires.txt +0 -0
  71. {de_shell-0.3.0 → de_shell-0.4.0}/de_shell.egg-info/top_level.txt +0 -0
  72. {de_shell-0.3.0 → de_shell-0.4.0}/pyproject.toml +0 -0
  73. {de_shell-0.3.0 → de_shell-0.4.0}/setup.cfg +0 -0
  74. {de_shell-0.3.0 → de_shell-0.4.0}/tests/test_actions.py +0 -0
  75. {de_shell-0.3.0 → de_shell-0.4.0}/tests/test_figure.py +0 -0
  76. {de_shell-0.3.0 → de_shell-0.4.0}/tests/test_session_base.py +0 -0
  77. {de_shell-0.3.0 → de_shell-0.4.0}/tests/test_stream.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: de-shell
3
- Version: 0.3.0
3
+ Version: 0.4.0
4
4
  Summary: The Electron + Python desktop-app shell Direct Electron's apps (SpyDE, Ground Crew, Autopilot) are assembled from
5
5
  Author-email: Carter Francis <cartsfrancis@gmail.com>
6
6
  License: MIT
@@ -20,6 +20,6 @@ from __future__ import annotations
20
20
 
21
21
  #: The package version — the one place it is written. pyproject.toml reads it
22
22
  #: (dynamic version) and the release workflow refuses a tag that disagrees.
23
- __version__ = "0.3.0"
23
+ __version__ = "0.4.0"
24
24
 
25
25
  __all__ = ["ipc", "log_stream", "process_guard", "debug_flags", "compute"]
@@ -0,0 +1,125 @@
1
+ /**
2
+ * framing.test.ts — the encoders against the existing demuxer: whatever
3
+ * framing.ts writes, stdoutDemux.ts must read back unchanged, whole or split
4
+ * one byte at a time.
5
+ *
6
+ * Run: `node --test de_shell/js/main/framing.test.ts`, or via `npm run test:unit`.
7
+ */
8
+ import { test } from 'node:test'
9
+ import assert from 'node:assert/strict'
10
+ import { encodeBinary, encodeMessage } from './framing.ts'
11
+ import { createStdoutDemux } from './stdoutDemux.ts'
12
+
13
+ type Event =
14
+ | { kind: 'message'; msg: Record<string, unknown> }
15
+ | { kind: 'stream'; text: string }
16
+ | { kind: 'binary'; header: Record<string, unknown>; payload: string }
17
+
18
+ /** Feed `stream` to a fresh demuxer in `step`-byte slices; return the event trace. */
19
+ function decode(stream: Buffer, step = stream.length || 1): Event[] {
20
+ const events: Event[] = []
21
+ const demux = createStdoutDemux({
22
+ onMessage: (msg) => events.push({ kind: 'message', msg }),
23
+ onStream: (text) => events.push({ kind: 'stream', text }),
24
+ onBinary: (header, payload) =>
25
+ events.push({ kind: 'binary', header, payload: payload.toString('hex') }),
26
+ })
27
+ for (let pos = 0; pos < stream.length; pos += step) {
28
+ demux.push(stream.subarray(pos, pos + step))
29
+ }
30
+ return events
31
+ }
32
+
33
+ /** A whole binary frame as it goes on the wire: prefix, header, then the caller's payload. */
34
+ function frame(header: Record<string, unknown>, payload: Buffer): Buffer {
35
+ return Buffer.concat([...encodeBinary(header, payload), payload])
36
+ }
37
+
38
+ function patternPayload(n: number): Buffer {
39
+ const p = Buffer.allocUnsafe(n)
40
+ for (let i = 0; i < n; i++) p[i] = i & 0xff // includes 0x0a bytes
41
+ return p
42
+ }
43
+
44
+ test('a message is one PLOTAPP line and decodes to the same object', () => {
45
+ const msg = { type: 'state_update', key: 'clim', value: [0, 255], nested: { ok: true, none: null } }
46
+ const bytes = encodeMessage(msg)
47
+ assert.equal(bytes.subarray(0, 8).toString('ascii'), 'PLOTAPP:')
48
+ assert.equal(bytes.indexOf(0x0a), bytes.length - 1, 'exactly one newline, at the end')
49
+ assert.deepEqual(decode(bytes), [{ kind: 'message', msg }])
50
+ })
51
+
52
+ test('a newline inside a string stays escaped, so the message is still one line', () => {
53
+ const msg = { type: 'status', text: 'line one\nline two\r\n' }
54
+ const bytes = encodeMessage(msg)
55
+ assert.equal(bytes.indexOf(0x0a), bytes.length - 1)
56
+ assert.deepEqual(decode(bytes), [{ kind: 'message', msg }])
57
+ })
58
+
59
+ test('a frame with an empty payload decodes with an empty payload', () => {
60
+ assert.deepEqual(decode(frame({ fig_id: 'f3', key: 'spec' }, Buffer.alloc(0))), [
61
+ { kind: 'binary', header: { fig_id: 'f3', key: 'spec' }, payload: '' },
62
+ ])
63
+ })
64
+
65
+ test('a payload holding newlines and PLOTBIN:/PLOTAPP: lookalikes decodes whole, at any chunking', () => {
66
+ const nasty = Buffer.concat([
67
+ Buffer.from('\nPLOTBIN:9:9\nPLOTAPP:{}\n', 'ascii'),
68
+ patternPayload(3000),
69
+ ])
70
+ const stream = Buffer.concat([
71
+ frame({ fig_id: 'f1', key: 'image' }, nasty),
72
+ encodeMessage({ type: 'done' }),
73
+ ])
74
+ const expected: Event[] = [
75
+ { kind: 'binary', header: { fig_id: 'f1', key: 'image' }, payload: nasty.toString('hex') },
76
+ { kind: 'message', msg: { type: 'done' } },
77
+ ]
78
+ for (const step of [stream.length, 65536, 7, 1]) {
79
+ assert.deepEqual(decode(stream, step), expected, `diverged at ${step}-byte chunks`)
80
+ }
81
+ })
82
+
83
+ test('a non-ASCII header: hlen counts UTF-8 bytes, not characters', () => {
84
+ const header = { fig_id: 'f', key: 'k', label: 'εxx Å' }
85
+ const [prefix, head] = encodeBinary(header, Buffer.from([1, 2, 3]))
86
+ const json = JSON.stringify(header)
87
+ assert.notEqual(Buffer.byteLength(json, 'utf8'), json.length, 'the fixture must be multi-byte')
88
+ assert.equal(prefix.toString('ascii'), `PLOTBIN:${Buffer.byteLength(json, 'utf8')}:3\n`)
89
+ assert.equal(head.toString('utf8'), json)
90
+ assert.deepEqual(decode(frame(header, Buffer.from([1, 2, 3])), 1), [
91
+ { kind: 'binary', header, payload: '010203' },
92
+ ])
93
+ })
94
+
95
+ test('encodeBinary returns the prefix and header only; the payload is never copied', () => {
96
+ const payload = patternPayload(64)
97
+ const parts = encodeBinary({ fig_id: 'f', key: 'k' }, payload)
98
+ assert.equal(parts.length, 2)
99
+ assert.ok(!parts.includes(payload))
100
+ assert.match(parts[0].toString('ascii'), /^PLOTBIN:\d+:64\n$/)
101
+ })
102
+
103
+ test('the layout is the documented wire format, byte for byte', () => {
104
+ // PLOTBIN:<hlen>:<plen>\n<header json><payload>, as anyplotlib's
105
+ // _binary_frame.encode_frame writes it and stdoutDemux.ts parses it.
106
+ const header = { fig_id: 'f', key: 'k', dims: [2, 2] }
107
+ const payload = Buffer.from([0, 10, 255, 10])
108
+ const json = JSON.stringify(header)
109
+ assert.deepEqual(frame(header, payload), Buffer.concat([
110
+ Buffer.from(`PLOTBIN:${Buffer.byteLength(json)}:4\n${json}`, 'utf8'),
111
+ payload,
112
+ ]))
113
+ assert.deepEqual(encodeMessage({ type: 'done' }), Buffer.from('PLOTAPP:{"type":"done"}\n', 'utf8'))
114
+ })
115
+
116
+ test('a non-finite number is refused rather than framed', () => {
117
+ for (const bad of [NaN, Infinity, -Infinity]) {
118
+ assert.throws(() => encodeMessage({ type: 'fit', value: bad }), RangeError)
119
+ assert.throws(
120
+ () => encodeBinary({ fig_id: 'f', key: 'k', clim: [0, bad] }, Buffer.alloc(0)),
121
+ RangeError,
122
+ )
123
+ }
124
+ assert.throws(() => encodeMessage({ a: { b: [1, NaN] } }), /non-finite/)
125
+ })
@@ -0,0 +1,34 @@
1
+ /**
2
+ * framing.ts — encoders for the backend wire format; the inverse of
3
+ * stdoutDemux.ts.
4
+ *
5
+ * PLOTAPP:<json>\n a message
6
+ * PLOTBIN:<hlen>:<plen>\n<header json><payload> a binary frame
7
+ *
8
+ * The layout ipc.emit and anyplotlib's _binary_frame.encode_frame write on the
9
+ * Python side. hlen and plen are byte counts. The JSON text is JSON.stringify's
10
+ * own (compact, raw UTF-8); every decoder of the format accepts it. Imports
11
+ * nothing.
12
+ */
13
+
14
+ /** JSON.stringify replacer: a non-finite number is not JSON, so refuse it rather than emit null or NaN. */
15
+ function refuseNonFinite(key: string, value: unknown): unknown {
16
+ if (typeof value === 'number' && !Number.isFinite(value)) {
17
+ throw new RangeError(`cannot frame the non-finite number ${value}${key ? ` at key "${key}"` : ''}`)
18
+ }
19
+ return value
20
+ }
21
+
22
+ /** `PLOTAPP:<json>\n`. JSON escapes every newline inside a string, so the message is one line. */
23
+ export function encodeMessage(obj: Record<string, unknown>): Buffer {
24
+ return Buffer.from(`PLOTAPP:${JSON.stringify(obj, refuseNonFinite)}\n`, 'utf8')
25
+ }
26
+
27
+ /**
28
+ * The prefix line and the header bytes of a PLOTBIN frame. The payload is the
29
+ * caller's Buffer, written as its own chunk after these two, never copied.
30
+ */
31
+ export function encodeBinary(header: Record<string, unknown>, payload: Buffer): [Buffer, Buffer] {
32
+ const head = Buffer.from(JSON.stringify(header, refuseNonFinite), 'utf8')
33
+ return [Buffer.from(`PLOTBIN:${head.length}:${payload.length}\n`, 'ascii'), head]
34
+ }
@@ -22,6 +22,10 @@ export {
22
22
  export { recordProblem, recordedProblems } from './problemLog'
23
23
  export type { Problem } from './problemLog'
24
24
 
25
+ export { createRelay } from './relay'
26
+ export type { Relay, RelayConnection, RelayCloseReason, RelayOptions } from './relay'
27
+ export { encodeMessage, encodeBinary } from './framing'
28
+
25
29
  export {
26
30
  initErrorReporting, reportingConfigured, collectDiagnostics, submitReport,
27
31
  } from './errorReport'