Python for Humans · Draft v0.1 · Oct 29, 2026 · Draft position: chapter 14 · Install Requests in a project environment and use a public weather API to build a small report.
Packages and uv
- You should already know
- Imports, functions, dictionaries, exception handling and running saved programs from the terminal.
- You'll build
- A weather report that uses Requests to fetch a temperature, unit and timestamp from a public API.
- You'll be able to
- Install a project dependency, run a program with uv, send request parameters and use a JSON response.
- You need
- Your local Python setup, any plain-text editor, your course folder and an internet connection for installation and live requests.
Suppose you want your program to tell you the current temperature somewhere. You could type a number into a dictionary, but that number would be out of date almost immediately.
Another program already supplies weather data over the web. We can ask it for a value, then use our own code to select and display the answer.
Python's standard library includes networking tools, but a third-party package called Requests gives us a convenient way to send that request. Let's install it and make something useful.
Code From Outside the Standard Library
We've used standard-library modules such as random and our own text_tools module. Requests is maintained separately from Python, so we need to install it before importing it.
A dependency is code our project relies on. We'll use uv to install Requests for this project and run our programs with that installation.
Installing and importing are different actions. Installation makes the package available in an environment. import requests makes its Python names available in a running program.
Install uv
Open a terminal. If you're at a Python >>> prompt, leave it with exit() first.
Follow the current official uv installation instructions. The standalone installer commands are:
Windows, in PowerShell:
powershell -ExecutionPolicy ByPass -c "irm https://astral.sh/uv/install.ps1 | iex"
macOS or Linux, in the terminal:
curl -LsSf https://astral.sh/uv/install.sh | sh
These commands download and run Astral's uv installer. Follow its final instructions, then open a new terminal and check:
uv --version
You should see a uv version. If the command isn't found, check the installer's instructions for making it available in a new terminal before continuing.
Add Requests to This Project
Open a terminal in your existing python-for-humans folder, as in Keeping Your Work. Check that your Python files are there with dir on Windows or ls on macOS/Linux.
Initialize project metadata once:
uv init --bare --vcs none
--bare asks for a minimal project without generated source files. --vcs none keeps this step focused on the Python project. Your existing programs and data stay in place. If this folder already has a pyproject.toml from a previous attempt, continue with that project instead of initializing it again.
Now add Requests:
uv add requests
uv creates a project environment in .venv, installs Requests and its dependencies, and records the dependency information. It may download a suitable Python version if it needs one.
Create weather.py in your editor with:
import requests print('Requests is ready.')
Save it and run:
uv run weather.py
This command is the same on Windows, macOS and Linux. uv run uses the project's environment, so we don't have to activate it manually.
If you see ModuleNotFoundError: No module named 'requests', check that you're in the project where you added Requests and that you used uv run. A separate python or python3 command may use a different environment.
Ask a Weather API
An API, or application programming interface, is a way for one program to use another program's capabilities. This one accepts an HTTP request at a URL and returns weather data.
We'll use Open-Meteo. Its free endpoint supports noncommercial uses such as this learning exercise without an account or API key. Follow its usage terms and credit it when sharing the results.
The endpoint URL is:
https://api.open-meteo.com/v1/forecast
Replace the initial contents of weather.py with:
import requests parameters = { 'latitude': 37.7749, 'longitude': -122.4194, 'current': 'temperature_2m', 'temperature_unit': 'celsius', 'timezone': 'auto' } response = requests.get( 'https://api.open-meteo.com/v1/forecast', params=parameters, timeout=10 ) response.raise_for_status() data = response.json() print(data)
Save and run uv run weather.py. You should see a dictionary containing weather data.
The coordinates are for San Francisco. Latitude describes a north/south position; longitude describes east/west. We supply them as numbers, including the negative longitude for this location.
The other parameters request the current temperature at two meters above ground, select Celsius, and ask the service to use the location's timezone. These names come from the API's documentation, rather than Python itself.
Requests takes our dictionary in params and encodes it into the request URL. We don't have to assemble the query string ourselves.
timeout=10 limits how long Requests waits for network activity. It isn't a promise that the entire request will finish within exactly ten seconds.
Read the Response
response represents the HTTP response. raise_for_status() raises an exception for an unsuccessful HTTP status such as 400 or 500. Then .json() decodes the response's JSON text into Python values.
JSON is a text format used to exchange structured data. A JSON object becomes a Python dictionary; an array becomes a list. JSON also has numbers, strings, true, false and null, which decode to their corresponding Python values. You can do this chapter even if you skipped the optional JSON exercise in Modules.
A shortened example response looks like this:
{ "timezone": "America/Los_Angeles", "current_units": {"temperature_2m": "°C"}, "current": { "time": "2026-10-29T06:00", "temperature_2m": 9.9 } }
This is an excerpt from an archived response, not a prediction of your result. The complete example JSON is available if you want to inspect the shape without making a live request. Weather data by Open-Meteo, CC BY 4.0.
Replace print(data) in your program with:
current = data['current'] units = data['current_units'] print('Temperature:', current['temperature_2m'], units['temperature_2m']) print('Time:', current['time'], data['timezone']) print('Weather data: Open-Meteo')
First we look up a dictionary under 'current'. Then we look up its temperature. The units come from the response too; we don't assume a number always means Celsius.
Open-Meteo supplies model-based weather data. The returned coordinates can identify a nearby model grid point rather than exactly the coordinates we requested. The temperature's time and location help us understand what the number describes.
Try it: Change 'celsius' to 'fahrenheit', save and run once more. Check both the temperature and returned unit. Restore your preferred unit afterward.
Keep Fetching and Formatting Separate
Fetching needs a connection; formatting an existing dictionary doesn't. Put those jobs in separate functions, using the organization from Modules.
Try the extraction first. Then compare your file with this complete version:
Compare weather.py
# weather.py import requests def fetch_weather(parameters): response = requests.get( 'https://api.open-meteo.com/v1/forecast', params=parameters, timeout=10 ) response.raise_for_status() return response.json() def format_weather(data): current = data['current'] units = data['current_units'] temperature = current['temperature_2m'] if temperature is None: raise ValueError('No temperature is available for this response.') return ( f"Temperature: {temperature} {units['temperature_2m']}\n" f"Time: {current['time']} {data['timezone']}\n" 'Weather data: Open-Meteo' ) def main(): parameters = { 'latitude': 37.7749, 'longitude': -122.4194, 'current': 'temperature_2m', 'temperature_unit': 'celsius', 'timezone': 'auto' } try: data = fetch_weather(parameters) report = format_weather(data) except requests.RequestException as error: print('Could not fetch weather:', error) return except ValueError as error: print('Could not make a weather report:', error) return print(report) if __name__ == '__main__': main()
fetch_weather() accepts one dictionary of request parameters and returns the decoded response. format_weather() accepts that response and returns the report string. Putting the string across several lines inside parentheses keeps the code readable; adjacent string literals are joined into one string. The \n characters supply its line breaks.
temperature is None checks for a missing temperature value represented by Python's None. We report that absence rather than printing it as though it were a temperature.
The main guard keeps importing these helpers from sending a request. We'll reuse them in the next chapter.
When a Request Fails
Try an invalid parameter once: set latitude to 999. The API should reject it, and raise_for_status() should lead to the fetch error message. Restore a valid latitude.
A connection failure or timeout also raises a Requests exception. requests.RequestException is the package's common base for its request-related errors. Catching it leaves unrelated bugs, such as a misspelled Python name, visible for debugging.
A response containing valid JSON doesn't automatically mean the request succeeded. An API can return an error description as JSON. That's why we check the HTTP status before using the data.
If the response's structure changes, a missing dictionary key can still raise KeyError. Inspect the actual response and the API documentation rather than suppressing an unfamiliar failure.
Make one request per run while experimenting. Check the service's documented usage limits before increasing that rate; a loop that repeatedly downloads the same answer isn't needed here.
What uv Kept for You
Your project now includes:
python-for-humans/
pyproject.toml <- project metadata and requested dependencies
uv.lock <- resolved dependency versions
.venv/ <- this project's Python environment
weather.py
text_tools.py
test_text_tools.py
...your other programs and data...
Open pyproject.toml in your editor and find requests in its dependency list. You may see several packages in uv.lock: Requests itself uses other packages, and uv records those versions too.
Keep the project metadata and lock file with your source. On another computer, uv can use them to recreate a compatible environment. Copying .venv itself isn't the way to move the project.
The lock file records Python dependencies; it doesn't freeze a remote API or the changing weather. Next, we'll save the data itself.
You can keep running the earlier checks with:
uv run test_text_tools.py
They still use ordinary assertions and explicit calls. Installing Requests doesn't change them.
Your Turn
Predict the Source of Each Value
Which of these is chosen by our code, and which comes back from the API: requested longitude, returned unit, response timestamp, the label Temperature:? Explain where you would make a change to each one you control.
Diagnose a Package Problem
Someone successfully runs uv add requests in one folder, opens a terminal in a different project, and gets an import error. What should they check before trying another installation? Include the working folder and run command in your explanation.
Make a Report for Another Place
Choose a different location and look up its latitude and longitude. Change the parameters, select a temperature unit, and add a place label of your own to the report.
Check the response timezone, unit and timestamp. Explain which work Requests performs, which data Open-Meteo supplies, and which words or calculations your program contributes. The exact temperature will vary.
What I've Learned
Explain the difference between installing a package and importing it. Then describe why a successful program run doesn't guarantee the same API answer tomorrow.
Optional: Another Kind of Package
If you'd prefer a visual experiment, Pillow works with images. Add it with uv add pillow, put a photo called photo.jpg in your course folder, and save this as gray_photo.py:
# gray_photo.py from PIL import Image with Image.open('photo.jpg') as photo: grayscale = photo.convert('L') grayscale.save('photo-gray.png')
Run uv run gray_photo.py and open the new image. Here the installed package is called pillow, while its import name is PIL; package and import names don't always match. The original photo is unchanged because the output has a different filename.
You can continue without this extension. In The Web, we'll use our existing Requests installation to download public datasets and save responses for repeatable offline analysis.