# Python: `HandCalcs`
`HandCalcs` renders calculations from any Python program. Create it with a renderer,
then call it with source code.
```python
from handcalcs import HandCalcs
from handcalcs.renderers import MarkdownRenderer
hc = HandCalcs(renderer=MarkdownRenderer())
markdown = hc("""
## Beam moment
w = 5 # kN/m
L = 6 # m
M = w * L**2 / 8
""")
```
The return value is whatever the renderer produces: a string for the Markdown, LaTeX,
HTML and ASCII renderers, a document for the Word renderer, a workbook for the Excel
renderer, a nested list for the base renderer. Pass `destination=` to
[write a file](#writing-a-file) instead. One `HandCalcs` object can be called any
number of times.
## Creating it
`HandCalcs(renderer=None)`
: `renderer` is a renderer **instance**. If it is omitted, the renderer named by the
`default_renderer` [option](../getting-started/configuration.md) is used (the ASCII
renderer unless you change it).
The global configuration is copied when the object is created, so set options first.
## Calling it
`hc(source_or_path, supplied_globals=None, supplied_locals=None, *, inputs=None, strict=True, allow_computed=False, destination=None)`
`source_or_path`
: Either a **string of Python source**, or a **`pathlib.Path`** to a `.py` file.
```{important}
A plain string is always treated as source code, even if it looks like a file name.
Wrap paths in `pathlib.Path`.
```
`supplied_globals`
: Where values come from.
- If omitted, the source is **run** (with `exec`, in a fresh namespace) and the
resulting variables are used.
- If given, the source is **not run**: values are looked up in this dictionary. Use
this when you have already run the code, or want to render code with values from
somewhere else.
```python
import pathlib
# Run a file and render it
latex = HandCalcs(LatexRenderer())(pathlib.Path("beam.py"))
# Render with values you already have (the code is not run)
values = {"w": 5, "L": 6, "M": 22.5}
text = HandCalcs(ASCIIRenderer())("M = w * L**2 / 8", values)
```
If a name has no value in `supplied_globals`, it is shown as empty in the substituted
step. The exception is a name used in an `if` condition: the condition is evaluated to
decide which branch to show, so a missing name raises `NameError`.
`inputs`, `strict`, `allow_computed`
: Render the source with different given values. See
[Rendering many input sets](#rendering-many-input-sets). `inputs` cannot be combined
with `supplied_globals`.
`destination`
: A file path (a string or a `pathlib.Path`). If given, the render is written to that
file and its `Path` is returned instead of the render. See
[Writing a file](#writing-a-file).
## Writing a file
Give a `destination` and handcalcs writes a file that is ready to open:
```python
import pathlib
from handcalcs import HandCalcs
from handcalcs.renderers import LatexRenderer, HTMLRenderer, WordRenderer
calc = pathlib.Path("calcs/beam.py")
HandCalcs(LatexRenderer())(calc, destination="report/beam.tex")
HandCalcs(HTMLRenderer())(calc, destination="report/beam.html")
HandCalcs(WordRenderer())(calc, destination="report/beam.docx")
```
| Renderer | The file |
|---|---|
| LaTeX | A complete document: `\documentclass{article}`, the packages the output needs (`amsmath`, `amssymb`, `xcolor`), then the calculation between `\begin{document}` and `\end{document}`. It compiles with `pdflatex` as it is. |
| HTML | A complete page: ``, a `
` with the character set and a title, and the calculation in the ``. |
| Markdown, ASCII | The output as it is: neither format needs anything around it. |
| Word, Excel | The `.docx` or `.xlsx` file, saved. |
Text is written as UTF-8. The parent folder must already exist, and an existing file is
replaced.
Without a `destination`, the LaTeX and HTML renderers return a **fragment**, ready to
`\input` into your own document or insert into your own page. Use that when the
calculation is one part of a larger document.
The text before and after the output comes from the renderer's `preamble` and
`postamble` attributes. Set them on a renderer to use your own document class or page
template:
```python
renderer = LatexRenderer()
renderer.preamble = (
"\\documentclass{report}\n"
"\\usepackage{amsmath,amssymb,xcolor}\n"
"\\begin{document}\n"
)
HandCalcs(renderer)(calc, destination="report/beam.tex")
```
The renderer's `to_file()` method does the writing; see
{ref}`Overridable methods ` to change it
in your own renderer.
### Inspecting the parsed tree
After a call, `hc.hc_ast` holds the parsed calculation (an `HcSequence`). This is
mostly useful when [building a renderer](../building-renderers/nodes.md).
## Rendering many input sets
Write a calculation once, as an ordinary script with sample values, then render it
with different inputs, one document per input set:
```python
import json
import pathlib
from handcalcs import HandCalcs
from handcalcs.renderers import LatexRenderer
hc = HandCalcs(LatexRenderer())
for case in json.loads(pathlib.Path("beams.json").read_text()): # [{"w": 6.2, "L": 8}, ...]
doc = hc(pathlib.Path("beam.py"), inputs=case)
```
In a notebook, [`%%render inputs=case`](jupyter.md#rendering-with-inputs) does the same
for a cell. A [`@handcalc`](decorator.md) function needs nothing extra: its parameters
are already its inputs.
`beam.py` stays a normal script that runs and renders on its own:
```python
import forallpeople as si
si.environment("structural")
from forallpeople import *
w = 5 * kN/m # load
L = 6 * m
M = w * L**2 / 8
```
With `inputs={"w": 7.5, "L": 8}`, the first assignment of each input name is given
the input value and the script is then run and rendered as usual, so everything
computed from the inputs is recomputed:
```text
w = 7.5 * kN / m = 7.5 * 1 kN / 1 m = 7.5 kN/m (load)
L = 8 * m = 8 * 1 m = 8 m
M = w * L² / 8 = 7.5 kN/m * 8 m² / 8 = 60 kN·m
```
**Units are kept.** When the input is a number and the script assigns a number times
units (`L = 6 * m`), only the number is replaced, so the line becomes `L = 8 * m`. Any
other input replaces the whole right-hand side.
**Which assignment is bound.** The first top-level `name = value` assignment of each
input name, including one statement of an `a = 1; b = 2` line. Later reassignments
still run. A name assigned only inside a block or function, by `+=`, or by unpacking
cannot be bound and raises `ValueError`.
**Input values** are usually what JSON holds: numbers, strings, `True`/`False`, `None`,
and lists and dicts of these. They are written into the script as typed. Numbers of
other types, such as `numpy.float64` from a pandas row, are written as plain numbers.
**Quantities** are converted to the units of their line. With
`inputs={"w": 6200 * ureg.N / ureg.m}`, the line `w = 4.5 * kN / m` runs and renders as
`w = 6.2 * kN / m`, so the document keeps the script's units. A quantity whose units
do not convert (a length for a load) raises `ValueError`. pint quantities from another
`UnitRegistry` are converted into the script's registry.
**Other objects**, such as arrays, sections or a quantity on a line with no units
(`A = 100`), are assigned as they are and shown as the line's value. Keep pint
quantities on such lines in the script's registry, since pint cannot calculate with
quantities from two registries.
### Checks
`strict=True`
: An input the script never assigns raises `KeyError`, which catches misspelled names.
With `strict=False` such inputs are ignored, so one input file can serve several
scripts.
`allow_computed=False`
: An input for a computed line (`M = w * L**2 / 8`) raises `ValueError`, so a
calculation is not replaced by accident. With `allow_computed=True` the line is
replaced by the input value.
### Finding a script's inputs
`find_inputs` lists the names whose first assignment is a literal or a number times
units, with their values in the script (the number only, for a value with units). Use
it as a template for an input file:
```python
from handcalcs import find_inputs
find_inputs(pathlib.Path("beam.py")) # {'w': 5, 'L': 6}
```
## The demo
`hc.demo(destination=None)` renders a built-in example calculation (headings, prose, parameter grids,
long equations and nested conditionals) with the object's renderer:
```python
print(HandCalcs(ASCIIRenderer()).demo())
```
It is a quick way to compare renderers, or to check a renderer you are writing; with a
`destination`, it writes the demo to a file as above. A
renderer that cannot render the general demo can supply its own source in a
`demo_source` attribute (the Excel renderer does).
## Examples
### Write a LaTeX fragment for `\input`
```python
import pathlib
from handcalcs import HandCalcs
from handcalcs.renderers import LatexRenderer
body = HandCalcs(LatexRenderer())(pathlib.Path("calcs/beam.py"))
pathlib.Path("report/beam.tex").write_text(body)
```
Without `destination=`, the output is a fragment for the body of your own document; see
[the LaTeX renderer](../renderers/latex.md#using-the-output) for the packages it needs.
### Serve HTML from a web app
```python
from handcalcs import HandCalcs
from handcalcs.renderers import HTMLRenderer
hc = HandCalcs(HTMLRenderer())
def beam_page(w: float, L: float) -> str:
return hc("M = w * L**2 / 8", {"w": w, "L": L, "M": w * L**2 / 8})
```
### One calculation per row of a table
```python
source = """
w = 5
L = 6
M = w * L**2 / 8
"""
hc = HandCalcs(ASCIIRenderer())
for w, L in [(5, 6), (7.5, 4)]:
print(hc(source, inputs={"w": w, "L": L}))
```
## Source
{source}`src/handcalcs/interfaces/object_oriented.py`