Writing a book
A book is a folder of Markdown pages. Atlas reads that folder from the books checkout. It does not store the lessons in the app.
Where a book lives
The books repo has one folder per book, plus a shared images folder.
books/ PythonAI/md/ one book assets/ images any book can use
A new book is a new folder next to PythonAI. Its pages live in md/. Push to main. The public server pulls that repo and restarts within a few minutes. A push does not update the site by itself before that pull.
The app has to know the folder name before students can open the book. PythonAI is wired to /read/python-ai. Adding the folder is not enough for a second book.
A page
The file name is the page address. basics/values.md is /read/python-ai/basics/values. Use letters, numbers, hyphens, and underscores. The first heading is the page title.
# Names and printing A name points at a value. `name = "Ada"` stores the text "Ada". ## A name stores a value - A string is text, written in quotes. - A number is written without quotes. > The equals sign stores. It does not compare.
Ordinary Markdown is enough for the reading part of a page:
#through######for headings.- Paragraphs, separated by a blank line.
-for a bullet list, and1.for a numbered list.>for a quotation.`inline code`,**bold**, and a normal link.- A fenced code block with a language, such as
```python, is a listing. Students cannot edit or run it.
The contents page
index.md is not a lesson. It lists the pages or lessons under it. Do not put teaching prose there. The file name in the list has no .md.
# Python basics
```{toctree}
:caption: In this lesson
:maxdepth: 1
values
functions
lists
```A lesson index uses the same list. basics/index.md listing values opens basics/values.md.
Notes in the prose
:::{admonition} Quotes mark text
"Ada" is a value. Ada without quotes is a name.
:::
:::{check}
Try the example before you change the exercise.
:::An admonition is a titled note. A check is a short reminder before the student starts the exercise. Both close with a line that is only :::.
A figure
Put the image in assets/ in the books repo. The page names it from the site root. Do not paste a long external image URL when the file can live with the book.
:::{figure} /books/assets/PythonAI/folder.png
:alt: A folder icon
:width: 120px
A file lives in a folder.
::::alt: is the text a reader hears if the image does not load. :width: is optional. The caption is the text after the options.
A video
```{video}
:src: https://player.vimeo.com/video/123456789
:title: What a name stores
:caption: Two minutes on assignment.
```Ids
Every exercise has an id, the word after the widget name. It must be unique on that page. Atlas uses it to save the attempt. Do not rename an id after students have started. Their saved work is tied to the old one.
:practice: true marks a question as practice. The student still gets feedback. It is not the graded exercise for the page.
Active code
This is the cell where the student writes and runs code. The text before ~~~~ is the instruction. The text between ~~~~ and ==== is the starter. Everything after ==== is the hidden test. The student never sees the test.
```{activecode} py-label
:lang: python
Write a function named label that returns "Name: " joined with the name.
~~~~
def label(name):
return ""
====
from unittest.gui import TestCaseGui
class Tests(TestCaseGui):
def test_label(self):
self.assertEqual(label("Ada"), "Name: Ada")
Tests().main()
```If you leave out ~~~~, the whole body before ==== is the starter and the cell has no separate instruction. :lang: defaults to Python. JavaScript, HTML, and SQL use the same widget with :lang: javascript, :lang: html, or :lang: sql. A SQL cell that should read the shared textbook database adds :dburl: /_static/textbook.sqlite3. That database is read-only.
The hidden test uses TestCaseGui:
self.assertEqual(actual, expected)self.assertNotEqual(actual, expected)self.assertTrue(value)andself.assertFalse(value)self.assertIn(item, container)self.assertAlmostEqual(actual, expected)for numbers that are close.
End the test with Tests().main(). A passing test compares the returned value, not text the program printed, unless the question is specifically about printing.
Several files
Name each file on its ~~~~ line. :entry: is the file that runs. :keep: is a comma-separated list of files the student cannot edit. :add: files lets the student create another file.
```{activecode} py-split
:entry: greetings.py
:keep: main.py
Write greet in greetings.py.
~~~~ greetings.py
def greet(name):
return ""
~~~~ main.py
from greetings import greet
print(greet("Ada"))
====
from unittest.gui import TestCaseGui
class Tests(TestCaseGui):
def test_greet(self):
self.assertEqual(greet("Ada"), "Hello, Ada")
Tests().main()
```A file name is one word: letters, numbers, dots, hyphens, and underscores. It cannot contain a slash. A CSV named this way can be read by the program when it runs.
Ask, then run
promptcode is the cell where the student practices asking. Ask and Run are separate. Ask sends the brief, the student's prompt, and the visible files. It does not send the hidden tests. Run is what decides the grade.
```{promptcode} py-ask-greet
:feedback: prompt
Ask for a function named greet that takes a name and returns Hello, a space, and that name.
~~~~
def greet(name):
return ""
====
from unittest.gui import TestCaseGui
class Tests(TestCaseGui):
def test_greet(self):
self.assertEqual(greet("Ada"), "Hello, Ada")
Tests().main()
```:lock: prompt makes the editor read-only, so only Ask can change it. :feedback: prompt adds a second note about the student's prompt. That note does not decide the grade. Named files, :entry:, :keep:, and :add: files work the same way as in active code.
Multiple choice
[x] is a correct choice. [ ] is not. One or more choices may be correct. The indented :feedback: line is shown for that choice.
```{mchoice} py-basics-names-q
:practice: true
What does print do?
- [x] It shows a value in the output.
:feedback: print writes to the output. It does not return that value.
- [ ] It stores a name.
:feedback: A name is stored with =.
```Fill in the blank
Put |blank| in the prompt once for each hole, in order. Under the prompt, one bullet starts each hole. A line :answer: feedback is an accepted answer. A line :x: feedback is the message for anything else. :casei: accepts any capitalization. A numeric answer may be a tolerance, written as the value, a space, and the allowed distance, such as :2 1:.
```{fillintheblank} py-fitb-sample
:casei:
The function that hands a value back is |blank|.
- :return: return hands the value back.
:print: print shows a value. It does not hand it back.
:x: The keyword is return.
```Matching
Each pair is one bullet. The left side and the right side are separated by |||. The student sees the right side shuffled.
```{dragndrop} py-ask-match
:feedback: Ask sends the prompt. Run checks the result.
Match each step with what it is for.
- Ask ||| Sends your prompt so the model can draft the function.
- Run ||| Calls the function and compares the result.
```Clickable code
Wrap a correct span in :click-correct: and :endclick:. Wrap a tempting wrong span in :click-incorrect: and :endclick:. :iscode: true shows the body as code. :question: is the instruction. :feedback: is the note after a wrong check.
```{clickablearea} py-basics-click
:practice: true
:iscode: true
:question: Click the line that stores a value.
:feedback: The equals sign stores a value under a name.
:click-correct:name = "Ada":endclick:
:click-incorrect:print(name):endclick:
```Put the lines in order
The prompt ends at a line that is only -----. Each block ends at a line that is only =====. The blocks are the correct order. A block whose text ends in #distractor is shown to the student but is not part of the answer. Indentation inside a block matters.
```{parsons} py-functions-order
:practice: true
Put the lines in an order that defines double and prints 8.
-----
def double(number):
=====
return number * 2
=====
print(double(4))
=====
print(number) #distractor
```A step-through trace
CodeLens shows the code and lets the student step through it. It is not graded.
```{codelens} py-values-trace
name = "Ada"
print(name)
```What the student does not see
- Hidden tests, the text after a line that is only
====. - The markers
:click-correct:and:click-incorrect:. The student sees the code, not the markers. #distractoron a Parsons line. The line is visible. The marker is not.
An index page shows only its contents list. The prose in an index.md is not the lesson.