Ephes Blog

Miscellaneous things. Mostly Weeknotes and links I stumbled upon.

Date -

TIL: Managing Editable Dependencies in uv Without Committing Local Paths

The Problem

When developing Python packages that depend on each other, you often need to install dependencies in editable mode to see changes immediately. With `uv`, I was running into an annoying workflow issue:

  1. Install a dependency as editable: `uv pip install -e ../django-cast`
  2. Run tests or dev server: `uv run pytest` or `just dev`
  3. uv automatically runs `uv sync`, which reinstalls the package from git, overwriting my editable install!

This meant I had to reinstall editable packages after every `uv run` command, which was frustrating and slowed down development. There's also an existing GitHub issue for that.

Continue reading →


Mutable Defaults in Python: When None Isn't Enough

This post walks through Python's mutable default gotcha and explores modern solutions for when the standard None workaround isn't sufficient.

The Mutable Default Gotcha

Every Python developer eventually learns about the mutable default argument gotcha. Here's what happens:

# BAD - shared mutable default
def add_item(item, items=[]):
    items.append(item)
    return items

# First call works as expected
print(add_item("a"))  # ['a']

# But the second call reveals the problem!
print(add_item("b"))  # ['a', 'b'] - wait, what?

The issue is that default arguments are evaluated once when the function is defined, not each time it's called. So all calls to add_item() without providing items share the same list object!

The Standard Solution

This leads to the standard solution using None:

def add_item(item: str, items: list[str] | None = None) -> list[str]:
    if items is None:
        items = []
    items.append(item)
    return items

This works perfectly well for most cases. The list[str] | None annotation accurately represents what callers can pass, even though it looks a bit ugly and we immediately replace None with an empty list internally.

When None Has Meaning

The real trouble starts when None is a legitimate value in your domain:

  • Database ORMs where None represents NULL
  • Configuration systems where None means "inherit from parent"
  • Data processing where None means "no value provided" (different from "empty collection")

Now you need to distinguish between "no argument provided" and "explicitly passed None". This is where sentinel values come in.

The Search for a Better Pattern

When I read about Luke Plant's Auto sentinel pattern, it seemed elegant. The idea was to use a falsy sentinel:

Continue reading →


Weeknotes 2025-07-21

I guess you've heard some bad jokes about Fibonacci sequences?
Well, this one is as bad as the last two you heard put together.
--Kit Yates

Still got a ton of work ahead of me, and it probably won't let up until the holidays. I did manage to update the django-cast documentation a bit though. It's not as detailed as I'd like, but at least the structure is better now. Over the weekend I played around with VibeTunnel and Wispr Flow, which were really interesting. It still feels weird dictating to my phone or laptop, but I probably just need more practice to get used to it. We also recorded a new podcast episode about data science that should be out soon.

Articles

Software

Videos


Weeknotes 2025-07-14

I don’t use em dashes because I’m an AI. I use em dashes because I’m pretentious --Jason Lefkowitz

This has been a hectic summer, and there's not much I can do about it. I pushed out a new release of django-cast that fixes gallery navigation issues with duplicate images and speeds up tox execution, along with several other development infrastructure improvements. After spending over two months with Claude Code, I figured it was worth exploring some alternatives. Since I'd already tried codex and found it didn't quite measure up to Claude Code, I gave gemini-cli a shot. It's marginally better than codex, but still not in the same league. It'll probably be a while before the competition catches up.

Since I often run Claude Code in yolo --dangerously-skip-permissions mode, I've been thinking about how to manage the multiple instances I typically have running concurrently. I ended up writing a short blog post about the quirks of juggling them all.

I may have also inadvertently agreed to write a blog post about running Django in an Electron shell. This could make for a good holiday project, I suppose.

Articles

Software


Running Coding Agents Remotely: SSH, tmux, and the Quest for a Seamless Experience

Why This Matters: Coding Agents Are Changing How I Work

Coding agents like Claude Code are quite powerful, but watching them work can be like watching paint dry—especially when you're just clicking "approve" repeatedly. Running them with --dangerously-skip-permissions speeds things up, but doing that on your main development machine? That's asking for trouble.

My solution: dedicate an old Mac mini as a sacrificial lamb for agent experiments. But this created new challenges that took me down a rabbit hole of SSH, tmux, and shell configuration.

Continue reading →