Files and databases

save and load keep a value under a name. When a program has more to keep, it can write a file, or keep a table of rows in a database. Both are still there the next time the program runs.

notes = load_text("notes.txt", "")      # what the file holds, or "" the first time

def on_key_down(key):
    global notes
    if len(key) == 1:                   # a letter, a number or a space
        notes += key
        save_text("notes.txt", notes)

def draw():
    background("midnightblue")
    text(notes, 20, 40, "white", 22)

Click the preview and type a few words, then press Run again, or close the file and open it again. They are still there.

Text files and JSON files

CallWhat it does
save_text(path, text)Writes a text file. If the file is there already, this replaces it.
load_text(path, otherwise)Gives back the text in the file. If there is no such file, gives otherwise.
save_json(path, value)Writes a value as a JSON file: a number, text, True or False, a list or a dictionary.
load_json(path, otherwise)Gives back the value in a JSON file. If there is no such file, gives otherwise.

All four answer at once, so there is no await.

Always give load_text and load_json their second value. The first time a program runs there is no file yet, and that value is what you get. Left out, you get None.

A JSON file holds the same kinds of value save keeps, so a whole level, or a drawing, fits in one:

level = load_json("level.json", {"walls": [], "start": [40, 40]})

def on_mouse_down(x, y):
    level["walls"].append([x, y])
    save_json("level.json", level)

def draw():
    background("black")
    for x, y in level["walls"]:
        rect(x - 10, y - 10, 20, 20, "orange")

Something that cannot be written as JSON, such as a function or a sprite, stops the scene with an error that says so. So does save_text when it is given something that is not text: use str(score) to make text of a number, or use save_json. A JSON file that is there but has a mistake in it stops the scene with an error that says where.

Paths

A path is from the program's own folder: "notes.txt" is a file beside the program. It can name a folder, and the folder is made if it is not there:

save_json("levels/one.json", level)

A path cannot leave the scene's folder. A / at the front is dropped, and so are . and .., so "../../notes.txt" is "notes.txt".

In Circuitry a program writes data, not programs. A path ending in .py, .js, .html or another kind of file that is run is not kept, and neither is a hidden file (a name starting with .) or one of Circuitry's own documents: the log says which file was not kept, and the program carries on. Give the files a program writes endings such as .txt, .json and .db.

Databases

A database keeps rows in tables, like a spreadsheet you can ask questions of: the five best scores, every word that starts with "a", how many games were played today. Pyctures uses SQLite, the database inside most phones and browsers, and you talk to it in SQL.

db = open_database("scores")            # the file scores.db, made the first time
db.run("create table if not exists best (name, points)")

db.run("insert into best values (?, ?)", "Ann", 12)
db.run("insert into best values (?, ?)", "Bo", 7)

rows = db.query("select name, points from best where points > ? order by points desc", 10)
for row in rows:
    print(row["name"], row["points"])   # Ann 12
CallWhat it does
open_database(name)Opens a database, making it if it is new. A name with no ending gets .db: "scores" is the file scores.db. Several can be open at once.
db.query(sql, values...)Asks a question. Gives a list of rows, each a dictionary by column name. No rows is an empty list.
db.run(sql, values...)Makes a change: create table, insert, update, delete. Gives the number of rows it changed.
db.close()Finishes with the database. What it holds is kept.

Put values in with ?. Each ? in the SQL takes the next value after it, in order. Do not build the SQL out of the values yourself: a name with a ' in it would break it, and ? never does.

db.run("insert into best values (?, ?)", name, points)     # yes
db.run(f"insert into best values ('{name}', {points})")    # no

The values can also be given as one list: db.query("select * from best where points > ?", [10]).

create table if not exists at the top of the program makes the table the first time and does nothing after that, so the same program works on its first run and every later one.

When SQL has a mistake in it, the scene stops with an error that shows the SQL and what SQLite said about it, such as no such column: nme.

You do not have to call close(). What a frame changes is kept when the frame ends, however many runs it made, and a scene that stops closes its databases.

Where they are kept

That depends on where Circuitry is running. The program is the same in each.

Where the program runsWhere its files and databases are
The Circuitry app on an iPhone, iPad or Android deviceReal files beside the program, in the folder its file is saved in
The desktop app, for a program saved on your computer or on a paired CircuitReal files beside the program
Circuitry in a web browser, and a desktop program that is not saved yetIn the browser, on that device
A page made with File, Export as Web PageIn the browser that opens the page
  • Real files are ones you can see. save_text("notes.txt", ...) writes notes.txt beside the program and open_database("scores") makes scores.db there. They show in Circuitry's file browser, and on an iPhone or iPad in the Files app, so you can open, copy and share them. A program saved in iCloud Drive or in a linked folder keeps its files there too.
  • A program that is not saved yet, on a phone or tablet, uses the folder documents are saved to on the device. Save the program into a folder of its own to keep its files together.
  • Programs in the same folder share its files. Two programs that both write notes.txt beside themselves write the same file. In a browser each program has its own.
  • A workflow can read the same database. On an iPhone or iPad, a Database node set to a database on the device, with the same name, in the same folder, opens the same file. Do not change it from a program and from a workflow at the same moment: a program writes the whole file each time.
  • In a browser nothing is sent anywhere. The files are kept on the computer, tablet or phone you are using, for this program alone, until Circuitry's data on the device is cleared. Circuitry on another device starts with nothing.
  • It is the same whichever Python runs the program: the one in the page, the built-in Python on an iPhone or iPad, or Circuit's on a computer.

Everything the program has stored is handed to Python before it starts. That is why load_text can answer at once, and why it is best to keep what you store small: a few files, a database of thousands of rows rather than millions. A text file can hold 5 million characters and a database 32 MB; one bigger than 16 MB is not handed over at the start, and the log says so. A database is kept whole each time a frame changes it, so change it when something happens (a game ends, a level is saved), not on every frame.

A file that comes with the program

A program can come with a file of its own: a level.json you made, or a database already filled in. Put it beside the program's file, and name it in the program as a quoted path: load_json("level.json", {}), open_database("scores"). Circuitry reads the files a program names that way before it starts. A name the program builds while it runs, such as f"level{n}.json", is found only once the program has saved that file itself.

Where the files are real ones beside the program, a file that came with it and one the program saves under the same name are the same file. In a browser, load_json("level.json", {}) reads the saved one.

Things to know

  • Read what you saved with load_text and load_json. Python's own open() is another way to files, and finds them only with import files (Using your files).
  • A file is found by its quoted name. load_text("words.txt", "") finds words.txt beside the program. load_text(name, ""), with the name in a variable, finds it once the program has saved it.
  • The page's Python loads SQLite for a program whose own file says open_database. If the call is tucked away in a module the program imports, mention open_database in the program too.
  • A file that could not be kept is said in the log, once for each file, and the program carries on with what it has.
  • A program open in two tabs, or on two devices, has two copies of a database in memory, and the one that changes it last is the one that is kept.
  • Outside Circuitry the same functions work with the Pyctures library: real files beside the scene with pyctures run, the browser on a web page (Outside Circuitry).