A bank holiday to honour uv?

Author
Published

August 11, 2025

The command bank.py is run in a macOS terminal Window, yielding the phrase 'Summer bank holiday is on 25 August 2025', with a series of triangle symbols alternating with a black-and-white fill to look like bunting.

tl;dr

You can use uv to help turn a Python script into a self-contained executable. Useful for really important stuff, like… discovering the next public holiday from your terminal?

This is my own attempt at something I saw Rodrigo do.

Worth a bunt

The UK government maintains an API catalogue. A very (very) simple API example is the bank-holidays API run by the Government Digital Service (GDS). It’s perhaps most well-known for the associated page on the GOV.UK website.

All the API does is serve a JSON file with upcoming public holidays. I wrote a ramshackle little Python script that grabs the file, works out what the next holiday is, and prints it out.

Crucially, it also prints bunting if, according to the JSON, the occasion calls for it.

Banking crisis

So, here’s the quite-ordinary code for this demonstration:

bank.py
import httpx
from datetime import date

# Fetch bank holidays

resp = httpx.get("https://www.gov.uk/bank-holidays.json")
events = resp.json()["england-and-wales"]["events"]

for event in events:
    event["date"] = date.fromisoformat(event["date"])

# Find next holiday
today = date.today()
future = [event for event in events if event["date"] >= today]
next = future[0]

# Print message

is_today = next["date"] == today
needs_bunting = next["bunting"]

if is_today:
    when = "today!"
else: 
    when = f"on {next['date'].strftime('%d %B %Y')}"

str_out = f"{next['title']} is {when}"

if needs_bunting:
    str_out = str_out + " ▼▽▼▽▼▽"

print(str_out)
Summer bank holiday is on 25 August 2025 ▼▽▼▽▼▽

Very simple. Does the job. But if we want to use the script, maybe we’ll have to clone a repo containing the script, change directory, activate the virtual environment and only then run the script. Every time we want to use it.

Wouldn’t it be better if you could just call it quickly from the command line when you need it? Just type bank.py and get a quick response?

uv protection

This is where uv can help out.

You can start by adding a block at the top of your file to declare dependencies. Kind of like bundling a pyproject.toml inside your script. No need to worry about a separate dependencies file, lockfile or virtual environment.

To add your dependencies, use uv add with the --script option in the form:

uv add httpx datetime --script bank.py

Which will add the following section at the top of our bank.py script, including the Python requirement:

# /// script
# requires-python = ">=3.12"
# dependencies = [
#     "datetime",
#     "httpx",
# ]
# ///

And there you have it: all the information needed to run this file in an isolated manner. You’ll be able to run uv run bank.py from your terminal without any additional files required.

But wait! There’s more. How boring, how painful to have to write the uv run bit. That’s six characters. And you’ll have to provide the full path to the script, yikes.

Instead, you can add a shebang at the top of the file to make it executable:

#!/usr/bin/env -S uv run

Note the unusual bit on the end… aha! uv again! The -S uv run bit asks the interpreter to let good ol’ uv handle the whole operation.

Finally, you can change the mode (chmod) of the file by adding (+) executable (x) status. Then put the file where you terminal will look if you type the name of the file.

chmod +x bank.py
mv bank.py ~/.local/bin

Then you can merely call bank.py from the terminal to know when the next bank holiday is.

One man’s trash

I only found this thanks to Rodrigo, who spotted it via Simon, who spotted it via David. And now I’ve ripped it off.

You can find a GitHub Gist of my code, or:

Click to see the complete bank.py script.
bank.py
#!/usr/bin/env -S uv run

# /// script
# requires-python = ">=3.12"
# dependencies = [
#     "datetime",
#     "httpx",
# ]
# ///

import httpx
from datetime import date

# Fetch bank holidays

resp = httpx.get("https://www.gov.uk/bank-holidays.json")
events = resp.json()["england-and-wales"]["events"]

for event in events:
    event["date"] = date.fromisoformat(event["date"])

# Find next holiday
today = date.today()
future = [event for event in events if event["date"] >= today]
next = future[0]

# Print message

is_today = next["date"] == today
needs_bunting = next["bunting"]

if is_today:
    when = "today!"
else: 
    when = f"on {next['date'].strftime('%d %B %Y')}"

str_out = f"{next['title']} is {when}"

if needs_bunting:
    str_out = str_out + " ▼▽▼▽▼▽"

print(str_out)

Next, more important goal: write something similar to help me know what bin day it is (we alternate fortnightly between refuse and recycling). I saved a neighbour from getting erroneously binfluenced recently, so this is dear to my heart.

Environment

Session info
[project]
name = "2025-08-11-uv-standalone"
version = "0.1.0"
description = "rostrum.blog post: uv-standalone"
requires-python = ">=3.12"
dependencies = [
    "datetime>=5.5",
    "httpx>=0.28.1",
]

Footnotes

  1. And thus my 182-post streak without running Python comes to an end.↩︎

  2. Please feel free to critique my Python code. How could it possibly be worse than my R code?↩︎

  3. As a more frequent R user: 0 days since last zero-indexing mistake.↩︎

  4. Perhaps you have been living under a rock where uv rays have not struck you. uv is all the rage for doing all-the-Python-setup-things-that-make-Python-setup-a-headache. And it’s super fast. Because Rust. Because carcinisation is coming for all of us.↩︎

  5. The call was coming from inside the script.↩︎

  6. Likely more, given my terrible typo history.↩︎

  7. Basically says ‘if I’m called, execute me like a little standalone program’.↩︎

  8. I swear I saw a blog or video somewhere that showed how to just type the filename without the extension, like bank. Tell me if you know what I mean.↩︎

  9. I’m losing my mind becuase I’ve discovered recently that the pronunciation of ‘gist’ is another ‘how do you pronounce gif?’ situation. To be clear, it should be pronounced ‘gist’.↩︎

Reuse

CC BY-NC-SA 4.0