Published

All published posts

2565 posts latest post 2026-07-13 simple view
Publishing rhythm
Jun 2026 | 27 posts
Looking for inspiration? dotfiles [1] by jessarcher [2]. $HOME sweet $HOME References: [1]: https://github.com/jessarcher/dotfiles [2]: https://github.com/jessarcher

With the latest release of version of nvim 0.8.0 we get access to a new winbar feature. One thing I have long wanted somewhere in my nvim is navigation for pairing partners or anyone watching can keep track of where I am. As the driver it’s easy to keep track of the file/function you are in. But when you make big jumps in a few keystrokes it can be quite disorienting to anyone watching, and having this feedback to look at is very helpful.

“cybernetic soldier working on a rusting tape machine robot, cinematic lighting, detailed, cell shaded, 4 k, warm colours, concept art, by wlop, ilya kuvshinov, artgerm, krenz cushart, greg rutkowski, pixiv. cinematic dramatic atmosphere, sharp focus, volumetric lighting, cinematic lighting, studio quality” -s50 -W832 -H416 -C6.0 -Ak_lms -S2841371882

winbar #

nvim exposes the winbar api in lua, and you can send any text to the winbar as follows.

vim.o.winbar = "here"

You can try it for yourself right from the nvim command line.

:lua vim.o.winbar = "here"

Now you will notice one line above your file with the word here at the very beginning.

Clearing the winbar #

If you want to clear it out, you can just set it to an empty string or nil.

:lua vim.o.winbar = ""
:lua vim.o.winbar = nil

Setting up nvim-navic #

You will need to install nvim-navic if you want to use it. I added it to my plugins using Plug as follows.

call plug#begin('~/.local/share/nvim/plugged')
Plug 'SmiteshP/nvim-navic'
call plug#end()

Note! nvim-navic does require the use of the nvim lsp, so if you are not using it then maybe this won’t work for you.

I created an on_attach function long ago, cause that’s what Teej told me to do. Now I am glad I did, because it made this change super easy.

local function on_attach(client, bufnr)
    if client.server_capabilities.documentSymbolProvider then
        navic.attach(client, bufnr)
    end
end

Then you need to use that on_attach function on all of the lsp’s that you want navic to work on.

Then in a lua file you need to setup the winbar, for now I put this in my lsp-config settings file, but eventually I want to move my settings to lua and put it there.

vim.o.winbar = " %{%v:lua.vim.fn.expand('%F')%}  %{%v:lua.require'nvim-navic'.get_location()%}"

What my winbar looks like #

What I have right now is everything someone who is watching would need to know to navigate to the same place that I am in the project.

 waylonwalker/app.py   Link >  on_click
nvim-navic-example.webp

Diff #

Here are the changes that I made to to my plugins list and my lsp-config to get it.

 /home/u_walkews/.config/nvim/plugins.vim
call plug#begin('~/.local/share/nvim/plugged')
+Plug 'SmiteshP/nvim-navic'
#  /home/u_walkews/.config/nvim/lua/waylonwalker/lsp-config.lua
-local function on_attach() end
+local navic = require("nvim-navic")
+local function on_attach(client, bufnr)
+    if client.server_capabilities.documentSymbolProvider then
+        navic.attach(client, bufnr)
+    end
+end
+
+vim.o.winbar = " %{%v:lua.vim.fn.expand('%F')%}  %{%v:lua.require'nvim-navic'.get_location()%}"

GH commit #

If you want to see the change on GitHub, here is the diff

nvim-navic-setup-gh-diff.webp

Just starred nvim-navic [1] by SmiteshP [2]. It’s an exciting project with a lot to offer. Simple winbar/statusline plugin that shows your current code context References: [1]: https://github.com/SmiteshP/nvim-navic [2]: https://github.com/SmiteshP
I came across winbar.nvim [1] from fgheng [2], and it’s packed with great features and ideas. winbar config for neovim References: [1]: https://github.com/fgheng/winbar.nvim [2]: https://github.com/fgheng
Looking for inspiration? nvim-scrollbar [1] by petertriho [2]. Extensible Neovim Scrollbar References: [1]: https://github.com/petertriho/nvim-scrollbar [2]: https://github.com/petertriho
Looking for inspiration? nvim-hlslens [1] by kevinhwang91 [2]. Hlsearch Lens for Neovim References: [1]: https://github.com/kevinhwang91/nvim-hlslens [2]: https://github.com/kevinhwang91
I came across pre-commit [1] from pre-commit [2], and it’s packed with great features and ideas. A framework for managing and maintaining multi-language pre-commit hooks. References: [1]: https://github.com/pre-commit/pre-commit [2]: https://github.com/pre-commit

I really like having global cli command installed with pipx. Since textual 0.2.x (the css release) is out I want to be able to pop into textual devtools easily from anywhere.

“rusting tape machine robot, cinematic lighting, detailed, cell shaded, 4 k, warm colours, concept art, by wlop, ilya kuvshinov, artgerm, krenz cushart, greg rutkowski, pixiv. cinematic dramatic atmosphere, sharp focus, volumetric lighting, cinematic lighting, studio quality” -s50 -W832 -H416 -C12.0 -Ak_lms -S2404332231

Pipx Install #

You can pipx install textual.

pipx install textual

But if you try to run any textual cli commands you will run into a ModuleNotFoundError, because you need to install the optional dev dependencies.

Traceback (most recent call last):
  File "/home/u_walkews/.local/bin/textual", line 5, in <module>
    from textual.cli.cli import run
  File "/home/u_walkews/.local/pipx/venvs/textual/lib/python3.10/site-packages/textual/cli/cli.py", line 4, in <module>
    import click
ModuleNotFoundError: No module named 'click'

Pipx Inject #

In order to install optional dependencies with pipx you need to first install the library, then inject in the optional dependencies using the square bracket syntax.

pipx install textual
pipx inject textual 'textual[dev]'

I am working through the textual tutorial, and I want to put it in a proper cli that I can pip install and run the command without textual run --dev app.py. This is a fine pattern, but I also want this to work when I don’t have a file to run.

“An astronaut working in a lab, hacking on a computer terminal, htop is running, shallow depth of field beakers, test tubes, volumetric lighting, pink lighting, by victo ngai, killian eng vibrant colours, dynamic lighting, digital art” -s50 -W768 -H448 -C7.5 -Ak_lms -S3617210203

pyproject.toml entrypoints #

I set up a new project running hatch new, and added the following entrypoint, giving me a tutorial cli command to run.

...

[project.scripts]
tutorial = 'textual_tutorial.tui:tui'

https://waylonwalker.com/hatch-new-cli/

setup.py entrypoints #

If you are using setup.py, you can set up entrypoints in the setup command.

from setuptools import setup

setup(
    ...
    entry_points={
        "console_scripts": ["tutorial = textual_tutorial.tui:tui"],
    },
    ...
)

https://waylonwalker.com/minimal-python-package/

tui.py #

adding features

Now to get devtools through a cli without running through textual run --dev. I pulled open the textual cli source code, and this is what it does at the time of writing.

Note: I used sys.argv as a way to implement a --dev quickly tutorial. For a real project, I’d setup argparse, click, or typer. typer is my go to these days, unless I am really trying to limit dependencies, then the standard library argparse might be what I go with.

def tui():

    from textual.features import parse_features
    import os
    import sys

    dev = "--dev" in sys.argv # this works, but putting it behind argparse, click, or typer would be much better

    features = set(parse_features(os.environ.get("TEXTUAL", "")))
    if dev:
        features.add("debug")
        features.add("devtools")

    os.environ["TEXTUAL"] = ",".join(sorted(features))
    app = StopwatchApp()
    app.run()


if __name__ == "__main__":
    tui()

Other Flags??? #

If you look at the source, there is one other flag for headless mode.

FEATURES: Final = {"devtools", "debug", "headless"}

Run it #

Here it is running with tutorial --dev on the left, and textual console on the right.

textual-tutorial-devtools.webp
I like AnH0ang’s [1] project kedro-aim [2]. A kedro plugin that enables logging to the ml experiment tracker aim References: [1]: https://github.com/AnH0ang [2]: https://github.com/AnH0ang/kedro-aim
The work on PrismLauncher [1] by PrismLauncher [2]. A custom launcher for Minecraft that allows you to easily manage multiple installations of Minecraft at once (Fork of MultiMC) References: [1]: https://github.com/PrismLauncher/PrismLauncher [2]: https://github.com/PrismLauncher
I’m really excited about learn-cloudformation [1], an amazing project by widdix [2]. It’s worth exploring! Learn how to use Infrastructure as Code on AWS with the help of CloudFormation. References: [1]: https://github.com/widdix/learn-cloudformation [2]: https://github.com/widdix

For far too long I have had to fidget with v4l2oloopback after reboot. I’ve had this happen on ubuntu 18.04, 22.04, and arch.

After a reboot the start virtual camera button won’t work, It appears and is clickable, but never turns on. Until I run this command.

sudo modprobe v4l2loopback video_nr=10 card_label="OBS Video Source" exclusive_caps=1
“cell shaded, long, full body, shot of a cybernetic blue soldier with glowing pink eyes looking into a selfie camera with ring light, llustration, post grunge, 4 k, warm colors, cinematic dramatic atmosphere, sharp focus, pink glowing volumetric lighting, concept art by josan gonzales and wlop, by james jean, Victo ngai, David Rubín, Mike Mignola, Laurie Greasley, highly detailed, sharp focus,alien,Trending on Artstation, HQ, deviantart, art by artgem” -s50 -W832 -H416 -C12.0 -Ak_lms -S373882614

Today I learned that you can turn on kernel modules through some files in /etc/modules...

This is what I did to my arch system to get it to work right after boot.

echo "v4l2loopback" | sudo tee /etc/modules-load.d/v4l2loopback.conf
echo "options v4l2loopback video_nr=10 card_label=\"OBS Video Source\" exclusive_caps=1" | sudo tee /etc/modprobe.d/v4l2loopback.conf

I ran into an issue where I was unable to ask localstack for its status. I would run the command and it would tell me that it didn’t have permission to read files from my own home directory. Let’s fix it

The issue #

I would run this to ask for the status.

localstack status

And get this error

PermissionError: [Errno 13] Permission denied: '/home/waylon/.cache/localstack/image_metadata'

What happened #

It dawned on me that the first time I ran localstack was straight docker, not the python cli. When docker runs it typically runs as root unless the Dockerfile sets up a user and group for it.

“cell shaded, long, full body, shot of a cybernetic blue soldier with glowing pink eyes, llustration, post grunge, cinebatic dramatic atmosphere, sharp focus, pink glowing volumetric lighting, concept art by josan gonzales and wlop, by james jean, Victo ngai, David Rubín, Mike Mignola, Laurie Greasley, highly detailed, sharp focus,alien,Trending on Artstation, HQ, deviantart, art by artgem” -s50 -W832 -H416 -C12.0 -Ak_lms -S3517264680

How to fix it #

If you have sudo access to the machine you are on you can recursively change ownership to your user and group. I chose to just give myself ownership of my whole ~/.cache directory you could choose a deeper directory if you want. I feel pretty safe giving myself ownership to my own cache directory on my own machine.

whoami
# waylon

chown -R waylon:waylon ~/.cache

Now it’s working #

Running localstack status now gives me a nice status message rather than an error.

❯ localstack status
┌─────────────────┬───────────────────────────────────────────────────────┐
│ Runtime version │ 1.2.1.dev                                             │
│ Docker image    │ tag: latest, id: dbbfe0ce0008, 📆 2022-10-15T00:51:03 │
│ Runtime status  │ ✖ stopped                                             │
└─────────────────┴───────────────────────────────────────────────────────┘

Markata now allows you to create jinja extensions that will be loaded right in with nothing more than a pip install.

From the Changelog #

The entry for 0.5.0.dev2 from markata’s changelog

  • Created entrypoint hook allowing for users to extend marka with jinja exensions #60 0.5.0.dev2

“cybernetic soldier working on a rusting tape machine robot, cinematic lighting, detailed, cell shaded, 4 k, warm colours, concept art, by wlop, ilya kuvshinov, artgerm, krenz cushart, greg rutkowski, pixiv. cinematic dramatic atmosphere, sharp focus, volumetric lighting, cinematic lighting, studio quality” -s50 -W832 -H416 -C12.0 -Ak_lms -S1808537114

markata-gh #

The first example that you can use right now is markata-gh. It will render repos by GitHub topic and user using the gh cli, which is available in github actions!

Get it with a pip install

pip install markata-gh

Use it with some jinja in your markdown.

## Markata plugins

It uses the logged in uer by default.

{% gh_repo_list_topic "markata" %}

You can more explicitly grab your username, and a topic.
{% gh_repo_list_topic "waylonwalker", "personal-website" %}

How is this achieved #

The jinja extension details are for another post, but this is how markata-gh exposes itslef as a jinja extension.

class GhRepoListTopic(Extension):
    tags = {"gh_repo_list_topic"}

    def __init__(self, environment):
        super().__init__(environment)

    def parse(self, parser):
        line_number = next(parser.stream).lineno
        try:
            args = parser.parse_tuple().items
        except AttributeError:
            raise AttributeError(
                "Invalid Syntax gh_repo_list_topic expects <username>, or <username>,<topic> both must have the comma"
            )

        return nodes.CallBlock(self.call_method("run", args), [], [], "").set_lineno(
            line_number
        )

    def run(self, username=None, topic=None, caller=None):
        "get's markdown to inject into post"
        return repo_md(username=username, topic=topic)
“cybernetic soldier working on a rusting tape machine robot, cinematic lighting, detailed, cell shaded, 4 k, warm colours, concept art, by wlop, ilya kuvshinov, artgerm, krenz cushart, greg rutkowski, pixiv. cinematic dramatic atmosphere, sharp focus, volumetric lighting, cinematic lighting, studio quality” -s50 -W832 -H416 -C12.0 -Ak_lms -S2487720618

Entrypoints #

Then markata-gh exposes itself as an extension through entrypoints.

Creating entrypoints in pyproject.toml #

If your project is using pyproject.toml for packaging you can setup an entrypoint as follows.

[project.entry-points."markata.jinja_md"]
markta_gh = "markata_gh.repo_list:GhRepoListTopic"

Creating entrypoints in setup.py #

If your project is using setup.py for packaging you can setup an entrypoint as follows.

setup(
    ...
    entry_points={
        "markata.jinja_md": ["markta_gh" = "markata_gh.repo_list:GhRepoListTopic"]
    },
    ...
)
“cybernetic soldier working on a rusting tape machine robot, cinematic lighting, detailed, cell shaded, 4 k, warm colours, concept art, by wlop, ilya kuvshinov, artgerm, krenz cushart, greg rutkowski, pixiv. cinematic dramatic atmosphere, sharp focus, volumetric lighting, cinematic lighting, studio quality” -s50 -W832 -H416 -C12.0 -Ak_lms -S655826089

In my adventure to learn django, I want to be able to setup REST api’s to feed into dynamic front end sites. Potentially sites running react under the hood.

cell shaded full body shot of a cybernetic blue soldier with glowing eyes working ina lab, llustration, post grunge, pink glowing volumetric lighting, engulfed in smoke and fog, concept art by josan gonzales and wlop, by james jean, Victo ngai, David Rubín, Mike Mignola, Laurie Greasley, highly detailed, sharp focus,alien,Trending on Artstation, HQ, deviantart, art by artgem" -s50 -W832 -H416 -C18.0 -Ak_lms -S4270306418

Install #

To get started lets open up a todo app that I created with django-admin startproject todo.

pip install djangorestframework

Install APP #

Now we need to declare rest_framwork as an INSTALLED_APP.

INSTALLED_APPS = [
    ...
    "rest_framework",
    ...
]

create the api app #

Next I will create all the files that I need to get the api running.

mkdir api
touch api/__init__.py api/serializers.py api/urls.py api/views.py
cell shaded full body shot of a cybernetic blue soldier with glowing eyes working ina lab, llustration, post grunge, pink glowing volumetric lighting, concept art by josan gonzales and wlop, by james jean, Victo ngai, David Rubín, Mike Mignola, Laurie Greasley, highly detailed, sharp focus,alien,Trending on Artstation, HQ, deviantart, art by artgem" -s50 -W832 -H416 -C7.5 -Ak_lms -S3862698977

base/models.py #

I already have the following model from last time I was playing with django. It will suffice as it is not the focus of what I am learning for now.

Note the name of the model class is singular, this is becuase django will automatically pluralize it in places like the admin panel, and you would end up with Itemss.

from django.db import models

# Create your models here.

class Item(models.Model):
    name = models.CharField(max_length=200)
    created = models.DateTimeField(auto_now_add=True)

    def __str__(self):
        return f"{self.priority} {self.name}"

Next I will make some dummy data to be able to return. I popped open ipython and made a few records.

from base.models import Item

Item.objects.create(name='first')
Item.objects.create(name='second')
Item.objects.create(name='third')

api/serializers.py #

Next we need to set up a serializer to seriaze and de-serialize data between our model and json. You can specify each field individually or all of them by passing in __all__.

from rest_framework import serializers

from base.models import Item


class ItemSerializer(serializers.ModelSerializer):
    class Meta:
        model = Item
        fields = '__all__'

api/views.py #

“cell shaded full body shot of a shiny golden cybernetic soldier with glowing eyes looking through binoculars, llustration, post grunge, pink glowing volumetric lighting, engulfed in smoke and fog, concept art by josan gonzales and wlop, by james jean, Victo ngai, David Rubín, Mike Mignola, Laurie Greasley, highly detailed, sharp focus,alien,Trending on Artstation, HQ, deviantart, art by artgem” -s50 -W832 -H416 -C18.0 -Ak_lms -S2111691103 cell shaded full body shot of a shiny golden cybernetic soldier with glowing eyes looking through binoculars, llustration, post grunge, pink glowing volumetric lighting, engulfed in smoke and fog, concept art by josan gonzales and wlop, by james jean, Victo ngai, David Rubín, Mike Mignola, Laurie Greasley, highly detailed, sharp focus,alien,Trending on Artstation, HQ, deviantart, art by artgem command

Now we need a view leveraging the djangorestframework. The serializer we just created will be used to serialize all of the rows into a list of objects that Response can handle.

Note: to return a collection of model objects we need to set many to True

from rest_framework.decorators import api_view
from rest_framework.response import Response

from base.models import Item

from .serializers import ItemSerializer


@api_view(["GET"])
def get_data(request):
    items = Item.objects.all()
    serializer = ItemSerializer(items, many=True)
    return Response(serializer.data)

@api_view(['POST'])
def add_item(request):
    serializer = ItemSerializer(data = request.data)
    if serializer.is_valid():
        serializer.save()
    return Response()

api/urls.py #

“cell shaded full body shot of a shiny golden cybernetic soldier with glowing eyes looking at a map, llustration, post grunge, pink glowing volumetric lighting, engulfed in smoke and fog, concept art by josan gonzales and wlop, by james jean, Victo ngai, David Rubín, Mike Mignola, Laurie Greasley, highly detailed, sharp focus,alien,Trending on Artstation, HQ, deviantart, art by artgem” -s50 -W832 -H416 -C18.0 -Ak_lms -S192089936

Now we need to setup routing to access the views through an url.

from django.urls import path

from . import views

urlpatterns = [
        path('', views.get_data),
        path('add/', views.add_item),
        ]

todo/urls.py #

Then we need to include these urls from our api in the urls specified by settings.ROOT_URLCONf

from django.urls import path

urlpatterns = [
    ...
    path("api/", include("api.urls")),
]

Run it #

python manage.py runserver

Running the developement server and going to localhost:8000/api we can see the full list of items in th api.

djangorestframework-get-items.webp
I like openai’s [1] project whisper [2]. Robust Speech Recognition via Large-Scale Weak Supervision References: [1]: https://github.com/openai [2]: https://github.com/openai/whisper

Markata now uses hatch as its build backend, and version bumping tool. setup.py, and setup.cfg are completely gone.

“An astronaut working in a lab, there is a series of eggs ready to hatch baby snakes on the table, experiments running, beakers, test tubes, cyberpunk trending on artstation, neon lighting, volumetric lighting, pink lighting” -s50 -W800 -H450 -C7.5 -Ak_lms -S4048189038

0.5.0 is big #

Markata 0.5.0 is now out, and it’s huge. Even though it’s the backend of this blog I don’t actually have that many posts directly about it. I’ve used it a bit for blog fuel in generic ways, like talking about pluggy and diskcache, but very little have I even mentioned it.

Over the last month I made a big push to get 0.5.0 out, which adds a whole bunch of new configurability to markata.

Here’s the changelog entry.

  • Moved to PEP 517 build #59 0.5.0.dev1

My Personal Simple CI/CD #

Before cutting all of my personal projects over to hatch. The first thing I did was to setup a solid github action, hatch-actionthat I can resue.

It automatically bumps versions, using pre-releases on all branches other than main, with special branches for bumping major, minor, patch, dev, alha, beta, and dev.

hatch new –init #

To convert the project over to hatch, and get rid of setup.py/setup.cfg, I ran hatch new --init. This automatically grabs all the metadata for the project and makes a pyproject.toml that has most of what I need.

hatch new --init

I then manually moved over my isort config, put flake8 config into .flake8, and dropped setup.cfg.

lint-test #

Part of my hatch-action is to run a before-command, for markata, this runs all of my linting and testing in one hatch script called lint-test. If this fails CI will fail and I can read the report in the logs, make a fix and re-publish.

[tool.hatch.envs.default.scripts]
cov = "pytest --cov-report=term-missing --cov-config=pyproject.toml --cov=markata --cov=tests"
no-cov = "cov --no-cov"
lint = "flake8 markata"
format = "black --check markata"
sort-imports = "isort markata"
build-docs = "markata build"
lint-test = [
 "lint",
 "format",
 "seed-isort-config",
 "sort-imports",
 "cov",
]
test-lint = "lint-test"

Typical branching workflow #

with automatic versioning

My typical workflow is to work on features in their own branch where they do not automatically version or publish, they keep the same version they were branched off of. Then I do a pr to develop, which will do a minor,dev bump and publish a pre-relese to pypi.

# starting with version 0.0.0
Feature1 -- │
Feature2 -- ├── dev 0.1.0.dev1,2,3 ── main 0.1.0
Feature3 -- │

I will let several features collect in develop before cutting a full relese over to main. This gives me time to make sure the solution is what makes the most sense, I try to use it in a few projects, and generally its edges show, and another pr is warranted to make the feature useful for more use cases. After running and using these new releases in a few projects, I am confident that its ready and release to main.

managing prs #

Doing PR’s with gh, probably deserves its own post but here are some helpful commands.

gh pr create --base develop --fill
gh pr edit
gh pr diff | dunk
gh pr merge -ds

Building and publishing #

“An astronaut working in a lab, hacking on a computer terminal, htop is running, shallow depth of field beakers, test tubes, volumetric lighting, pink lighting, by victo ngai, killian eng vibrant colours, dynamic lighting, digital art” -s50 -W768 -H448 -C7.5 -Ak_lms -S3512493435

hatch makes building and publishing pretty straightforward. It’s one command inside my hatch-action to build and one to publish. On each project that uses my hatch-action I only need to give it a token that I get from PyPi.

env:
  HATCH_INDEX_USER: __token__
  HATCH_INDEX_AUTH: ${{ secrets.pypi_password }}

Full set of changes #

If you want to see all of the details on how markata moved over to hatch, you can check out this diff.

https://github.com/WaylonWalker/markata/compare/v0.4.0..v0.5.0.dev0

“An astronaut working in a lab, hacking on a computer terminal, htop is running, shallow depth of field beakers, test tubes, volumetric lighting, pink lighting, by victo ngai, killian eng vibrant colours, dynamic lighting, digital art” -s50 -W768 -H448 -C7.5 -Ak_lms -U 4.0 0.6 -S2409791448
lkwq007 [1] has done a fantastic job with stablediffusion-infinity [2]. Highly recommend taking a look. Outpainting with Stable Diffusion on an infinite canvas References: [1]: https://github.com/lkwq007 [2]: https://github.com/lkwq007/stablediffusion-infinity
Check out toumorokoshi [1] and their project deepmerge [2]. A deep merging tool for Python core data structures References: [1]: https://github.com/toumorokoshi [2]: https://github.com/toumorokoshi/deepmerge