# Functions

> Run Python functions on Nodus workers, deploy them as an App that stays up, and look them up from anywhere.

Source: https://nodus-platform-site.pages.dev/docs/guides/functions/
Build revision: 211ad9f836655b1c3a2668c4693e442471f28614

A Function is a Python function that runs on Nodus workers. You decorate it, call it from your own code with `.remote()`, `.map()` or `.spawn()`, and Nodus starts workers when calls arrive, keeps them warm for a while and scales them back down. The pages of this guide cover [calling Functions](https://nodus-platform-site.pages.dev/docs/guides/functions/calls), [scaling them](https://nodus-platform-site.pages.dev/docs/guides/functions/scaling) and [what is billed while a worker waits](https://nodus-platform-site.pages.dev/docs/guides/functions/billing).

## Run an App

An App is the group of Functions in one file. `nodus run` creates an ephemeral App, runs your `main` on your machine and deletes the App when `main` returns. Every `.remote()` call runs on a Nodus worker.

examples/functions/map/app.py

```python
"""One Function called three ways, then mapped over a thousand inputs.

Run it with `nodus run examples/functions/map/app.py`.
"""

import nodus

app = nodus.App("fn-map")


@app.function(cpu=1, memory="1Gi", max_workers=4, target_concurrency=8, max_cost=1)
def square(x: int) -> int:
    return x * x


@app.function(cpu=1, memory="1Gi", max_cost=1)
def divide(a: int, b: int) -> float:
    return a / b


@app.local_entrypoint()
def main(n: int = 1000) -> None:
    print("remote:", square.remote(7))  # one call; blocks for the result
    call = square.spawn(8)  # starts a call and returns a handle
    print("spawned:", call.get(timeout=600))
    results = list(square.map(range(n)))  # one call per input, results in input order
    print("map:", len(results), "ordered:", results == [i * i for i in range(n)])
    try:
        divide.remote(1, 0)
    except ZeroDivisionError as exc:  # the remote exception comes back as its own type
        print("raised:", type(exc).__name__)
```

Terminal window

```console
$ nodus run examples/functions/map/app.py
remote: 49
spawned: 64
map: 1000 ordered: True
raised: ZeroDivisionError
```

The directory of the file (minus what `.gitignore` and `.nodusignore` exclude) is uploaded once per content hash, so the workers import the same code you ran. The App renews itself while `main` runs and is deleted shortly after `main` stops, even when your machine loses its connection.

## Deploy an App

`nodus deploy` keeps the App. Its Functions stay available after your program exits, and any program can call them.

examples/functions/warm-pool/app.py

```python
"""A Function that keeps one worker warm, so a call never waits for a start.

Deploy it with `nodus deploy examples/functions/warm-pool/app.py`. The idle worker is billed at the Function's
worker rate for as long as it stays warm; set `min_workers=0` to pay only while calls run.
"""

import nodus

app = nodus.App("fn-warm")


@app.function(cpu=1, memory="1Gi", min_workers=1, max_workers=3, scaledown_window="2m", max_cost=1)
def ping() -> str:
    return "pong"
```

Terminal window

```console
$ nodus deploy examples/functions/warm-pool/app.py
```

```python
import nodus

ping = nodus.Function.from_name("fn-warm", "ping")
print(ping.remote())  # pong
```

A deploy updates the App in place. A Function whose code, image or resources changed rolls its workers once, after their in-flight calls finish. A Function you removed from the file is deleted, and the calls it still had queued end as `Failed` with the reason `FunctionDeleted`, so no caller waits for a call nobody will run.

## What Nodus creates

|Object|What it is|Look at it with|
|-|-|-|
|`App`|The Functions of one file; ephemeral for `run`, persistent for `deploy`|`nodus get apps`|
|`Function`|One decorated function or class, with its image, resources and `scaling`|`nodus get functions`|
|`FunctionCall`|One invocation, kept for seven days after it ends|`nodus get functioncalls -l nodus.dev/function=fn-warm-ping`|

A Function reports its state in `status.phase`, how many workers it has in `status.workers`, how many calls wait in `status.queue`, and the expected start time in `status.estimate`.

Terminal window

```console
$ nodus get function fn-warm-ping
NAME           PHASE     WORKERS   QUEUED   COST     AGE
fn-warm-ping   Running   1/3       0        $0.02    3m
```

## Stop, restart and delete

Terminal window

```console
$ nodus stop function/fn-warm-ping      # drain the workers; new calls wait in the queue
$ nodus start function/fn-warm-ping     # start workers again for the calls that waited
$ nodus restart function/fn-warm-ping   # replace every worker once, after in-flight calls finish
$ nodus delete app/fn-warm
```

A stopped Function keeps accepting calls and holds them in the queue, so work you submit while it is stopped runs once it starts. Workers also stop by themselves when your balance cannot cover another renewal; calls queue until you add credit and then run.

Note

A call that runs for longer than its `timeout` (five minutes by default, 24 hours at most) ends as `Failed` with the reason `DeadlineExceeded`, and its worker restarts, because the thread that ran it cannot be interrupted.
