Background #
Cog #
I’ve been dying to have some fun with cog ever since I
overheard Ned Batchelder (@nedbat) talking about it with someone else.
Here’s its own description:
Cog is a content generation tool. It lets you use small bits of Python code in otherwise static files to generate whatever text you need.
I think the best and most fun example of how cog works is a hidden gem in the cog docs.
Cog embeds the the CLI --help in the docs by using cog
to run cog -h and dump the output (into the file).
This is poetry (no, not the Python tool). This is beautiful (not the soup).
Single-file scripts #
Also, if you haven’t heard of or tried them yet, the Python packaging ecosystem has a neat way of taking a Python script’s dependency specifier and “inlining” it at the top of a file in a comment TOML (not a TOML comment), making a “single-file script”.
The result is something that can be uv run or pipx run and is spiritually a Python “single-file executable”.
This is also beautiful and amazing.
Problem #
Single-file scripts lack a way to pin transitive dependencies.
While uv supports this with uv lock --script, it creates a separate lock file, defeating the whole “single-file” concept.
(Really I want to fully embed the lockfile, but pinned transitive deps are “good enough” for now).
Solution #
Use cog to generate to pin the dependencies!
# /// script
# dependencies = [
# # [[[cog
# # import subprocess
# # DEPENDENCIES = [
# #     "cyclopts",
# #     "httpx",
# # ]
# # cog.outl(
# #     "\n".join(
# #         f'#   "{line}",' for line in
# #         subprocess.check_output(
# #             "uv pip compile --no-annotate --no-header -",
# #             shell=True,
# #             text=True,
# #             input="\n".join(DEPENDENCIES)
# #         ).splitlines()
# #     )
# # )
# # ]]]
#   "anyio==4.9.0",
#     "<the others omitted for brevity>",
# # [[[end]]]
# ]
# ///
import cyclopts
import httpx
# Script code here...
And now you can update (and pin) dependencies anytime with:
uvx --from cogapp cog -r <path>
Caveat: When compared with a full lockfile we still lack features such as hash support (if you’re into supply-chain security) and friends. However, incremental improvement is still improvement.
Bonus: Self-relocking #
This also means your single-file script (usually a CLI app) can now have a self relock command!
# <same preamble>
import cyclopts
import rich
import subprocess
app = cyclopts.App()
self_app = cyclopts.App(name="self")
app.command(self_app)
@self_app.command()
def relock():
    """Update this script's pinned dependencies."""
    subprocess.check_call(f"uvx --from cogapp cog -r {__file__}", shell=True)
    rich.print("[green]Relocked successfully[/green]")
...