> ## Documentation Index
> Fetch the complete documentation index at: https://aspect.build/llms.txt
> Use this file to discover all available pages before exploring further.

# Write a simple application

> Write a simple Python app with Bazel using the py starter, learning how Bazel manages the interpreter, PyPI packages, and BUILD file generation.

In this section, we will write a tiny client application.
We'll learn how Bazel manages the Python interpreter, third-party packages from PyPI, and generate a `BUILD` file to run it.

## Set up a dev environment

To get started, we'll use the Python starter from [https://github.com/aspect-starters/py](https://github.com/aspect-starters/py).

<img src="https://mintcdn.com/aspectbuild/gtqBKXWWd-jWHweY/learning/bazel-102/starter_repo.png?fit=max&auto=format&n=gtqBKXWWd-jWHweY&q=85&s=937a208375ea6b18d8a9d809a3112801" alt="starter" width="2320" height="1578" data-path="learning/bazel-102/starter_repo.png" />

<Warning>
  The container we'll be working in doesn't have ANY Python installed.
  This will keep us honest that the build is reproducible on other computers.
</Warning>

### A permanent project

If you'd like to make a permanent new project, click the green “Use this Template” button.
Then choose "Create a new repository" and pick a name for your new project.

After the course you can continue to evolve the code following Aspect's other recommendations.

Once you're in your new project, press the comma (",") keyboard shortcut to open the project in a codespace.

### Ephemeral playground

You can create a free remote development "playground" environment where you can easily experiment.

From the starter repo, there are a couple options:

* Click the green “Use this Template” button.
  Then choose "Open in a codespace". By default, this gives you a small machine to experiment with.
* You can also navigate to [https://github.com/codespaces/new](https://github.com/codespaces/new) and choose a larger machine.
  This will make the setup a few minutes faster, and of course it makes later development more responsive.

It will take a few minutes for the new machine to boot up.
Don't pay attention to errors in the terminal -- as soon as some setup hooks run, they will go away.
It's best to let the scripts finish running before you click anything.

The codespace already has Bazel installed, using Bazelisk.
It also includes some tools on the PATH which we'll use later.

You'll see a prompt about installing the recommended VS Code extensions.
We suggest allowing these, to get syntax highlighting in Bazel configurations and more.
If you get an error about the path to `starpls` or `buildifier` don't worry - these will be setup by the post-install hooks.

At this point you should have a working development environment - let's dive in!

## Requests example code

Let's start with a “getting started” snippet for the Python “requests” module:
It gives a good example of installing an external package from PyPI.

1. Create a folder named `app`
2. Create a file `__main__.py` (double underscores before and after `main`). This is the Python convention for an executable entry-point.
3. Copy this code to create a trivial "client" application:

```python theme={null}
import requests

x = requests.get("https://httpbin.org/bytes/1")

print(x.text.encode().hex())
```

## Declare and pin requirements

Since the `requests` package is a new dependency of the repository, you first need to declare that.
This example uses a `pyproject.toml`.

<Note>Bazel’s [rules\_python](https://github.com/bazelbuild/rules_python) supports the simpler `requirements.txt` format as well. </Note>

Add the “requests” string to the `dependencies` section, which should now look like this:

```toml theme={null}
dependencies = [
    "requests",
]
```

Next you need to “compile” or “pin” the dependencies.
Bazel can only provide reproducible builds if you give a fully-locked description of the dependencies.
There are a few options for `pip compile` tools.
This exercise uses `uv pip compile` which comes from [rules\_uv](https://github.com/theoremlp/rules_uv),
and it provides executable targets to update the pinned dependencies, which you can use with `bazel run //requirements:runtime.update`.

Take a look in the `requirements` folder.
There are files for different purposes; for example `test.in` lists the `pytest` dependency needed for testing.
Since there are multiple pinned dependencies, it would require multiple `bazel run` commands to update everything.
The repository has a convenience script to make this easy, so you should only need to run:

```bash theme={null}
% ./tools/repin
```

This results in updates to the `requirements/all.in` file, which has the complete, constraint-solved set of dependencies for our repo.
It includes hashes of each package for better supply-chain security.

This file is used by rules\_python to install dependencies. Note in `MODULE.bazel` that it’s referenced by a `pip.parse` module extension:

```python theme={null}
pip.parse(
    hub_name = "pip",
    python_version = "3.12",
    requirements_lock = "//requirements:all.txt",
)
```

Now you should be ready to use the dependency from Bazel-managed code.

## Generate BUILD files

For Bazel to understand the Python code, it expects `BUILD` files containing Python rules.
Since you’ve written a program, you need to add a `py_binary` rule to make it runnable.

Run `aspect gazelle` to create the BUILD files.

<Note>
  We have setup a pre-compiled Gazelle binary here, so this step doesn't need to compile Go and C++ code
</Note>

Now look in `app/BUILD` to see the generated content.

At this point the `py_binary` should be functional, so you can run it with the following command:

```python theme={null}
% bazel run app:app_bin
```
