Add multi-course support and Python notebook courses to QDK Learning#3394
Add multi-course support and Python notebook courses to QDK Learning#3394minestarks wants to merge 1 commit into
Conversation
| "The function just needs to return a value equal to the integer 42.", | ||
| "The simplest answer is to return the literal `42` itself." | ||
| ], | ||
| "solution": "@exercise\ndef forty_two():\n return qsharp.eval(\"40 + 2\")", |
There was a problem hiding this comment.
In the katas, we sometimes found that we wanted to provide multiple solutions. If this is a generalization, we might want that here?
| "from qdk.widgets import Circuit\n", | ||
| "\n", | ||
| "# Define a few operations we'll use throughout this unit.\n", | ||
| "OPERATIONS = \"\"\"\n", |
There was a problem hiding this comment.
I'm not familiar enough with python to suggest an alternative, but this seems like a painful level of escaping.
There was a problem hiding this comment.
I guess I don't actually know how this got here. Maybe you never actually touch this file and the escaping is done for you when it's persisted?
| "id": "circuit-diagrams", | ||
| "title": "Generating Circuit Diagrams", | ||
| "shortDescription": "Build and visualize quantum circuits with the QDK in Python notebooks.", | ||
| "readme": "README.md", |
There was a problem hiding this comment.
Why reference the readme but not the project? They seem similar to me, but I may be missing something.
There was a problem hiding this comment.
We probably don't need to reference either - they have well-known names.
| > Quantum Katas (_kaˑta_ | kah-tuh — Japanese for "form", a pattern of learning and practicing new skills) are self-paced, AI-assisted tutorials for quantum computing and Q# programming. Each tutorial includes relevant theory and interactive hands-on exercises designed to test knowledge. | ||
|
|
||
| The tools refer to each kata as a "unit." Each unit contains ordered activities (lessons, examples, exercises). | ||
| The tools refer to each unit of a course as a "unit." Each unit contains ordered activities (lessons, examples, exercises). |
There was a problem hiding this comment.
This change makes it a little tautological.
| **Handling guidance:** | ||
|
|
||
| - When the user asks to change courses, call `list-courses` first if you're unsure of the exact `courseId`, match the user's request to a course, then call `switch-course`. After switching, call `show` to surface the new course's current activity and briefly tell the user where they landed. | ||
| - Drop-in courses run author-provided code and only load in a **trusted** workspace. If a drop-in course doesn't appear or won't run, the workspace may be in Restricted Mode — suggest trusting the workspace. |
There was a problem hiding this comment.
I'm a little nervous about "suggest trusting the workspace". Maybe "explain that the user will need to trust the workspace to continue and should only do so if they trust the course material provider"?
There was a problem hiding this comment.
This is moot - our extension is entirely disabled in untrusted workspaces
|
|
||
| // Prefer `uv` when it's available — it's faster and is the modern | ||
| // default tooling. Fall back to the standard library `venv` module. | ||
| if (await this.uvAvailable()) { |
There was a problem hiding this comment.
This may be covered below, but I'm wondering if we want a way for users to force venv, rather than uv, even if the latter is available.
| "This Python installation can't create virtual environments " + | ||
| "(the `venv`/`ensurepip` modules are missing). On Debian/Ubuntu " + | ||
| "install them with `sudo apt install python3-venv` (matching your " + | ||
| "Python version, e.g. `python3.12-venv`), then try again.", |
There was a problem hiding this comment.
Don't we know their python version?
| * are typed here. | ||
| */ | ||
| private async pythonEnvironmentsApi(): Promise< | ||
| | { |
| export const KATAS_COURSE_ID = "katas"; | ||
|
|
||
| /** Per-course virtual environment folder (under the course working copy). */ | ||
| export const LEARNING_VENV_DIR = ".venv"; |
There was a problem hiding this comment.
Would there be any value in making this name more unique?
There was a problem hiding this comment.
Seems like it's in a folder people won't be editing and it might break notebook discovery, so probably not?
There was a problem hiding this comment.
Aside: the default discovery system only looks in the root and one level down - it doesn't recursively search for .venv. There may be a workspace setting to override it
| } | ||
|
|
||
| /** Read and JSON-parse a course manifest, or `undefined` if absent/invalid. */ | ||
| private async readManifest( |
There was a problem hiding this comment.
Remind me what guardrails we need when reading untrusted (?) files from disk. Or maybe this only works if they're in a trusted folder?
| import * as vscode from "vscode"; | ||
| import type { LearningService } from "./service.js"; | ||
|
|
||
| /** |
There was a problem hiding this comment.
The way to check whether a cell is an exercise is to refer to the exercies.json metadata
amcasey
left a comment
There was a problem hiding this comment.
The old kata notebooks at github.com/microsoft/quantumkatas may be useful references
| } | ||
| ], | ||
| "environment": { | ||
| "importChecks": ["qdk", "qdk.widgets"] |
There was a problem hiding this comment.
Could we discover these from the cells?
There was a problem hiding this comment.
Per our offline discussion, that's probably overkill
| "id": "circuit-diagrams", | ||
| "title": "Generating Circuit Diagrams", | ||
| "shortDescription": "Build and visualize quantum circuits with the QDK in Python notebooks.", | ||
| "readme": "README.md", |
There was a problem hiding this comment.
We probably don't need to reference either - they have well-known names.
| @@ -0,0 +1,10 @@ | |||
| [project] | |||
There was a problem hiding this comment.
This (using a standard python project format) is part of achieving our goal that you could get the basic course experience in any jupyter environment.
| "exercises": [ | ||
| { | ||
| "id": "forty_two", | ||
| "cellIndex": 7, |
There was a problem hiding this comment.
This seems brittle. Is the cell ID more stable?
| won't start, or the first cell reports a problem, set up and check your | ||
| environment here first: | ||
|
|
||
| 👉 [Check my environment](command:qsharp-vscode.learningDoctor) |
There was a problem hiding this comment.
This needs to be on screen somewhere, in case the notebook doesn't work. This file is basically an excuse to do that - we could find a different place for it.
| **Handling guidance:** | ||
|
|
||
| - When the user asks to change courses, call `list-courses` first if you're unsure of the exact `courseId`, match the user's request to a course, then call `switch-course`. After switching, call `show` to surface the new course's current activity and briefly tell the user where they landed. | ||
| - Drop-in courses run author-provided code and only load in a **trusted** workspace. If a drop-in course doesn't appear or won't run, the workspace may be in Restricted Mode — suggest trusting the workspace. |
There was a problem hiding this comment.
This is moot - our extension is entirely disabled in untrusted workspaces
| } | ||
| ], | ||
| "view/item/context": [ | ||
| { |
There was a problem hiding this comment.
This hasn't been reviewed by humans.
| export const KATAS_COURSE_ID = "katas"; | ||
|
|
||
| /** Per-course virtual environment folder (under the course working copy). */ | ||
| export const LEARNING_VENV_DIR = ".venv"; |
There was a problem hiding this comment.
Aside: the default discovery system only looks in the root and one level down - it doesn't recursively search for .venv. There may be a workspace setting to override it
| @@ -0,0 +1,98 @@ | |||
| // Copyright (c) Microsoft Corporation. | |||
There was a problem hiding this comment.
Might be mergeable with catalog.ts
This change generalizes the QDK Learning feature to support multiple courses of different kinds, including author-provided Python Jupyter notebook courses, while keeping the Quantum Katas as the default.