a blog archive
kd@ 3/19/2022

A very silly minor detail

I was trying to bash out a little manual DNS client in Go because it’s so much better at timeouts than Python. No big deal-

package main

import (
    "context"
    "fmt"
    "net"
    "time"
)

func main() {
    ctx, cancel := context.WithTimeout(context.Background(), time.Duration(time.Second*5))
    defer cancel()

    r := &net.Resolver{
        Dial: func(ctx context.Context, network, address string) (net.Conn, error) {
            return (&net.Dialer{
                Timeout: time.Duration(time.Second),
            }).DialContext(ctx, "udp", "1.2.3.4:53") // 1.2.3.4 is not a real DNS server
        },
    }
    ips, err := r.LookupHost(ctx, "www.google.com")
    fmt.Println(ips, err)
}

Unfortunately, I was getting a result here, instead of a timeout. Extremely confusing. There were some notes in the documentation about the pure-go resolver versus the system libc resolver, but it all sounded like the pure-go version was preferred, so surely this wasn’t something crazy like falling back to a real working DNS server, right?

No that’s totally it- you need to set PreferGo: true in your Resolver setup, and then you can check what the specified server returned, not some sort of sum of your local DNS cache and the server you’re trying to talk to.

Full, working version-

package main

import (
    "context"
    "fmt"
    "net"
    "time"
)

func main() {
    ctx, cancel := context.WithTimeout(context.Background(), time.Duration(time.Second*5))
    defer cancel()

    r := &net.Resolver{
        PreferGo: true,
        Dial: func(ctx context.Context, network, address string) (net.Conn, error) {
            return (&net.Dialer{
                Timeout: time.Duration(time.Second),
            }).DialContext(ctx, "udp", "1.2.3.4:53")
        },
    }
    ips, err := r.LookupHost(ctx, "www.google.com")
    fmt.Println(ips, err)
}
kd@ 3/13/2022

Goodbye, KrakensDen

I’m not going to renew this domain when it comes up again next year.

This is hard to do- I’m sadder about it than I should be. At least partly it’s about ambitions left undone- I have always wanted to be a regular blogger, but I’ve never been able to commit to topics or spend enough time on things. So here I am, with maybe 38 blog post over 9 years. There were more before then- I registered this domain in 2007- but I lost them reworking everything, as one does.

That rework was a really early experiment in Go- maybe even pre-1.0- and honestly, I still don’t know the language very well, despite very occasional dabbling since the very early days. A big, non-blog related regret.

I wish I had written more, and put more effort into what I did write. I wish I had followed up and done more book reviews. I wish I had the courage to write things and then tell people I wrote them, instead of just shouting into the void.

Still, here we are, and it’s probably time to stop throwing good money after bad.

I’ve used this nickname in dozens of places since I was 12 years old. I’m 34 now, it’s time to let it go.

kd@ 12/27/2021

Python object sugar

I read https://glyph.twistedmatrix.com/2016/08/attrs.html the other day while failing to get things done, and it really piqued my interest. I do a fair amount of “write a small plumbing utility” stuff in Python, and I basically never write new classes. I also occasionally regret that when things need real maintenance and grow over a couple of screenfuls.

So I took attrs, dataclasses, and pydantic, the three main competitors in this space for a test drive. Lets do attrs first, since that’s where I got started-

import attr

@attr.s
class InventoryItem:
  name: str = attr.ib()
  unit_price: float = attr.ib(validator=attr.validators.instance_of(float), converter=float)
  quantity: int = attr.ib(default=0)

  def total_cost(self) -> float:
    return self.unit_price * self.quantity

kitty_clock = InventoryItem("kitty clock", 21.99, 5)

print(kitty_clock.name)
print(kitty_clock.unit_price)
print(kitty_clock.quantity)
print(kitty_clock.total_cost())
print(kitty_clock == kitty_clock)

print("type coercion comparison")
print(InventoryItem("book", 9.99) == InventoryItem(unit_price="9.99", name="book"))

print(InventoryItem("book", 9.99))
print(InventoryItem("beast", "sloppy")) # Throws exception

That’s not terrible, and the features seem pretty reasonable. What I really don’t understand is the type validation. It uh… doesn’t work based on the native Python3 type labels, you have to manually do validator=attr.validators.instance_of(float) which just seems like nonsense I don’t want to have to remember.

I guess this is just par for the course- the official theory on Python types is you should run mypy on your laptop and not in production- but this seems sort of crazymaking to me. Why do we need to invent wild new frontiers in “works on my machine”, I thought we’d finally all agreed that that was bad.

Dataclasses is attrs, but run through the PEP committee process & vendored into the Standard Library. I’m a big fan of living off of the land in the standard library if at all possible, so I definitely wanted to take this for a test drive. To be perfectly honest, it’s much cleaner thanattrs:

from dataclasses import dataclass

@dataclass
class InventoryItem:
  name: str
  unit_price: float
  quantity: int = 0

  def total_cost(self) -> float:
    return self.unit_price * self.quantity

and usage is character-for-character identical to attrs. Except that that unit_price: float is a lie and worthless, because again, Python types are comments. So

print(InventoryItem("beast", "sloppy")) # Throws exception

does not throw an exception, and InventoryItem("kitty clock", "21.99", 5).total_cost() returns 21.9921.9921.9921.9921.99 which is frustrating. This is fixable-

  def __post_init__(self):
    self.unit_price = float(self.unit_price)

but is also supremely annoying. What’s the point of type annotations if they’re just going to be ignored?

Pydantic, the third library, fixes this.

from pydantic.dataclasses import dataclass

@dataclass
class InventoryItem:
  name: str
  unit_price: float
  quantity: int = 0

  def total_cost(self) -> float:
    return self.unit_price * self.quantity

Creates an object that behaves identically to the dataclass and attrs examples, and does automatic type validation, and will automatically convert "9.99" (the string) to 9.99 (the float). It’s perfect and wonderful.

This is not the native Pydantic model though, they prefer a base object over the magic decorator-

from pydantic import BaseModel

class InventoryItem(BaseModel):
  name: str
  unit_price: float
  quantity: int = 0

  def total_cost(self) -> float:
    return self.unit_price * self.quantity

This is a more natural integration into the Python type system, which is nice. Definitely less magical and cutesy than @attr.s.

Usage, unfortunately, is a little different-

kitty_clock = InventoryItem(name="kitty clock", unit_price=21.99, quantity=5)

No positional arguments allowed! I understand and empathize with this view, but I’m also in the business of “lots of little scripts quickly” business, so I kind of just like positional arguments.

The result object works the same in all other respects, and

print(kitty_clock.name)
print(kitty_clock.unit_price)
print(kitty_clock.quantity)
print(kitty_clock.total_cost())
print(kitty_clock == kitty_clock)

print("type coercion comparison")
print(InventoryItem(name="book", unit_price=9.99) == InventoryItem(unit_price="9.99", name="book"))

print(InventoryItem(name="book", unit_price=9.99))
print(InventoryItem(name="beast", unit_price="sloppy")) # Throws exception

gives the same results as the other two implementations.

So! Interesting stuff. I think I’m going to try and give Pydantic-implemented Dataclasses some run in the future. It’s a nice combination of sloppy-but-typed that really appeals to me.

kd@ 11/29/2020

gjs.guide

Another GJS docs site. All the usual basics, plus packaging, styleguides, and a great feature matrix.

Does tell you to use Glade though, which I gather is now controversial. Not a great time in desktop software.

kd@ 11/26/2020

taking an accessibility victory lap

It even looks pretty good in lynx-