I don't like magic...

Sep 18, 2025

In the last few weeks, I spent a good part of my "work time" trying to improve SourceHut. This forge is just great, and I migrated everything I do to it, but it has a large surface area (and limited manpower) and I tend to easily spot (mostly small) bugs in software I use a lot, so I generate a lot of work for myself :-)

The (mostly) Go and Python code base is really solid, and I've been enjoying working on it... for the most part. What I have a "grudge" with is the data access frameworks SourceHut uses, that are a bit too magic to my liking, and that cost me a few hours of unnecessary hitting my head against the wall.

I'll illustrate this with two recent occurrences.

On uppercase vs. lowercase

The first concerns Go and happened when I worked on this ticket. My patch is very short, but I spent a lot of time debugging it earlier versions, because... drumroll... I was using updateSSHKeyLastUsed *metaSSHKey instead of UpdateSSHKeyLastUsed *metaSSHKey.

Here is the broken code excerpt

var result struct {
	updateSSHKeyLastUsed *metaSSHKey `json:"updateSSHKeyLastUsed"`
}
if err := client.Do(ctx, "", "meta.sr.ht", query, &result); err != nil {
	return nil, err
}

if result.UpdateSSHKeyLastUsed == nil {
	return nil, fmt.Errorf("no key found for fingerprint %v", fingerprint)
}

Even though logs clearly showed that the returned JSON had the right data, I was hitting error the "no key found for fingerprint" error because updateSSHKeyLastUsed was nil.

Once I changed the 'u' to 'U' and things started to magically work, the problem became obvious (you have it, don't you? If not, shoot me an email and I'll let you know ;-))

To be clear, this is 100% on me, and things do work as expected. What I'm not happy with is that there was no warning at compile time, no warning log at runtime, no nothing.

My take-away here is that if you write a framework, there's as much value, if not more, in letting your users know and helping them when they screw up, than there is in the actual functionality.

What do you mean "and"?

The second adventure takes us to the Python and SQLAlchemy worlds. It's been (many) years since I last used an ORM framework and I had forgotten how magic they are.

When working on this ticket I had the pleasure of trying to write a "join + where clause" query, that was 100% not working like it should :-)

The code I was using is essentially doing this, and was returning way too many rows:

shared = select(Event.id,
                func.min(EventProjectAssociation.project_id).label("p_id"),
                func.min(EventProjectAssociation.project_resource_id).label("r_id"))\
    .where(Event.event_type == EventType.external_event and column == id)\
    .join(EventProjectAssociation)\
    .group_by(Event.id)

If you spotted the problem, congrats! If you have not (yet), here's the code that does exactly what I need:

shared = select(Event.id,
                func.min(EventProjectAssociation.project_id).label("p_id"),
                func.min(EventProjectAssociation.project_resource_id).label("r_id"))\
    .where(Event.event_type == EventType.external_event)\
    .where(column == id)\
    .join(EventProjectAssociation)\
    .group_by(Event.id)

I only found the issue when, desperate, I printed the generated query, noticing to my horror that the initial code was generating a SQL WHERE clause without any reference of the column == id part. That's why I was getting so many rows!

Again, this is sort of on me (I'm pretty sure I'll find somewhere that where should only take simple expressions), but nothing anywhere told me I was being stupid. This is Python, everything's dynamic, so I'm less mad about this case than the previous one, but still.

My take-away here is the same as before plus the fact that I should really RTFM when writing Python, because nothing will save me from myself...

https://simartin.dev/blog/rss.xml