Skip to content

Commit 99f3cd0

Browse files
authored
Merge pull request #13 from python-chile/fix/python-editor-runner
Fix/python editor runner
2 parents 9583776 + b7dbc36 commit 99f3cd0

8 files changed

Lines changed: 102 additions & 123 deletions

File tree

‎scripts/python-runner/chart.js‎

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,8 +41,13 @@ export async function captureChart(runtime, namespace) {
4141

4242
try {
4343
const chart = readVariable(namespace, CHART_VARIABLE);
44+
4445
const encodedChart = String(chart.value ?? "");
4546

47+
if (!encodedChart) {
48+
return undefined;
49+
}
50+
4651
if (encodedChart.length > MAX_CHART_BASE64_LENGTH) {
4752
throw new Error("El gráfico generado supera el tamaño máximo permitido.");
4853
}

‎scripts/python-runner/messages.js‎

Lines changed: 16 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,29 @@
11
export const MAX_OUTPUT_LENGTH = 12_000;
22

3-
const TRUNCATION_NOTICE = "… salida truncada";
3+
const TRUNCATION_NOTICE = "\n… salida truncada";
44

55
export function send(message) {
66
globalThis.postMessage(message);
77
}
88

99
export function appendOutput(current, value) {
10-
const nextOutput = `${current}${value}\n`;
10+
if (current.endsWith(TRUNCATION_NOTICE)) {
11+
return current;
12+
}
13+
14+
const availableLength = MAX_OUTPUT_LENGTH - current.length;
1115

12-
if (nextOutput.length <= MAX_OUTPUT_LENGTH) {
13-
return nextOutput;
16+
if (availableLength <= 0) {
17+
return `${current.slice(0, MAX_OUTPUT_LENGTH)}${TRUNCATION_NOTICE}`;
1418
}
1519

16-
const truncatedOutput = nextOutput.slice(0, MAX_OUTPUT_LENGTH);
20+
const normalizedValue = `${String(value)}\n`;
21+
22+
if (normalizedValue.length <= availableLength) {
23+
return current + normalizedValue;
24+
}
1725

18-
return `${truncatedOutput}\n${TRUNCATION_NOTICE}`;
26+
return (
27+
current + normalizedValue.slice(0, availableLength) + TRUNCATION_NOTICE
28+
);
1929
}

‎scripts/python-runner/validation.js‎

Lines changed: 30 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { readVariable } from "./runtime.js";
22

33
const VALIDATED_VARIABLE = "__pyschool_validated";
4+
45
const FEEDBACK_VARIABLE = "__pyschool_feedback";
56

67
const DEFAULT_TOLERANCE = 0.000001;
@@ -21,6 +22,20 @@ function createIncompleteResult(challenge) {
2122
};
2223
}
2324

25+
function getTolerance(challenge) {
26+
const tolerance = challenge.tolerance ?? DEFAULT_TOLERANCE;
27+
28+
if (
29+
typeof tolerance !== "number" ||
30+
!Number.isFinite(tolerance) ||
31+
tolerance < 0
32+
) {
33+
throw new Error("La tolerancia del desafío no es válida.");
34+
}
35+
36+
return tolerance;
37+
}
38+
2439
async function validatePythonChallenge(runtime, namespace, challenge) {
2540
namespace.set(VALIDATED_VARIABLE, false);
2641
namespace.set(FEEDBACK_VARIABLE, "");
@@ -57,9 +72,21 @@ function validateEqualsChallenge(result, challenge) {
5772

5873
function validateNumberChallenge(result, challenge) {
5974
const value = result.value;
60-
const isNumber = typeof value === "number" && Number.isFinite(value);
61-
const tolerance = challenge.tolerance ?? DEFAULT_TOLERANCE;
62-
const passed = isNumber && Math.abs(value - challenge.expected) < tolerance;
75+
76+
if (typeof value !== "number" || !Number.isFinite(value)) {
77+
return createValidationResult(false, challenge);
78+
}
79+
80+
if (
81+
typeof challenge.expected !== "number" ||
82+
!Number.isFinite(challenge.expected)
83+
) {
84+
throw new Error("El valor esperado del desafío no es válido.");
85+
}
86+
87+
const tolerance = getTolerance(challenge);
88+
89+
const passed = Math.abs(value - challenge.expected) <= tolerance;
6390

6491
return createValidationResult(passed, challenge);
6592
}

‎src/components/station/editor/PythonEditor.astro‎

Lines changed: 5 additions & 84 deletions
Original file line numberDiff line numberDiff line change
@@ -17,20 +17,18 @@ interface Props {
1717
const { starterCode, challenge } = Astro.props;
1818
1919
const resolvedChallenge = challenge
20-
? {
20+
? ({
2121
...challenge,
22-
2322
successHref: challenge.successHref
2423
? withBase(challenge.successHref)
2524
: undefined,
26-
2725
dataset: challenge.dataset
2826
? {
2927
...challenge.dataset,
3028
source: withBase(challenge.dataset.source),
3129
}
3230
: undefined,
33-
}
31+
} satisfies Challenge)
3432
: undefined;
3533
---
3634

@@ -48,13 +46,9 @@ const resolvedChallenge = challenge
4846
data-expanded="false"
4947
>
5048
<PythonEditorHeader />
51-
5249
<PythonErrorHelp />
53-
5450
<PythonEditorWorkspace />
55-
5651
<PythonEditorActions />
57-
5852
<PythonTerminal />
5953
</div>
6054

@@ -63,7 +57,9 @@ const resolvedChallenge = challenge
6357
src={withBase("/python-runner.html")}
6458
sandbox="allow-scripts"
6559
referrerpolicy="no-referrer"
66-
class="hidden"
60+
tabindex="-1"
61+
aria-hidden="true"
62+
hidden
6763
data-python-runner></iframe>
6864
</div>
6965

@@ -72,78 +68,3 @@ const resolvedChallenge = challenge
7268

7369
initializePythonEditors();
7470
</script>
75-
76-
<style>
77-
[data-python-editor-shell][data-expanded="true"] {
78-
position: fixed;
79-
z-index: 100;
80-
inset: 1rem;
81-
display: flex;
82-
overflow: hidden;
83-
flex-direction: column;
84-
background: white;
85-
box-shadow: 0 24px 80px
86-
color-mix(in srgb, var(--color-ink) 30%, transparent);
87-
}
88-
89-
[data-python-editor-shell][data-expanded="true"] [data-python-workspace] {
90-
min-height: 18rem;
91-
flex: 1;
92-
overflow: hidden;
93-
}
94-
95-
[data-python-editor-shell][data-expanded="true"] [data-python-code-panel] {
96-
height: 100%;
97-
min-height: 0;
98-
}
99-
100-
[data-python-editor-shell][data-expanded="true"] [data-python-code] {
101-
height: 100%;
102-
}
103-
104-
[data-python-editor-shell][data-expanded="true"] [data-python-results] {
105-
display: flex;
106-
max-height: 42dvh;
107-
min-height: 15rem;
108-
flex-direction: column;
109-
}
110-
111-
[data-python-editor-shell][data-expanded="true"]
112-
[data-python-terminal-content] {
113-
min-height: 0;
114-
flex: 1;
115-
overflow: auto;
116-
}
117-
118-
[data-python-editor-shell][data-expanded="true"]
119-
[data-python-terminal-panel] {
120-
min-height: 100%;
121-
}
122-
123-
[data-python-editor-shell][data-expanded="true"]
124-
[data-python-editor-expand-icon] {
125-
transform: scale(0.82) rotate(180deg);
126-
}
127-
128-
@media (max-width: 39.999rem) {
129-
[data-python-editor-shell][data-expanded="true"] {
130-
inset: 0;
131-
border: 0;
132-
}
133-
134-
[data-python-editor-shell][data-expanded="true"] [data-python-workspace] {
135-
min-height: 14rem;
136-
}
137-
138-
[data-python-editor-shell][data-expanded="true"] [data-python-results] {
139-
max-height: 48dvh;
140-
min-height: 13rem;
141-
}
142-
}
143-
144-
@media (prefers-reduced-motion: reduce) {
145-
[data-python-editor-expand-icon] {
146-
transition: none;
147-
}
148-
}
149-
</style>
Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
<div class="h-96 min-h-80 min-w-0 lg:h-128" data-python-workspace>
1+
<div class="h-96 min-w-0 lg:h-128" data-python-workspace>
22
<div class="h-full min-w-0 overflow-hidden bg-white" data-python-code-panel>
3-
<div class="h-full overflow-hidden bg-white" data-python-code></div>
3+
<div class="h-full overflow-hidden" data-python-code></div>
44
</div>
55
</div>

‎src/components/station/editor/constants.ts‎

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,16 @@
11
import type { Challenge } from "./types";
22

33
export const MAX_EXECUTION_TIME = 3_000;
4+
45
export const MAX_DATA_EXECUTION_TIME = 10_000;
6+
57
export const MAX_CHART_EXECUTION_TIME = 15_000;
8+
69
export const MAX_LOADING_TIME = 90_000;
710

811
export const MAX_DATASET_SIZE_BYTES = 2_000_000;
912

10-
export function getExecutionTime(challenge?: Challenge) {
13+
export function getExecutionTime(challenge?: Challenge): number {
1114
if (challenge?.packages?.includes("matplotlib")) {
1215
return MAX_CHART_EXECUTION_TIME;
1316
}
Lines changed: 26 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,36 +1,36 @@
11
export type PythonPackage = "pandas" | "matplotlib";
22

3-
type Dataset = {
4-
source: string;
5-
fileName: string;
3+
export type ChallengeDataset = {
4+
readonly source: string;
5+
readonly fileName: string;
66
};
77

8-
type ChallengeBase = {
9-
successMessage: string;
10-
errorMessage: string;
11-
incompleteMessage?: string;
12-
successHref?: string;
13-
successLabel?: string;
14-
packages?: PythonPackage[];
15-
dataset?: Dataset;
8+
export type ChallengeBase = {
9+
readonly successMessage: string;
10+
readonly errorMessage: string;
11+
readonly incompleteMessage?: string;
12+
readonly successHref?: string;
13+
readonly successLabel?: string;
14+
readonly packages?: readonly PythonPackage[];
15+
readonly dataset?: ChallengeDataset;
1616
};
1717

18-
export interface EqualsChallenge extends ChallengeBase {
19-
type: "equals";
20-
variable: string;
21-
expected: string | number | boolean;
22-
}
18+
export type EqualsChallenge = ChallengeBase & {
19+
readonly type: "equals";
20+
readonly variable: string;
21+
readonly expected: string | number | boolean;
22+
};
2323

24-
export interface NumberChallenge extends ChallengeBase {
25-
type: "number";
26-
variable: string;
27-
expected: number;
28-
tolerance?: number;
29-
}
24+
export type NumberChallenge = ChallengeBase & {
25+
readonly type: "number";
26+
readonly variable: string;
27+
readonly expected: number;
28+
readonly tolerance?: number;
29+
};
3030

31-
export interface PythonChallenge extends ChallengeBase {
32-
type: "python";
33-
validator: string;
34-
}
31+
export type PythonChallenge = ChallengeBase & {
32+
readonly type: "python";
33+
readonly validator: string;
34+
};
3535

3636
export type Challenge = EqualsChallenge | NumberChallenge | PythonChallenge;

‎templates/python-runner.html‎

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,23 @@
22
<html lang="es">
33
<head>
44
<meta charset="UTF-8" />
5+
<meta name="referrer" content="no-referrer" />
6+
57
<meta
68
http-equiv="Content-Security-Policy"
7-
content="default-src 'none'; script-src 'unsafe-inline' 'wasm-unsafe-eval' https://cdn.jsdelivr.net; connect-src https://cdn.jsdelivr.net; style-src 'unsafe-inline'; img-src 'none'; font-src 'none'; worker-src blob:; child-src blob:; base-uri 'none'; form-action 'none'"
9+
content="
10+
default-src 'none';
11+
script-src 'unsafe-inline' 'wasm-unsafe-eval' https://cdn.jsdelivr.net;
12+
connect-src https://cdn.jsdelivr.net;
13+
worker-src blob:;
14+
img-src 'none';
15+
font-src 'none';
16+
base-uri 'none';
17+
form-action 'none';
18+
object-src 'none';
19+
"
820
/>
21+
922
<title>Python Runner</title>
1023
</head>
1124

0 commit comments

Comments
 (0)