Posts tagged with "Python"

BlogMore v2.5.0

1 min read

I've released BlogMore v2.5.0 out into the world. This release is the result of an observation Andy made about the Markdown library used in BlogMore (it might apply to MarkdownIT too, which would of course affect Hike): it doesn't support strikethrough markup out of the box.

I'm not sure I've ever used that markup anywhere on my blog, but I've used it often enough on GitHub (for example) that I just assume it's going to be there (and now I think about it Hike might be okay 'cos it uses Textual's Markdown widget which I know for certain uses GitHub-flavoured Markdown). But, for sure, the Markdown library doesn't implement it because it's not part of the original approach to Markdown.

On the other hand: implementing a form of strikethrough markup is one of the samples in the documentation on writing extensions, so it seemed like a very reasonable thing to add. Given this, one quick prompt to the agents later and strikethrough was added.

Now I'm going to totally have to find a reason to use this markup from time to time to time.

BlogMore v2.4.0

1 min read

After adding the stats page to BlogMore yesterday I realised that the main stylesheet was starting to get fairly large. Not so big that it was a problem for downloading (and of course normally it would get cached anyway), just more that it was carrying around styles for things that only appear on one page (the styles for the stats, for example).

So I decided to break it up. Now, as well as the main style.css, there's also:

This should keep the load times for the main pages and individual posts just a wee bit faster when first encountered, leaving off all those styles that aren't necessary.

All of which means, along with a wee wording change on the stats page, BlogMore v2.4.0 is in the wild and ready to use.

Hike v1.4.0

1 min read

Hike, my wee terminal-based Markdown viewer/browser, has had an update to v1.4.0. In this update I've made a change I've been meaning to make for ages: some support for "wiki links".

By that I mean the sort of link markup you often see in Markdown documents made with Obsidian:

In other words [[this]] instead of [this](kind-of-link.md)

Personally it's something I seldom need, but on the occasion I have been delving into my Obsidian vaults with Hike I've wished the links at least rendered "correctly", even if they wouldn't fully work.

On that note, it's worth keeping in mind that this "wiki link" implementation in Hike doesn't support something that Obsidian does: find the most likely target file for a given link. If you click such a link, Hike expects the file to be exactly where the link suggests. There's no going off and finding the most likely match in the "vault", etc (Hike obviously has no concept of a "vault").

I'm open to the idea of extending this at some point, perhaps, but not yet. The intention here isn't to build a terminal-based Obsidian-a-like, but instead to build and maintain a workable Markdown browser/viewer (and occasional editor).

Hike is licensed GPL-3.0 and available via GitHub and also via PyPI. If you have an environment that has pipx installed you should be able to get up and going with:

pipx install hike

If you're more into uv:

uv tool install hike

If you don't have uv installed you can use uvx.sh to perform the installation. For GNU/Linux or macOS or similar:

curl -LsSf uvx.sh/hike/install.sh | sh

or on Windows:

powershell -ExecutionPolicy ByPass -c "irm https://uvx.sh/hike/install.ps1 | iex"

BlogMore v2.3.0

1 min read

I've just pushed BlogMore v2.3.0 up to PyPI. This release has a couple of bug fixes and a couple of significant new features.

The first new feature, which came in as a request, is to add support for control over the themes used for code blocks, including independent control of the themes used for light and dark mode. With these you can specify any of the Pygments styles to use for code blocks. Personally, I prefer to have things blend in, but this now also gives you the chance to have them really contrast (use a light mode theme for dark-mode blog, or a dark mode theme for a light-mode blog).

The other big feature popped into my head earlier today and once I thought about it I had to have it. It's similar to something I had for the photography section of the older version of my website, consisting of a bunch of useless but fun stats and facts about the content.

Things like which hour of the day I tend to post during:

Hour of day

Or the day of the week:

Day of week

Or the month of the year1:

Month of year

This is designed to be turned off by default -- I can imagine most folk would not want this sort of thing on their blog -- but it can easily be turned on with with_stats. The location of the stats can also be controlled using stats_path.


  1. Unsurprisingly March is leaping ahead as of the time of writing. 

BlogMore v2.2.0

1 min read

I've just bumped BlogMore to v2.2.0. This release adds post counts to the archive page.

Post counts in the archive

The overall count appears at the top of the page, with further counts broken down for each year and each month. I've tried to ensure that the counts appear subtle enough, but still readable.

Also, serving the same purpose, but giving more information at a glance, I've added the same counts to the table of contents that appears to the right of the archive if you're on a suitably wide display.

Counts in the ToC

This means I can easily see that I've posted more times this month than I have in any other month since I started this blog. In fact, I've posted more times this month than I have in quite a few individual years in the past.

So... that's today's "I thought I'd added everything but oh look here's another thing to add" feature. Which goes some way to explain why there are so many posts this month, I guess.

BlogMore v2.1.0

2 min read

It's been a couple or so days since I last made any changes to BlogMore -- mainly because I've been messing with blogmore.el -- but yesterday morning I decided to make a change I've been wanting to make for a wee while.

Ensuring fenced codeblocks were handled was one of the things that was important when I started planning BlogMore and, while the result was looking good (thanks to Pygments), the way the block itself looked in the page wasn't quite to my taste. So yesterday I wrote out how I wanted things to be changed and tasked Copilot with getting the job done.

I'm pretty happy with the result.

(defun greet (name &optional (greeting "Hello"))
  "Prints a greeting to standard output."
  (format t "~A, ~A!~%" greeting name))

For the sake of any future reader, should I happen to tweak this even more at some point in the future, here's what the above looks like at the time of writing:

Example code block

As you can see: the language for the block is now shown to the left, and there's a handy "copy to clipboard" icon to the right. I'm still not sure I'm loving the subtle border around the sides and the bottom, I think I'm going to live with that for a few days and see how it sits with me.

I'm also wondering if I should tweak the name of the language a little too, so that it's capitalised correctly. Of course, I could just get into the habit of writing the language name in the start of the block with the correct casing...

def greet(name: str, greeting: str = "Hello") -> None:
    """Print a greeting to standard output.

    Args:
        name: The name of the person to greet.
        greeting: The greeting to use.
    """
    print(f"{greeting}, {name}!")

but given how many code blocks I've got in my blog by this point, where I've typed them in lower case... it's probably easier to just tweak it when presenting it. Moreover, I do want to try and keep my Markdown sources compatible with as many rendering engines as possible and I can't be sure that all of them would downcase before doing the language lookup (although you'd hope they all would).

Meanwhile... given how much more I like how code is looking now, I'm going to have to find more reasons to include code, and Pygments supports so many languages too! Even ones from my distant past...

Function Greet( cName, cGreeting )
   If cGreeting == NIL
      cGreeting := "Hello"
   EndIf
   ? cGreeting + ", " + cName + "!"
Return NIL

As an aside: I also just noticed that they list FoxPro, Clipper, xBase and VFP as aliases for xBase-type languages, but no Harbour! I might have to see about doing a PR for that...

Astral and OpenAI

1 min read

It's a couple of days now since the news hit that OpenAI are in the process of purchasing Astral. When I first saw this my initial reaction was pretty much "woah", followed by getting on with what I was doing.

Until, that is, I opened up the socials. On Mastodon, Reddit, BSky, Threads, etc... anywhere I followed any Python-based content, I was seeing very firm opinions posted. Plenty of folk either talking like it was the end of their tooling as they know it, or proudly boasting that they'd avoided uv and ruff (and lately ty too I guess -- not that I've really tried that yet myself) because they'd predicted this evil outcome from the start and they were untainted by this but look at all you idiots who fell for this long play!

Okay, I exaggerate slightly, but there were some pretty strong opinions kicking around, especially in the (often fairly smug) "I stayed pure and never used uv" camp.

Personally, I don't get it. The last I looked the tools I use that Astral are behind are FOSS. Also, the last I looked, plenty of FOSS tooling is written by folk who are either paid to do so (I had my moment), given some time in the day job to work on those tools, or just plain have a day job and also work on those tools. If, as plenty are speculating, the Astral purchase is an acqui-hire, the likely result is going to be one of those three scenarios.

If it isn't one of those scenarios, if work on uv and friends just ceases, well, at best some smart folk can fork the tools that are useful and keep them going (this is a major benefit of FOSS after all) and, at worst, well... we can fork them and agent the shit out of them. Right?

BlogMore v2.0.0

3 min read

Well... I thought I was done with all the major changes with BlogMore, but then a fairly simple request came in and that kicked off a whole load of changes (which in turn ran into one or two problems with GitHub Copilot).

I dived into this because I liked the idea anyway and I think it's time that I, as much as possible, moved the layout of my blog to clean URLs almost everywhere. I can't reasonably do it for actual posts because a) lots of posts point to other posts so there's a whole editing job to do there1 and b) there are some links out there that point to my blog posts2.

The result is BlogMore v2.0.0.

Anyone who's been following the experiment with BlogMore might wonder that the version number is up to v2 already, given it's barely a month old. The answer to that is pretty simple: I'm using semver for the version number and there's a breaking change in this release with no real way of maintaining backward compatibility.

So what's changed? First the simple ones: I added some more *_path configuration options to control the locations of:

All of those keep their default values from before, and so can remain backward compatible. Personally, I've updated the configuration for this blog so that I'm using:

archive_path: "/archive/index.html"
categories_path: "/categories/index.html"
search_path: "/search/index.html"
tags_path: "/tags/index.html"

which, combined with:

clean_urls: true

results in cleaner URLs for all of those site features.

Another thing that came to mind was what to do about pagination. Things like the date-based archives, categories, tags, and so on, can all run to multiple pages and so all generated pages of content using a pagination scheme. This needed its own approach. I decided on adding a setting to control the first page of content (page_1_path) and a setting for all subsequent pages (page_n_path). But there was a problem here: to keep this approach backward compatible I'd need to have different settings per area of the blog. That would mean something like tags_page_1_path, tags_page_n_path, year_page_1_path, year_page_n_path, month_page_1_path, month_page_n_path, and so on; the reason being each one would need its own set of variables so the user can set where {tag} goes in the path, or {year}, or {month}, but one of those is no use in another context, and so on.

All of this would have been total overkill and an unnecessary explosion of things that can and might need to be modified in the configuration file; it would also be a nightmare to document.

So I decided this: the page_1_path and page_n_path settings simply describe what goes on the end of any other URL in the blog, and because of that the defaults would have to be incompatible. I think the result is a lot tidier.

This also felt like a good time to make this change because, aside from one other blog out there, I don't think anyone else other than me is using BlogMore at the moment.

This change did mean that I'd need to edit some of the posts in my blog to update for the slightly changed layout, but it was a small enough job with minimal impact so it was worth it. I also used a tweaked setting for page_n_path:

page_n_path: "/page/{page}/index.html"

so that any paginated page has a URL that ends something like .../page/2/. This holds for any page on the blog that has multiple pages.

This is the point where I'd say something about how I think that's all the big changes in this project done... but I'm starting to suspect this isn't the plan the coding gods have for BlogMore.


  1. Although I suspect I could agent the shit out of that problem

  2. Although few enough that I probably should't let it stop me doing this. 

Too much work for Copilot?

1 min read

I don't know if it's just that GitHub Copilot is having a bad time at the moment, or if I've run into a genuine problem, but all isn't well today. After merging last night's result I kicked off another request related to a group of changes I want to make to BlogMore. It's a little involved, and it did take it a wee while to work on, but mostly it got the work done.

Again, as I said in the earlier post, I won't get into the detail of these changes yet, but they're fairly extensive and do include some breaking changes, so it's probably going to take a wee while to have it all come together. Claude's first shot at the latest change was almost there but with the glaring bug that it did all the work but didn't actually add the part that reads the configuration file settings and uses them (yeah, that's a good one, isn't it?).

So I asked it to fix it. It went off, worked on the issue, and then suddenly...

Denied

This surprised me. After the past few weeks I've had sessions where I've requested it do things way more frequently than this morning. I'm also nowhere near out of premium requests either:

Number of requests left

While the error, as shown, might be valid and might be down to my actions, it's massively unhelpful and doesn't really explain what I did to cause this or even how I can remedy it. This is made all the more frustrating by the fact that it seems to be saying I need to wait <duration> to try again. Yes, literally a placeholder of <duration>. O_o

One thing is for sure: this is another useful experiment in the current experiment. It's worth having the experience of having the tool screw with the flow. It doesn't come as a surprise, but it's a good reminder that using an agent that is hosted by someone else means you fully rely on their ability to keep it working, the whims of their API limits, and perhaps even your ability to pay.

When your model leaves you

4 min read

Yesterday evening, after dinner, but just before loading up a game released just a few hours earlier, I decided to kick off a change to BlogMore. Part of the ongoing experiment is this convenience aspect: where I can get some work going and then go and do something entirely different.

The nature of the change itself isn't important (I'll write about it when I release an update), but something that happened is. As usual, I did the prompt as an issue, and assigned it to Copilot. The first thing that was curious was that, after around 5 minutes, despite it having added the 👀 reaction (which it seems to use to indicate the agent has seen it has work to do), nothing happened. I didn't see an agent kick off doing the work. Eventually I gave up waiting and opened a Copilot session and asked it to set about dealing with the issue I'd raised.

It did as I requested, but apparently alongside another agent which had suddenly started doing the exact same work. Thinking it was a glitch (it's not like GitHub hasn't been having some trouble of late) I stopped the newest one and let the "original" go about its work.

A wee bit later, just before I started up my game and the stream, I checked in on how it was doing. It had finished, but in a quick test, I noticed a small bug. I prompted it to fix the problem, closed the tab, and went about the real business of killing bugs.

Fast forward a couple of hours, I was done with getting my arse kicked on Klendathu, I packed away my controller and headset and opened GitHub again to see where Copilot was at. It wasn't good:

@davep The model claude-sonnet-4.6 is not available for your account. This can happen if the model was disabled by your organization's policy or if your Copilot plan doesn't include access to it.

You can try again without specifying a model (just @copilot) to use the default, or choose a different model from the model picker.

If you want to contact GitHub about this error, please mention the following identifier so they can better serve you: 79b81d32-e26a-4898-bd41-4bba088d08f6

Wait... what? I've been using this for weeks now, as best as I can tell I've generally been making all the changes and improvements using Claude Sonnet 4.6; there's never been an issue. Then, suddenly, in the middle of a PR, I don't have it?

Quickly checking elsewhere, sure enough, I had access to almost no models. I can't remember what there was, but it wasn't much and all the Claude ones had disappeared. Even if I tested in the Copilot CLI1 I saw a very limited set of models.

Around this time I had two reactions: one was something like "cool, this is an important part of the experiment, knowing how it goes if your models are taken from you", the other was a curious "but Claude and I have an understanding about this codebase I can't trust some other model I've not been using".

As it was getting late into the evening and I still wanted to watch an episode of Stargate SG-1 (yes, I'm doing a full rewatch given it's on Netflix) I closed the MacBook and decided to check in on the issue in the morning.

Fast forward one SG1 episode later and, just before I headed to bed, I decided to do a quick search. While it could be a problem with my own account, it felt like it was more of a general issue. It was more of a general issue. At that point (around 23:20 UTC), checking in the GitHub app on my phone, I could see that some, if not all, of the models I was used to seeing, had come back.

As of this morning, as of time of writing, it's all looking back to how it was.

All the models back

All of which is a great reminder, and something useful in the experiment: what does happen when some third party takes away the models you're using to get your work done? In the time I've been building up BlogMore I've come to trust the quality of Claude Sonnet (in the sense that I know when and where I have to pay closer attention to what it's done, and where it'll very likely have done a pretty good job2), so finding that I'd possibly have to switch to some other model that I've had no experience with... I genuinely had a moment of concern about how and where I was going to take BlogMore.

Ultimately it's not actually a serious concern for me: while I aim to maintain it for a very long time to come (it is how I'm creating the site for this blog after all), BlogMore isn't that critical. Moreover, I know I could cease using Copilot to create and maintain it and I could tidy up the code and hand-update and hand-manage it. There's a reason why I decided to really dive into this using a language I'm extremely confident with.

But for those applications some might now be relying on, developed by someone keen but as yet unskilled, what way forward for them if such a situation were to happen and not be resolved?


  1. Which I'm not really using at the moment, but do have installed to experiment with. 

  2. So just like working with humans, oddly enough.