# Tutorial: Deploying and Calling Functions
source: https://docs.chalk.ai/docs/functions/tutorial

## Deploy a function, call it once and many times at once, retry calls that fail, and delete what you deployed.

A function is Python code that you deploy to Chalk
and call from anywhere: a script, a notebook, or another function. In this
tutorial you will:

- Deploy a function and call it
- Call it many times at once
- Retry calls that fail
- Delete what you deployed

### Before you start

You need:

- A Chalk account and an environment to work in.
- A role that can deploy and call functions: the deploy.create and
deploy.redeploy permissions. If you created your Chalk team yourself, you
already have them. Otherwise, ask an admin for a role that includes them. See
Permissions.
- A function queue in that environment. Steps 2 and 3 call functions through
it. If your environment does not have one, see
Function Queue Setup.

You do not need a Chalk project or chalk init, and you do not deploy anything
with chalk apply. Every function in this tutorial is deployed by the tutorial
and deleted in the last step.

Set up the Python environment that will run the tutorial:

- If you followed the Setup Guide, chalk init already
installed chalkcompute in your project. Run the tutorial's files from your
project folder with uv run python.
- Otherwise, install the Chalk CLI,
run chalk login, and install chalkcompute in a virtual environment:python3 -m venv .venv
source .venv/bin/activate
pip install chalkcomputeOn Windows, activate the environment with .venv\Scripts\activate instead.
Run the tutorial's files with python, from the folder where you ran
chalk login, so that chalkcompute finds your login. See
Logins and environments.

### Step 1: Deploy a function and call it

Save this file as functions_tutorial.py:

```
import chalkcompute

@chalkcompute.function
def word_count(text: str) -> int:
    return len(text.split())

if __name__ == "__main__":
    print(word_count.remote("Chalk deploys this function and runs it"))
```

Run it with python functions_tutorial.py. While the function deploys, the
script prints its progress, and then the result:

```
7
```

Running the file deploys word_count. The first deploy can take a few minutes.
Later runs deploy again only if the function has changed. .remote(...) waits
until the deployed function is ready, calls it, and returns its result.
word_count("..."), without .remote, would run the Python body on your own
machine instead.

Chalk imports this file inside the deployed function, and that runs its
top-level code. Keep functions at the top level of the file, and put the code
that calls them under if __name__ == "__main__":, so that it runs only when
you run the file.

### Step 2: Call it many times at once

Suppose each call does slower work, such as calling a model. To process a list
of texts quickly, start every call at once and collect the results afterward.

Replace the contents of functions_tutorial.py with:

```
import time

import chalkcompute

@chalkcompute.function(concurrency=10)
def word_count(text: str) -> int:
    time.sleep(2)  # Stands in for slower work, such as calling a model.
    return len(text.split())

if __name__ == "__main__":
    texts = [
        "one",
        "one two",
        "one two three",
        "one two three four",
        "one two three four five",
        "one two three four five six",
        "one two three four five six seven",
        "one two three four five six seven eight",
        "one two three four five six seven eight nine",
        "one two three four five six seven eight nine ten",
    ]
    word_count.wait_ready()  # Waits until the new version is deployed and ready.

    start = time.time()
    handles = [word_count.defer(text) for text in texts]
    print(chalkcompute.gather(handles))
    print(f"{time.time() - start:.0f} seconds")
```

Run it again:

```
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
4 seconds
```

Your time will differ a little. The changed function deploys as a new version,
and wait_ready() waits until that version can take calls, so the timing
covers only the calls.

.defer(...) sends a call to the function queue and returns a handle right
away, without waiting for the result. chalkcompute.gather(...) waits for all
the handles and returns their results in the same order as the calls.

concurrency=10 lets ten calls run at the same time. A function runs one call
at a time unless you set concurrency, so without it these ten calls would
run one after another, taking 20 seconds or more. See
Rate and Concurrency Limits.

### Step 3: Retry calls that fail

Calls can fail for reasons that go away on their own, such as a network error
in a service the function calls. retries runs a failed call again.

To see a retry happen, the function below fails on its first two attempts for
each request. It counts the attempts in a file inside the deployed function.

Replace the contents of functions_tutorial.py with:

```
import time
import uuid

import chalkcompute

@chalkcompute.function(concurrency=10)
def word_count(text: str) -> int:
    time.sleep(2)  # Stands in for slower work, such as calling a model.
    return len(text.split())

@chalkcompute.function(retries=3)
def flaky_word_count(request_id: str, text: str) -> str:
    import os

    path = f"/tmp/attempts-{request_id}"
    attempt = int(open(path).read()) + 1 if os.path.exists(path) else 1
    with open(path, "w") as f:
        f.write(str(attempt))
    if attempt < 3:
        raise ConnectionError(f"Attempt {attempt} failed")
    return f"{len(text.split())} words, on attempt {attempt}"

if __name__ == "__main__":
    flaky_word_count.wait_ready()  # Waits until the new function is deployed and ready.
    handle = flaky_word_count.defer(str(uuid.uuid4()), "This call fails twice before it works")
    print(handle.get())
```

Run it again. This step takes a minute or two, because flaky_word_count is
deployed for the first time and each retry waits before it runs:

```
7 words, on attempt 3
```

retries=3 allows up to three more attempts after the first one fails. The
first two attempts raised ConnectionError, and the third returned a result.

Retries apply to calls made with .defer(...), which go through the function
queue. A call made with .remote(...) raises the first failure to the caller.

### Step 4: Clean up

A deployed function keeps running until you delete it. Save this file as
cleanup.py and run it with python cleanup.py:

```
import chalkcompute

for name in ["word_count", "flaky_word_count"]:
    chalkcompute.RemoteFunction.from_name(name).delete()
```

delete() removes each function and all of its versions.

### Next steps

- Defining a function: ship helper
modules with a function, and call a deployed function from another script
- Configuring functions: images, CPU,
memory, GPUs, and replicas
- Rate and Concurrency Limits:
cap how often a function is called
- Streaming results: return results as
they are produced
- Calling from a DataFrame: call a
function on every row of a dataset
- Managing functions: list, inspect, and
delete functions with the CLI




