Most recent blog posts

Or you can click on the categories to filter by topic

How I run standalone Python in 2025

January 14, 2025
0 comments Python

I don't do as much Python as I used to do. The few projects I still maintain, in Python, have a pyproject.toml and uv.lock. I.e. I'm using uv for getting the right executable version of Python and for installing dependencies. No more pip install ... and no more requirements.(in|txt). And definitely no poetry.lock.

And pyenv stopped working entirely when Python 3.12 came out. I used to use pyenv instead of Homebrew to get different versions of Python for different projects. uv is just that much better. I still use virtual envs, in the form of uv sync && source .venv/bin/activate when working inside a project and want to be able to type python ... and that referring to the exact version of Python with the relevant dependencies (from the pyproject.toml) installed.

However, there's a problem how: Outside of projects (that have a pyproject.toml and uv.lock) I no longer have a valid python executable. There's still a python3 executable that comes from /opt/homebrew/bin/python3 but that one I can't add dependencies to.
And many times I just want to whip up a quick script or start a repl, but with some certain dependencies installed. For example, to run...


import requests
print(requests.get('https://www.peterbe.com').headers['content-type'])
# prints 'text/html; charset=utf-8'

Again, uv to the rescue! I created ~/bin/python (plus chmod +x ~/bin/python) which now looks like this:


#!/bin/bash
set -x
uv run --python 3.12 --with requests python $@

Now I can quickly start a repl. Or if I create a /tmp/test-something.py I can just run that with


python /tmp/test-something.py

Truncated! Read the rest by clicking the link below.

My 2024 golf goals

December 30, 2024
0 comments Golf

When I started with an instructor at the start of this year (2024), I texted her about my goals. They were:

  • Eliminate big tee-off mistakes (slices, pulls)
  • Break 80
  • Handicap 6
  • Predictable positioning at setup

Admittedly, only "Break 80" and "Handicap 6" are measurable.

Text message about goals
Text message about goals

So how did I do?

Truncated! Read the rest by clicking the link below.

How I built an index of my blog posts on my GitHub profile page

December 13, 2024
1 comment Node, GitHub

If you go to https://github.com/peterbe it lists the most recent blog posts here on my blog. The page is rebuilt every hour using GitHub Actions. This blog post is about how I built that, so that you can build something just like it.

In case you don't have access or it's quicker to look at a picture, this is what it looks like:

List of blog posts

The way GitHub profiles work is you create a GitHub repo that is in the same name as your username. In my case, my username is peterbe, and the repo is thus called peterbe. So, it's named https://github.com/peterbe/peterbe. It has to be a public repo for this to work.

In that repo you have a README.md and mine looks like this: https://github.com/peterbe/peterbe/blob/main/README.md?plain=1 If you look carefully, the Markdown in that README.md contains:


<!-- blog posts -->
...
<!-- /blog posts -->

By default, HTML comments work in GitHub-flavored Markdown just like they do in HTML.

Then, I have a Node script that finds that inside the file and replaces its content with a list of Markdown links.

Truncated! Read the rest by clicking the link below.

Run TypeScript in Node without extensions

December 10, 2024
0 comments Node, JavaScript

A couple of months ago I wrote about "Node watch mode and TypeScript" which suggests using the @swc-node/register package to be able to run node on .ts file without first converting it to .js using tsc.

In Node 22, you don't even need @swc-node/register. You can use --experimental-strip-types instead. (Note the prefix "experimental"). See release notes here.

Imagine a script like this:


// example.ts

function c2f(c: number) {
  return (c * 9) / 5 + 32;
}
console.log(c2f(123));

you can execute it directly in Node v22 using:


❯ node --experimental-strip-types --no-warnings example.ts
253.4

Truncated! Read the rest by clicking the link below.

An ideal pattern to combine React Router with TanStack Query

November 18, 2024
1 comment React, JavaScript

I'm writing this blog post from an admin interface I built, which is a web app frontend for the backend behind peterbe.com. It's built as a single-page app in Vite, with React.
Vite, unlike frameworks like Remix or Next, doesn't come with its own routing. You have to add that yourself. I added React Router. Another thing you have to do yourself is a way to load remote data into the app for display and for manipulation. This is done as XHR requests happen on the client side. For that, I chose TanStack Query. If you haven't used it but used React, it's sugar for this type of code:


// DON'T DO THIS. USE TANSTACK QUERY

const [stuff, setStuff] = useState(null)
const [error, setError] = useState(null)
useEffect(() => {
  fetch('/api/some/thing')
    .then(r => r.json())
    .then(data => setStuff(data)
    .catch(err => setError(err))
}, [])

Truncated! Read the rest by clicking the link below.

brotli_static in Nginx

November 8, 2024
0 comments Nginx, Linux

tl;dr; for Ubuntu (>=24.10) run apt install libnginx-mod-http-brotli-static

Nginx is the web server I use on my personal web server. It sits in front of static files and Python and Node backend servers.
For static files, you can make it server the files compressed with Brotli, if the equivalent file exists on disk as a .br file.
Suppose you have this:


server {
    root /path/to/my/app/dist;
    server_name myapp.example.com;

    brotli_static on; // THIS!

    location / {
        try_files $uri /index.html;
    }

    ...
}

Truncated! Read the rest by clicking the link below.

Object.keys to return the known strings of an object in TypeScript

October 25, 2024
0 comments JavaScript

This one always confused me but by blogging about it, hopefully, it will stick.

This does NOT work in TypeScript:


const person = {
  firstName: "peter",
  lastName: "bengtsson",
  state: "happy"
}

const keys = Object.keys(person)
const randomKey = keys[Math.floor(Math.random() * keys.length)];
const value = person[randomKey]

It works in JavaScript, but in TypeScript you get an error on the last line (from the person[randomKey]):

Truncated! Read the rest by clicking the link below.

How I make my Vite dev server experience faster

October 22, 2024
0 comments React, Node, JavaScript

I have a web app that operates as a SPA (Single Page App). It's built on regular Vite + React + TypeScript. In production you just host the built static assets, but in development (for hot module reloading and stuff) I use the built-in dev server in Vite. This is what you get when you type vite without any other command line arguments.

But here's what happens, you're in the terminal and you type npm run dev and hit Enter. Then, using your trackpad or keyboard shortcuts you switch over to a browser tab and go to http://localhost:5173/. When you get there to the first request, Vite starts at the entry point, which is src/main.tsx and from there, it looks at its imports and starts transpiling the files needed. You can see what happens with vite --debug transform.

With debug:

Truncated! Read the rest by clicking the link below.

How to extend a function in TypeScript without repeating the signature types

October 16, 2024
0 comments JavaScript

Suppose you have a function, written in TypeScript, that is fine and complete. Now you want to write a function that works like that one but with just some slightly more parameters and other differences. It's not too different from a decorator function.

Extending the list of parameters

Let's imagine that you already have this function:


type Operation = "sum" | "sub" | "mul" | "div"

function calculator(a: number, b: number, op: Operation) {
  if (op === "sum") return a + b
  if (op === "sub") return a - b
  if (op === "mul") return a * b
  if (op === "div") return a / b
  throw new Error(op)
}

And this could, for the sake of argument, be in a different file and/or possibly outside your control. What you now want to do is write a new function that takes those same arguments, plus one more. And importantly, you don't want to have to spell out all the arguments one more time.

First, the WRONG way to solve this:


import { calculator } from './calculator' // The code in the snippet above

type Operation = "sum" | "sub" | "mul" | "div"

function doubleCalculator(a: number, b: number, op: Operation, c: number) {
  return calculator(calculator(a, b, op), c, op)
}

console.log(calculator(1, 2, "sum")) // 3
console.log(doubleCalculator(1, 2, "sum", 10)) // 13

Technically, it works, but you had to repeat the calculator function's signature.

Truncated! Read the rest by clicking the link below.

Trying out the new Bun "Compile to bytecode"

October 15, 2024
0 comments Bun, JavaScript

Bun 1.1.30 came out last week. What got me intrigued is this new option to bun build which is --bytecode. With it you can create an executable, supposedly compiled partially to bytecode which means it can start even faster.

I tried it on my hylite CLI which is a CLI, built with Bun, but works in all versions of Node, that can syntax highlight code to HTML on the command line. Here's what I did:

Truncated! Read the rest by clicking the link below.

Previous page
Next page