# settings


<!-- WARNING: THIS FILE WAS AUTOGENERATED! DO NOT EDIT! -->

Each value that a person can change is a row in one table, `SETTINGS`. A
row has a group, a name, a label, one line of help, a type, a default
and, for a number, a range. The engine, the web app and the Settings
page all read this table. Thus each value has one place.

Your changes go in `settings.json` in
[`cfg_dir()`](https://vedicreader.github.io/abhyasa/settings.html#cfg_dir).
The code reads a value when it uses it, so a change works at once, with
no restart. A saved value outside its range gets the nearest end of the
range. A saved value of the wrong type gets the default. A bad file
never stops the app.

This module imports only the standard library and fastcore. Each engine
module reads its values from here, so the import must stay light.

## The groups

The Settings page shows the groups in this order. Each group has a label
and one lead line.

## The table

[`add`](https://vedicreader.github.io/abhyasa/settings.html#add) puts
one row in `SETTINGS`. The name is `section.key`: the section of
`settings.json` and the key in it. The type comes from the default.
`more` puts the row under the closed “More” part of its group.
`store='profile'` marks a value that the vault keeps in your profile,
not this file. `hi` can be the name of another row. Then that value is
the top of the range. `ui=False` marks a value that its group shows with
its own controls. `reset=False` keeps a value when you use the defaults
of its group: the theme, and the speech engine and model.

------------------------------------------------------------------------

<a
href="https://github.com/vedicreader/abhyasa/blob/main/abhyasa/settings.py#L35"
target="_blank" style="float:right; font-size:smaller">source</a>

### add

``` python
def add(
    name:str, group:str, label:str, help:str, dflt, lo:NoneType=None, hi:NoneType=None, more:bool=False,
    choices:NoneType=None, unit:str='', pct:bool=False, store:str='file', ui:bool=True, reset:bool=True
):
```

*Add the setting `name` to `SETTINGS` and return its row.*

The **Look** group keeps its own controls: the theme swatches and the
mode. The **Learning** group holds the three values of your profile.

The **Walks** group sets the size of a walk and of a side walk.

The **Talks** group sets how a talk runs: the questions, the time, the
vote and the room for phones.

The **Checks and grading** group sets the tries, the grade of a shaky
pass, and when an idea counts as learned.

The **Review** group sets the time to the next review. A daily limit on
review is new: 0 means no limit, as before.

The **Links** group sets how much of a link the tutor gets. The pages
read by default can not be more than the page cap.

The **Research** group sets the web search for a step with no source.
The tutor searches the web for the idea, reads the best pages, and
quotes one passage word for word. When research is off, the tutor does
no web search, and the step says that the text is in the tutor’s words.

The **Code runner** group sets the time limits of a run. The kill time
and the CPU limit follow the total time: total + 2 s and total + 1 s.

The **Speech to text** group keeps the engine and the model, and the
pauses at which the mic cuts what you say.

The **Models** group has the default model and one model for each task.
An empty value uses the default model. The tasks after
[`extract`](https://vedicreader.github.io/abhyasa/sources.html#extract)
borrow the model of an older task when they have none: `BORROWS` says
which. The reply caps are in tokens.

The **Lists** group sets how many items show in the lists of the app.
All of it is under “More”.

Each group except **Prompts** has rows, and each row is in a group. The
Prompts group lists the prompts of `prompts.PROMPTS`.

``` python
assert set(GROUPS) - {'prompts'} == {r.group for r in SETTINGS.values()}
assert all(r.kind != 'int' or r.lo <= r.dflt for r in SETTINGS.values())
len(SETTINGS)
```

## The file

`DFLT_SETTINGS` is the default of each section: the rows that the file
keeps, the saved prompt texts and the models that the Settings page
downloaded.
[`settings()`](https://vedicreader.github.io/abhyasa/settings.html#settings)
gives the saved settings over the defaults. A section that is not a dict
in the file is ignored, and a top-level key that is not known is
dropped.

------------------------------------------------------------------------

<a
href="https://github.com/vedicreader/abhyasa/blob/main/abhyasa/settings.py#L158"
target="_blank" style="float:right; font-size:smaller">source</a>

### cfg_dir

``` python
def cfg_dir()->pathlib.Path:
```

*The folder for abhyasa’s settings and keys: `$ABHYASA_CONFIG`, else
`~/.config/abhyasa`.*

[`_saved`](https://vedicreader.github.io/abhyasa/settings.html#_saved)
reads the file only when it changed, so a value read in a loop costs one
`stat`.

------------------------------------------------------------------------

<a
href="https://github.com/vedicreader/abhyasa/blob/main/abhyasa/settings.py#L183"
target="_blank" style="float:right; font-size:smaller">source</a>

### settings

``` python
def settings()->dict:
```

*The saved settings over the defaults.*

[`save_settings`](https://vedicreader.github.io/abhyasa/settings.html#save_settings)
writes the file: first a temporary file with its own name, then a
rename, so a crash never leaves half a file. A lock keeps two changes
from the web app apart. It does not keep a value that is the default.
[`update_settings`](https://vedicreader.github.io/abhyasa/settings.html#update_settings)
changes keys in one section.

------------------------------------------------------------------------

<a
href="https://github.com/vedicreader/abhyasa/blob/main/abhyasa/settings.py#L211"
target="_blank" style="float:right; font-size:smaller">source</a>

### update_settings

``` python
def update_settings(
    section:str, **kw
)->dict:
```

*Change some keys in one section of the settings.*

------------------------------------------------------------------------

<a
href="https://github.com/vedicreader/abhyasa/blob/main/abhyasa/settings.py#L195"
target="_blank" style="float:right; font-size:smaller">source</a>

### save_settings

``` python
def save_settings(
    d:dict
)->dict:
```

*Write all the settings.*

With no file,
[`settings()`](https://vedicreader.github.io/abhyasa/settings.html#settings)
gives the defaults. A change in one section keeps the others.

``` python
test_eq(settings()['walks']['steps'], 12)
update_settings('stt', segment=False)
test_eq(settings()['stt']['segment'], False)
test_eq(settings()['look'], dict(theme='paper', mode='auto'))
update_settings('stt', segment=True)
```

A file from before this table still loads: its sections update the
defaults, and the new sections get their defaults.

``` python
_path().write_text(json.dumps(dict(stt=dict(engine='mlx', model='m', segment=True), models=dict(question='', grade='', hint='', lesson=''),
                                   tokens=dict(question='', grade='', hint='', lesson='', extract=''), look=dict(theme='ink', mode='dark'), prompts={}, downloaded=[])))
d = settings()
test_eq((d['stt']['engine'], d['stt']['pause'], d['look']['theme'], d['walks']['steps'], d['models']['extract']), ('mlx', 700, 'ink', 12, ''))
save_settings(dict(prompts={}, downloaded=[]))
```

A file that is not JSON, or a section that is not a dict, gives the
defaults. An unknown top-level key is dropped.

``` python
_path().write_text('{not json')
test_eq(settings()['walks']['steps'], 12)
_path().write_text(json.dumps(dict(walks=5, mine=1)))
d = settings()
test_eq((d['walks']['steps'], 'mine' in d), (12, False))
save_settings(dict(prompts={}, downloaded=[]))
```

## Read one value

[`fit`](https://vedicreader.github.io/abhyasa/settings.html#fit) makes a
saved value fit its row. A number outside the range gets the nearest
end. A value of the wrong type gets the default. A whole number can come
as `12.0` from a form; it stays a whole number. A choice must be one of
the choices.

------------------------------------------------------------------------

<a
href="https://github.com/vedicreader/abhyasa/blob/main/abhyasa/settings.py#L222"
target="_blank" style="float:right; font-size:smaller">source</a>

### fit

``` python
def fit(
    r, v
):
```

*The value `v` made to fit row `r`: clamped to the range, or the default
for the wrong type.*

------------------------------------------------------------------------

<a
href="https://github.com/vedicreader/abhyasa/blob/main/abhyasa/settings.py#L218"
target="_blank" style="float:right; font-size:smaller">source</a>

### top

``` python
def top(
    r
):
```

*The top of the range of row `r`: a number, or the value of the row that
`r.hi` names.*

[`val`](https://vedicreader.github.io/abhyasa/settings.html#val) gives
the value of a row now: the saved value made to fit, else the default.
The engine calls it each time it needs the value.

------------------------------------------------------------------------

<a
href="https://github.com/vedicreader/abhyasa/blob/main/abhyasa/settings.py#L248"
target="_blank" style="float:right; font-size:smaller">source</a>

### dflt

``` python
def dflt(
    name:str
):
```

------------------------------------------------------------------------

<a
href="https://github.com/vedicreader/abhyasa/blob/main/abhyasa/settings.py#L241"
target="_blank" style="float:right; font-size:smaller">source</a>

### val

``` python
def val(
    name:str
):
```

*The value of the setting `name` now.*

------------------------------------------------------------------------

<a
href="https://github.com/vedicreader/abhyasa/blob/main/abhyasa/settings.py#L235"
target="_blank" style="float:right; font-size:smaller">source</a>

### raw

``` python
def raw(
    name:str
):
```

*The saved value of `name` as it is in the file, or None.*

A saved 50 ideas becomes 30, the most. A string gives the default. A
share above 1 becomes 1.

``` python
update_settings('walks', steps=50, hands='a lot', note=40.0)
test_eq((val('walks.steps'), val('walks.hands'), val('walks.note')), (30, 1/3, 40))
update_settings('walks', hands=1.5, steps=1)
test_eq((val('walks.hands'), val('walks.steps')), (1., 3))
save_settings(dict(prompts={}, downloaded=[]))
```

The top of the pages read by default is the page cap.

``` python
update_settings('links', pages=40, cap=10)
test_eq((val('links.pages'), val('links.cap')), (10, 10))
save_settings(dict(prompts={}, downloaded=[]))
```

## Change one value

[`set_val`](https://vedicreader.github.io/abhyasa/settings.html#set_val)
saves a value made to fit its row, and returns it.
[`reset`](https://vedicreader.github.io/abhyasa/settings.html#reset)
puts back the default of one row, and
[`reset_group`](https://vedicreader.github.io/abhyasa/settings.html#reset_group)
of each row in a group.
[`changed`](https://vedicreader.github.io/abhyasa/settings.html#changed)
is true when a value is not its default.
[`clamped`](https://vedicreader.github.io/abhyasa/settings.html#clamped)
gives the value in the file when it was outside the range, so the page
can say so.

------------------------------------------------------------------------

<a
href="https://github.com/vedicreader/abhyasa/blob/main/abhyasa/settings.py#L281"
target="_blank" style="float:right; font-size:smaller">source</a>

### clamped

``` python
def clamped(
    name:str
):
```

*The number in the file for `name` when it was outside the range, else
None.*

------------------------------------------------------------------------

<a
href="https://github.com/vedicreader/abhyasa/blob/main/abhyasa/settings.py#L279"
target="_blank" style="float:right; font-size:smaller">source</a>

### changed

``` python
def changed(
    name:str
)->bool:
```

------------------------------------------------------------------------

<a
href="https://github.com/vedicreader/abhyasa/blob/main/abhyasa/settings.py#L270"
target="_blank" style="float:right; font-size:smaller">source</a>

### reset_group

``` python
def reset_group(
    group:str
)->list:
```

*Use the default of each row of `group` that the file keeps and that a
group reset can change. Return their names.*

------------------------------------------------------------------------

<a
href="https://github.com/vedicreader/abhyasa/blob/main/abhyasa/settings.py#L266"
target="_blank" style="float:right; font-size:smaller">source</a>

### rows

``` python
def rows(
    group:str
)->list:
```

*The rows of `group`, in table order.*

------------------------------------------------------------------------

<a
href="https://github.com/vedicreader/abhyasa/blob/main/abhyasa/settings.py#L257"
target="_blank" style="float:right; font-size:smaller">source</a>

### reset

``` python
def reset(
    name:str
):
```

*Use the default of the setting `name` again.*

------------------------------------------------------------------------

<a
href="https://github.com/vedicreader/abhyasa/blob/main/abhyasa/settings.py#L251"
target="_blank" style="float:right; font-size:smaller">source</a>

### set_val

``` python
def set_val(
    name:str, v
):
```

*Save the value `v` of the setting `name`, made to fit its row. Return
the value in use.*

A value saved out of range is saved as the end of the range.
[`reset`](https://vedicreader.github.io/abhyasa/settings.html#reset) and
[`reset_group`](https://vedicreader.github.io/abhyasa/settings.html#reset_group)
put back the defaults.

``` python
test_eq(set_val('walks.steps', 99), 30)
test_eq(raw('walks.steps'), 30)
assert changed('walks.steps') and not changed('walks.note')
reset('walks.steps')
test_eq(val('walks.steps'), 12)
set_val('checks.tries', 5); set_val('checks.near', 8)
test_eq(sorted(reset_group('checks')), ['checks.near', 'checks.tries'])
test_eq((val('checks.tries'), val('checks.near')), (3, 7))
update_settings('walks', steps=50)
test_eq(clamped('walks.steps'), 50)
# a number typed by hand as text works, and text that is not a number gives the default
update_settings('tokens', hint='900', grade='many')
test_eq((val('tokens.hint'), val('tokens.grade')), (900, 2048))
# a value that the profile keeps has no reset here
test_fail(lambda: reset('learning.pace'), contains='profile')
save_settings(dict(prompts={}, downloaded=[]))
```

## What stays fixed

Some values keep you safe, so they have no setting: the closed process
of the code runner (no network, the sandbox profile, the audit hook,
`python -I`, `sandbox=True`), the link checks for local addresses, plain
HTTP, the link kinds, the HTML allowlist, the check of a pane address,
the local-only hosts, the test allowlist, the grade scale and the depth
ladder. The room for phones keeps its safety limits fixed too: the bind
rule, the join code, the rate limits, the body size and the headers.
`FIXED` names them, and no row may have one of these words in its name.

``` python
assert not [n for n in SETTINGS for w in FIXED if w in n]
```
