Recommended resources
The freely available resources this lesson will direct you to.
Hello! This first module builds the Python foundation you’ll use throughout the AI engineering course. Today’s goal is to create a small project that another developer—or a CI job—can set up and run without guessing which packages to install.
By the end of this lesson, you’ll have a project with a pyproject.toml file, a uv.lock file, and a working command for running its code. Allow about 40 minutes, including the hands-on steps.
What makes a project reproducible?
A Python script alone does not describe its environment. If it imports a third-party package, someone else needs to know which package to install and which versions work together. A reproducible project records that information rather than relying on what happens to be installed on one computer.
We’ll use uv to manage three related things:
pyproject.tomlstates the project’s metadata, Python requirement, and declared dependencies.uv.lockrecords the resolved dependency versions, including packages required by your direct dependencies..venv/is the local, isolated environment where those packages are installed. It can be recreated; it is not the record you share.
Watch Corey Schafer create a uv project and add its first dependencies. His demonstration provides a useful visual reference before you do the same work yourself.
Python Tutorial: UV - A Faster, All-in-One Package Manager to Replace Pip and Venv
Watch “Python Tutorial: UV - A Faster, All-in-One Package Manager to Replace Pip and Venv” by Corey Schafer to see the project files appear and observe how uv rebuilds an environment.
Watch project initialization for the generated files, then adding packages for the changes to the dependency files. Skip ahead to running and syncing; focus on what happens when the local virtual environment is missing. You do not need to delete your own environment yet.
The key distinction is that the environment is an output, not the source of truth. Sharing a .venv/ directory is not a dependable setup procedure. Sharing the project files lets uv construct an appropriate environment on the next machine.
Read the project files before changing them
The official uv guide explains the roles of the files you’re about to create. In particular, notice why both a dependency declaration and a lockfile are useful: they answer different questions.
Read the “Project structure” portion of Astral’s uv guide to identify which files specify requirements, which file fixes resolved versions, and which directory holds installed packages.
Within “Project structure,” start immediately below the pyproject.toml example, at the paragraph explaining what you specify in that file. Read the file descriptions through the end of the uv.lock subsection. Include the intervening .python-version and .venv subsections. Pay particular attention to which files the guide says to put in version control.
One subtlety matters in team projects: .python-version indicates the Python version uv should select by default, while requires-python in pyproject.toml declares which Python versions the project supports. The values should make sense together. They do not mean every machine has an identical operating system or an identical Python patch release; the lockfile primarily makes dependency resolution repeatable across supported environments.
Build a starter AI project
First, check that uv is available in your terminal:
uv --version
If the command is not found, install uv for your operating system before continuing. Then, from a directory where you keep projects, run:
uv init ai-doc-starter
cd ai-doc-starter
Open the new directory in your editor. Find pyproject.toml and .python-version. Depending on your uv version and initialization options, you may also see a starter main.py, README.md, and Git files. In pyproject.toml, locate [project] and its requires-python and dependencies entries. Initially, there may be no third-party dependencies.
Add HTTPX, a Python HTTP client we’ll have a reason to use later in the course:
uv add httpx
This command declares HTTPX in pyproject.toml, resolves the dependencies into uv.lock, and installs them in the project environment. Inspect both files. The declaration in pyproject.toml may allow a range of HTTPX versions; uv.lock records the particular resolution. Do not manually edit uv.lock—let uv maintain it.
Replace the starter main.py with the following code, or create main.py if your project does not have one:
import httpx
def main() -> None:
print(f"HTTPX {httpx.__version__} is ready")
if __name__ == "__main__":
main()
Run it from the project directory:
uv run python main.py
You should see a message containing an HTTPX version. uv run uses the project environment; you do not have to activate .venv/ first. If it needs to bring the environment into line with the project files, uv does that before running the command.
At this point, check what you would share with a teammate. Include your code, pyproject.toml, uv.lock, and .python-version in version control. Keep .venv/ out of version control: it is local and regenerable. If uv created a .gitignore, check that it excludes .venv/.
Verify that the files—not your machine—define the setup
There is a useful difference between locking and syncing. Locking resolves dependencies and records them in uv.lock; syncing installs packages from that resolution into .venv/. uv commonly performs both automatically, but you can ask it to check or sync explicitly.
Locking and syncing | uv - Astral Docs
Read Astral’s explanation of automatic locking and syncing before checking your project. It also clarifies why an ordinary run is not quite the same as an exact environment sync.
In “Automatic lock and sync,” read the opening explanation, then continue through “Checking the lockfile,” ending after the paragraph about changed version constraints. Follow the checks, especially what the --locked option does when project metadata and the lockfile disagree. Then, under “Syncing the environment,” read through “Editable installation” and the opening of “Handling of extraneous packages.” Compare sync behaviors: uv sync removes packages absent from the lockfile by default, whereas uv run ordinarily does not.
Now check your project without asking uv to silently update an out-of-date lockfile:
uv lock --check
uv sync --locked
uv run --locked python main.py
A successful uv lock --check means the lockfile agrees with the project metadata. uv sync --locked prepares the environment while refusing to update an outdated lockfile. The final command runs your script under the same no-lockfile-update condition.
For the strongest practical check, make a fresh checkout or copy of the project that includes the tracked files but not .venv/. In that fresh directory, run uv sync --locked and then uv run --locked python main.py. This simulates a teammate receiving your project. It may still need access to Python and a package source to download files; reproducibility does not mean the first setup works without those prerequisites.
If you later change dependencies, use uv add or uv remove and commit the resulting changes to both pyproject.toml and uv.lock. If a locked check fails, investigate the mismatch rather than deleting the lockfile as a routine fix.
Takeaway
You’ve created a project whose setup is described by files rather than by the accidental state of your computer. pyproject.toml declares what the project needs, uv.lock fixes the resolved dependency set, and uv can recreate the local environment and run the code. The fresh-directory check is the clearest proof that you have a usable handoff.
Next, we’ll use Python collections and comprehensions to transform the documents, messages, and model responses that flow through an AI application.