Archive

All published posts

2541 posts latest post 2026-08-10 simple view
Publishing rhythm
Jul 2026 | 14 posts
I’m really excited about htmx-ai [1], an amazing project by bufferhead-code [2]. It’s worth exploring! Add AI support to HTMX [3] References: [1]: https://github.com/bufferhead-code/htmx-ai [2]: https://github.com/bufferhead-code [3]: /htmx/
2.5 Admins 180: Email 777 – 2.5 Admins 2.5admins.com [1] How do you pronounce URL, is it U.R.L or Earle? I’m about 50/50, mostly when I am in a hurry I use Earle as it is one syllable and easy to say. I picked this up from MPJ of fun fun function, who took over Dev Tips. In this episide Jim uses Earle and they make fun of him. If it’s good enough for Jim, I am done with my 50/50 and I’m going all in on Earle. Episode also included a fastinating corrdinated attack that used Ars Technica profile photos communicate directions for the next attack via query parameters in the image url. References: [1]: https://2.5admins.com/2-5-admins-180/
- This really makes me want to try Dolphin Mixtral with ollama now. It looks very impressive from this video. The ability to keep adding features before becoming confused is though with a lot of these llms. Being chat based, this is not a co pilot replacement. I was really hoping for an in line co pilot like tool that I can run locally. I have not used co pilot yet, but I have had great luck with codeium.
- Great take on low code. I have definitely felt the pressure of being presented low code options, “look it does almost everything you need, and you can do it without code.” Granted there are tons of great low code environments that serve their markets well (things like zapier). As pointed out here when they fall short rather than being hard, it goes to nearly impossible. As Theo points out here many applications follow an 80/20 rule. 80% of the app is really easy to put together, and takes about 20% of the time, probably less. What no code does is it takes that 80% that is already easy, makes it even easier ( pitches it as faster whether or not that is true ), and makes the last 20% of the project impossibly hard to create and maintain, so you just should have picked a tool that had the capability of doing the whole thing from the start anyways.

poc is not product

A poc is not a product. I started focais, not in a rush, but as something that I already had a POC for and thought it would be easy. I wanted to build tools to make creating blog posts like this one easier. I stared with shots a tool that takes screenshots of websites. POC (proof of concept) # [1] For the poc, I made a single fastapi [2] endpoint that takes a url and returns a screenshot of the page. It converts the url into a key that I can lookup to see if I have the shot, if I don’t I go get it. With the open source libraries out there, this is not too hard of a task. Progress Thus Far # [3] - /shot But this wasn’t enough All it does so far for this first tool is take screenshots of websites, and give you a hosted image. Users # [4] To bring in users, I need to create a signup flow, with a database to store users, login, logout, and email recovery. I’ve never had to use an email service before that wasn’t already mandated by a company or an iternal smtp server. After some...
- I’ve heard prime say just give it the one eyed fighting kirby so many times, and execute it few times, and there is no way to find it online, so this will be the link that I will come to, when I need to remember what @theprimeagen means when he says Give it the one eyed fighting kirby. :s/\(.*\);/console.log(\1) So what is this? # [1] This is a vim substitute comand to replace text in the buffer. the one eyed fighting kirby is a regex capture group to capture everything between matches, and assign it a value to place back in after the match. substitute in a nutshell, :s/<what you want to replace>/<what you want to replace with> More examples # [2] Here is a contrived example of text. here there from here go there here = some_fuction(there) Now for some reason I want to switch all of the words here and there. I can do that with three capture groups, \1 is here, \2 is everything between, \3 is there. :%s/\(here\)\(.*\)\(there\)/\3\2\1 Just give it the one eyed fighting kirby ~Prime still struggling # [3] I thought this explaination from phind was good and more verbose than mine. --- describe this vim substitute regex :%s/(here)(.)(there)/\3\2\1 ANSWER | PHIND V9 M...
Java - ArchWiki wiki.archlinux.org [1] Today I learned that arch has a helper script archlinux-java to set the version of java. archlinux-java status archlinux-java set <JAVA_ENV_NAME> References: [1]: https://wiki.archlinux.org/title/java#Switching_between_JVM
GitHub - charmbracelet/mods: AI on the command line AI on the command line. Contribute to charmbracelet/mods development by creating an account on GitHub. GitHub · github.com [1] This is a pretty sweet interface into llms. I used it a bit with my son tonight while he was asking me for datapack ideas. ❯ mods -f 'I am trying to have fun on my minecraft server and am creating a minecraft datapack send me some load.mcfuncions that will make it fun' You can continue the conversation with a -C ❯ mods -C -f 'I like where you are going with number 4, can you make it so that it runs when a player opens a door' You can pass it some data curl https://waylonwalker.com/thoughts-on-unit-tests/ | mods -f 'summarize this post' References: [1]: https://github.com/charmbracelet/mods
GitHub - charmbracelet/mods: AI on the command line AI on the command line. Contribute to charmbracelet/mods development by creating an account on GitHub. GitHub · github.com [1] This is a pretty sweet interface into llms. I used it a bit with my son tonight while he was asking me for datapack ideas. ❯ mods -f 'I am trying to have fun on my minecraft server and am creating a minecraft datapack send me some load.mcfuncions that will make it fun' You can continue the conversation with a -C ❯ mods -C -f 'I like where you are going with number 4, can you make it so that it runs when a player opens a door' References: [1]: https://github.com/charmbracelet/mods
The work on mods [1] by charmbracelet [2]. AI on the command line References: [1]: https://github.com/charmbracelet/mods [2]: https://github.com/charmbracelet
Read a Range of Data - LIMIT and OFFSET - SQLModel SQLModel, SQL databases in Python, designed for simplicity, compatibility, and robustness. sqlmodel.tiangolo.com [1] Today I was running some sqlmodel queries through the sqlalchemy orm. Admittedly I’ve not done enough orm queries before, and I’ve done quite a bit of raw sql. I was trying to get objects from two separate models that had relationships setup. session.query(User, Images).where(User.id == 3).all() It is incredibly slow, and gives me the following warning. SELECT statement has a cartesian product between FROM element(s) What I learned from the SQLModel docs is that you should give it a join to correct this and go much faster. session.query(User, Images).join(Images).where(User.id == 3).all() References: [1]: https://sqlmodel.tiangolo.com/tutorial/limit-and-offset/

So after months of fighting with gf not going to template files, I finally decided to put in some effort to make it work.

This was the dumbest keybind in my config, that I copied from someone else without understanding it.

What I am trying to do #

I have jinja templates in a directory called templates. I want to bind gf to open a template file, but it is trying to open a new file ./base.html

{% extends "base.html" %}
{% if request.state.user %}
    {% block title %}Fokais - {{ request.state.user.full_name }} {% endblock %}
{% else %}
    {% block title %}Fokais {% endblock %}
{% endif %}
{% block content %}
    {% if request.state.user %}
        <h1 id="title"
            class="inline-block mx-auto text-5xl font-black leading-loose
            text-transparent bg-clip-text bg-gradient-to-r from-red-600
            via-pink-500 to-yellow-400 ring-red-500 text-shadow-xl
            text-shadow-zinc-950 ring-5">
            {{ request.state.user.full_name }}
        </h1>
    {% endif %}
    {% include "me_partial.html" %}
{% endblock %}

What did not work #

I tried all sorts of changes to my path, but it still didn’t work.

vim.api.nvim_command("set path+=templates/**")

What I found #

after digging into my keymap I found that I had remaped gf to edit years ago. This works great if the file is in your current directory, and if it’s not it makes the file. This bind completely breaks vim’s ability to :find files and was a terrible keybind that I added probably from someone else years ago and have literally never used this feature. If gf opens an empty file I always close it and assume that vim failed to :find the file.

-- Allow gf to open non-existent files
set("", "gf", ":edit <cfile><CR>")

Yes, after that fix I still needed to adjust my path #

I ended up with the following in my options.lua.

-- look for jinja templates in the templates directory
vim.opt.path:append("templates/**")
Template Designer Documentation — Jinja Documentation (3.1.x) jinja.palletsprojects.com [1] html [2] code generated by my jinja templates generally look half garbage because of indents and whitespace all over the place. I just learned about these pesky Whitespace Control characters that can get rid of the whitespace added from templating. You can also strip whitespace in templates by hand. If you add a minus sign (-) to the start or end of a block (e.g. a For tag), a comment, or a variable expression, the whitespaces before or after that block will be removed: {% for item in seq -%} {{ item }} {%- endfor %} References: [1]: https://jinja.palletsprojects.com/en/3.0.x/templates/#whitespace-control [2]: /html/
Intro - Minecraft Server on Docker (Java Edition) Documentation for Minecraft Server on Docker docker-minecraft-server.readthedocs.io [1] I just learned that if you can exec into the container running minecraft with the itzg/minecraft container you can run rcon-cli to get command access to the server. You need to set the RCON_PASSWORD if you want to access rcon remotely, but if you have not already done this and have access to the server you can just run rcon-cli when you are in. References: [1]: https://docker-minecraft-server.readthedocs.io/en/latest/
- Theo’s response puts a lot of my feelings about unit testing into words. It’s crazy how cargo culty it becomes that the echo chamber of twitter can bring in beliefs that we think we believe, but have not experienced enough or put enough thought in to form our own opinion. This video made me think so much that it turned into it’s own blog post Thoughts on Unit Testing [1] References: [1]: https://waylonwalker.com/thoughts-on-unit-tests

thoughts on unit tests

[1] Theo’s response puts a lot of my feelings about unit testing into words. Many of us have grown up in this world preaching unit testing. We often hear these statements “Everything must be unit tested, tests make code more maintainable.” In reality when we are not writing complex low level code unit tests are probably the wrong approach. We Finally Agree On Unit Tests - YouTube !https://www.youtube.com/watch?v=MbU-PKukdMw Dec 22, 2023 [2] Most of us are assemblers # [3] So much of software engineering is assembling existing well tested code. Crud applications, UI, Data Pipelines, building on top of battle tested code. Manufacturing Analogy - Unit Testing # [4] This kind of reminds me of Manufacturing. Individual components are QA tested with tests that look more like unit test. Parts like bearings, pistons, shafts, valves, they are all tested against sophisticated statistics of sample measurements. This is quite similar to unit testing. [5] You see measuring the indivi...
5 min read
Mastodon.py — Mastodon.py 2.2.2 documentation mastodonpy.readthedocs.io [1] Mastadon.py is a python api client for mastadon that makes it easy to cross post to mastadon. from mastodon import Mastodon Mastodon.create_app( 'pytooterapp', api_base_url = 'https://mastodon.social', to_file = 'pytooter_clientcred.secret' ) from mastodon import Mastodon mastodon = Mastodon(client_id = 'pytooter_clientcred.secret',) mastodon.log_in( '[email protected]', 'incrediblygoodpassword', to_file = 'pytooter_usercred.secret' ) mastodon.toot('Tooting from Python using #mastodonpy !') References: [1]: https://mastodonpy.readthedocs.io/en/stable/
I came across nvim-macroni [1] from jesseleite [2], and it’s packed with great features and ideas. 🤌 Save your macros for future use References: [1]: https://github.com/jesseleite/nvim-macroni [2]: https://github.com/jesseleite
jesseleite [1] has done a fantastic job with macroni.nvim [2]. Highly recommend taking a look. Save your macros for future use 🤌 References: [1]: https://github.com/jesseleite [2]: https://github.com/jesseleite/macroni.nvim
External Link stackoverflow.com [1] After struggling to get dependencies inside of middleware I learned that you can make global dependencies at the app level. I used this to set the user on every single route of the application without needing Depend on getting the user on each route. from fastapi import Depends, FastAPI, Request def get_db_session(): print("Calling 'get_db_session(...)'") return "Some Value" def get_current_user(session=Depends(get_db_session)): print("Calling 'get_current_user(...)'") return session def recalculate_resources(request: Request, current_user=Depends(get_current_user)): print("calling 'recalculate_resources(...)'") request.state.foo = current_user app = FastAPI(dependencies=[Depends(recalculate_resources)]) @app.get("/") async def root(request: Request): return {"foo_from_dependency": request.state.foo} References: [1]: https://stackoverflow.com/questions/72243379/fastapi-dependency-inside-middleware#answer-72480781
ikalnytskyi [1] has done a fantastic job with httpie-auth-store [2]. Highly recommend taking a look. Credential store plugin for HTTPie, attaches auth to ongoing request. References: [1]: https://github.com/ikalnytskyi [2]: https://github.com/ikalnytskyi/httpie-auth-store

Authentication from cli tools can be a bit of a bear, and I have to look it up every time. This is my reference guide for future me to remember how to easily do it.

I set up a fastapi server running on port 8000, it uses a basic auth with waylonwalker as the username and asdf as the password. The server follows along with what comes out of the docs. I have it setup to take basic auth, form username and password, or a bearer token for authentication.

curl #

The og of command line url tools.

# basic auth
curl -u 'waylonwalker:asdf' -X POST localhost:8000/token
# basic auth with password prompt
curl -u 'waylonwalker' -X POST localhost:8000/token
# token
curl -H 'Authorization: bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJ3YXlsb253YWxrZXIiLCJleHAiOjE3MDI5NTI2MDJ9.GeYNt7DNal6LTiPoavJnqypaMt4vYeriXdq5lqu1ILg' -X POST localhost:8000/token

wget #

My go to if I want the result to go into a file.

# basic auth
wget -q -O - --auth-no-challenge --http-user=waylonwalker --http-password=asdf --post-data '' localhost:8000/token

# token
wget -q -O - --header="Authorization: bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJ3YXlsb253YWxrZXIiLCJleHAiOjE3MDI5NTI2MDJ9.GeYNt7DNal6LTiPoavJnqypaMt4vYeriXdq5lqu1ILg" -O - --post-data '' localhost:8000/token

httpx #

An http client written in python, primarilty used with the python api, but has a nice cli.

# install
python3 -m pip install httpx

# basic auth
httpx -m POST --auth waylonwalker asdf http://localhost:8000/token

# basic auth with password prompt
httpx -m POST --auth waylonwalker - http://localhost:8000/token

# token
httpx -m POST --headers="Authorization" "bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJ3YXlsb253YWxrZXIiLCJleHAiOjE3MDI5NTI2MDJ9.GeYNt7DNal6LTiPoavJnqypaMt4vYeriXdq5lqu1ILg" http://localhost:8000/token

httpie #

A modern http client written in python.

# install
python3 -m pip install httpie

# basic auth
http POST localhost:8000/token -a waylonwalker:asdf

# basic auth with password prompt
http POST localhost:8000/token -a waylonwalker

# token
http POST localhost:8000/token -A bearer -a eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJ3YXlsb253YWxrZXIiLCJleHAiOjE3MDI5NTI2MDJ9.GeYNt7DNal6LTiPoavJnqypaMt4vYeriXdq5lqu1ILg

httpie with plugin #

# install
python3 -m pip install httpie-credential-store
# usage
http POST localhost:8000/token -A creds

httpie prompt #

http-prompt comes from the httpie org, and has an interactive cli interface into apis. You can even specify a spec file to autocomplete on api methods.

http-prompt localhost:8000 --auth waylonwalker:asdf --spec openapi.json
Handling Errors - FastAPI FastAPI framework, high performance, easy to learn, fast to code, ready for production fastapi.tiangolo.com [1] This page shows how to customize your fastapi [2] errors. I found this very useful to setup common templates so that I can return the same 404’s both programatically and by default, so it all looks the same to the end user. from fastapi import FastAPI, Request from fastapi.responses import JSONResponse class UnicornException(Exception): def __init__(self, name: str): self.name = name app = FastAPI() @app.exception_handler(UnicornException) async def unicorn_exception_handler(request: Request, exc: UnicornException): return JSONResponse( status_code=418, content={"message": f"Oops! {exc.name} did something. There goes a rainbow..."}, ) @app.get("/unicorns/{name}") async def read_unicorn(name: str): if name == "yolo": raise UnicornException(name=name) return {"unicorn_name": name} References: [1]: https://fastapi.tiangolo.com/tutorial/handling-errors/ [2]: /fastapi/
logs with FastAPI and Uvicorn · Issue #1508 · fastapi/fastapi Hello, Thanks for FastAPI, easy to use in my Python projects ! However, I have an issue with logs. In my Python project, I use : app = FastAPI() uvicorn.run(app, host="0.0.0.0", port=8000) And when... GitHub · github.com [1] Setting an additional log handler to the uvicorn logger for access logs in fastapi [2] was not straightforward, but This post was very helpful. @app.on_event("startup") async def startup_event(): logger = logging.getLogger("uvicorn.access") handler = logging.StreamHandler() handler.setFormatter(logging.Formatter("%(asctime)s - %(levelname)s - %(message)s")) logger.addHandler(handler) References: [1]: https://github.com/tiangolo/fastapi/issues/1508 [2]: /fastapi/
External Link stackoverflow.com [1] Setting tags in your fastapi endpoints will group them in the docs. You can also set some metadata around the tags to get nice descriptions. Here is a full example from the post. from fastapi import FastAPI tags_metadata = [ {"name": "Get Methods", "description": "One other way around"}, {"name": "Post Methods", "description": "Keep doing this"}, {"name": "Delete Methods", "description": "KILL 'EM ALL"}, {"name": "Put Methods", "description": "Boring"}, ] app = FastAPI(openapi_tags=tags_metadata) @app.delete("/items", tags=["Delete Methods"]) @app.put("/items", tags=["Put Methods"]) @app.post("/items", tags=["Post Methods"]) @app.get("/items", tags=["Get Methods"]) async def handle_items(): return References: [1]: https://stackoverflow.com/questions/63762387/how-to-group-fastapi-endpoints-in-swagger-ui#answer-63762765
waylon walker (@_WaylonWalker) on X Bloggers where do you put your markdown? X (formerly Twitter) · twitter.com [1] Most bloggers on my twitter blog right into a file that goes on git [2]. I kinda expected to have more database folk. I have my blog in markdown on git and the editing experience is top notch. I can just find files edit them in MY EDITOR, push them and I got a post. I am running thoughts in a sqlite database with a fastapi [3] backend, and holy crap the instant nature of posting feels so much better. Both sides have good points. References: [1]: https://twitter.com/_WaylonWalker/status/1734387536716308693 [2]: /glossary/git/ [3]: /fastapi/
Show some equivalent list comprehensions in filter examples · Issue #1068 · pallets/jinja I'm willing to write a pull-request for this, but I just want to see what people think before I write it. So the issue is this. I'm very familiar with python. I'm new to Jinja2. Often I find myself... GitHub · github.com [1] I often want to reach for non existing list comprehensions in jinja 2, Here are a few nice equivalents. a: {{ data | selectattr('x', 'gt', 5) | list }} b: {{ data | map(attribute='c') | list }} c: {{ data | selectattr('x', 'gt', 5) | map(attribute='c') | list }} References: [1]: https://github.com/pallets/jinja/issues/1068
External Link vi.stackexchange.com [1] I fixed my missing macro recording indicator that I lost and was never quite sure why. (because I forgot that I set cmdheight=0). vim.cmd [[ autocmd RecordingEnter * set cmdheight=1 ]] vim.cmd [[ autocmd RecordingLeave * set cmdheight=0 ]] References: [1]: https://vi.stackexchange.com/questions/39947/nvim-vim-o-cmdheight-0-looses-the-recording-a-macro-messages

I am working on fokais.com’s signup page, and I want to hide the form input during an htmx request. I was seeing some issues where I was able to prevent spamming the submit button, but was still able to get one extra hit on it.

It also felt like nothing was happening while sending the email to the user for verification. Now I get the form to disappear and a spinner to show during the request.

html">HTML #

Let’s start off with the form. It uses htmx to submit a post request to the post_request route. Note that there is a spinner in the post_request with the htmx-indicator class.

The intent is to hide the spinner until the request is running, and hide all of the form input during the request.

<form
  id="signup-form"
  hx-swap-oob="outerHTML"
  class="m-4 mx-auto mb-6 flex w-80 flex-col rounded-lg b p-4 shadow-xlc shadow-cyan-500/10"
  method="POST"
  action="{{ url_for('post_signup') }}"
  hx-post="{{ url_for('post_signup') }}"
>

<!--markata-attribution-->
  <input
    class="mx-8 mt-6 mb-4 border border-black bg-zinc-900 p-1 text-center focus:bg-zinc-800"

<!--markata-attribution-->
    type="text"

<!--markata-attribution-->
    value="{{ full_name }}"

<!--markata-attribution-->
    name="full_name"

<!--markata-attribution-->
    placeholder="Full Name"
  />

<!--markata-attribution-->
  {% if full_name_error %}

<!--markata-attribution-->
  <label class="-mt-6 mb-6 mx-8 text-red-500 p-1 text-center">

<!--markata-attribution-->
    {{ full_name_error }}

<!--markata-attribution-->
  </label>

<!--markata-attribution-->
  {% endif %}

<!--markata-attribution-->
  <input
    class="mx-8 mb-4 border border-black bg-zinc-900 p-1 text-center focus:bg-zinc-800"

<!--markata-attribution-->
    type="text"

<!--markata-attribution-->
    value="{{ username }}"

<!--markata-attribution-->
    name="username"

<!--markata-attribution-->
    placeholder="username"
  />

<!--markata-attribution-->
  {% if username_error %}

<!--markata-attribution-->
  <label class="-mt-6 mb-6 mx-8 text-red-500 p-1 text-center">

<!--markata-attribution-->
    {{ username_error }}

<!--markata-attribution-->
  </label>

<!--markata-attribution-->
  {% endif %}

<!--markata-attribution-->
  <input
    class="mx-8 mb-4 border border-black bg-zinc-900 p-1 text-center focus:bg-zinc-800"

<!--markata-attribution-->
    type="email"

<!--markata-attribution-->
    name="email"

<!--markata-attribution-->
    value="{{ email }}"

<!--markata-attribution-->
    placeholder="email"
  />

<!--markata-attribution-->
  {% if email_error %}

<!--markata-attribution-->
  <label class="-mt-6 mb-6 mx-8 text-red-500 p-1 text-center">

<!--markata-attribution-->
    {{ email_error }}

<!--markata-attribution-->
  </label>

<!--markata-attribution-->
  {% endif %}

<!--markata-attribution-->
  <input
    class="mx-auto w-32 mb-4 border border-black bg-purple-900 p-1 text-center focus:bg-zinc-800"

<!--markata-attribution-->
    type="submit"

<!--markata-attribution-->
    value="sign up"
  />

<!--markata-attribution-->
  <div role="status" class="mx-auto htmx-indicator">

<!--markata-attribution-->
    <svg

<!--markata-attribution-->
      class="mx-auto animate-spin h-5 w-5 text-white"

<!--markata-attribution-->
      xmlns="https://www.w3.org/2000/svg"

<!--markata-attribution-->
      fill="none"

<!--markata-attribution-->
      viewBox="0 0 24 24"
    >

<!--markata-attribution-->
      <circle

<!--markata-attribution-->
        class="opacity-25"

<!--markata-attribution-->
        cx="12"

<!--markata-attribution-->
        cy="12"

<!--markata-attribution-->
        r="10"

<!--markata-attribution-->
        stroke="currentColor"

<!--markata-attribution-->
        stroke-width="4"
      ></circle>

<!--markata-attribution-->
      <path

<!--markata-attribution-->
        class="opacity-75"

<!--markata-attribution-->
        fill="currentColor"
        d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"
      ></path>

<!--markata-attribution-->
    </svg>

<!--markata-attribution-->
    <p>Signing up...</p>

<!--markata-attribution-->
  </div>

<!--markata-attribution-->
</form>

Yes this is styled using tailwindcss.

https://waylonwalker.com/still-loving-tailwind/

CSS #

Let’s take a look at how we achieve switching between only spinner an only form inputs using css.

.htmx-indicator {
  @apply hidden;
  opacity: 0;
  transition: opacity 500ms ease-in;
}
.htmx-request button,
.htmx-request input[type="submit"],
.htmx-request input,
.htmx-request label {
  @apply hidden;
}
.htmx-request .htmx-indicator {
  opacity: 1;
  @apply block;
}
.htmx-request.htmx-indicator {
  opacity: 1;
  @apply block;
}

Final Result #

Here is the final result of me signing up for a new account in fokais.

GitHub - DataDog/ddqa: Datadog's QA manager for releases of GitHub repositories Datadog's QA manager for releases of GitHub repositories - DataDog/ddqa GitHub · github.com [1] DataDog ddqa is building out a textual app and deploying it with pyapp. They have CI setup to fully build and cross compile their textual tui into github releases that you can just download from their releases page. This is something I am looking at for markata. This would be pretty sweet to be able to make it just work on places like windows. It would also be interesting to try to build a full desktop app with pyapp. References: [1]: https://github.com/DataDog/ddqa
Check out ddqa [1] by DataDog [2]. It’s a well-crafted project with great potential. Datadog’s QA manager for releases of GitHub repositories References: [1]: https://github.com/DataDog/ddqa [2]: https://github.com/DataDog
I like cross-rs’s [1] project cross [2]. “Zero setup” cross compilation and “cross testing” of Rust crates References: [1]: https://github.com/cross-rs [2]: https://github.com/cross-rs/cross
If you’re into interesting projects, don’t miss out on pyapp [1], created by ofek [2]. Runtime installer for Python applications References: [1]: https://github.com/ofek/pyapp [2]: https://github.com/ofek
Adam Wathan (@adamwathan) on X Hear me out. X (formerly Twitter) · twitter.com [1] I’m going to give this trick a shot on my sites, and see how I like it. * { min-width: 0 } Down in the comments @adamwathan [2] goes on to say. Basically every layout overflow bug ever boils down to some flex or grid child needing min-width: 0 😄 Oh and @ryanflorence [3] also says in the comments. I … do this. References: [1]: https://twitter.com/adamwathan/status/1734696245015494711 [2]: https://twitter.com/adamwathan/ [3]: https://twitter.com/ryanflorence
External Link tushar.lol [1] Nice message by @tusharsadhwani [2]. Write it down. You had to dig deeper than face value at something. Write it down. You had to combine multiple pages of docs. Write it down. Someting was simply not obvious to you at first and it took someone else to give you that ah ha moment. Write it down. You had a small discovery that had a marginal impact on your day. Write it down. A blog does not have to be a Blog, it can be small meaningful posts. There are absolutely no rules. If you think you are going to end up with too many posts, that is a solvable problem, make a search, curate your favorite posts, make multiple feeds. At the end of the day. Write it down. This post itself is a thought, the smallest component to my blogging strategy. Write it down. References: [1]: https://tushar.lol/post/write-a-blog/ [2]: https://twitter.com/sadhlife
Path Operation Advanced Configuration - FastAPI FastAPI framework, high performance, easy to learn, fast to code, ready for production fastapi.tiangolo.com [1] Excluding routes from fastapi docs, can be done from the route configuration using `include_in_schema`. This is handy for routes that are not really api based or duplicates. From the Docs # [2] from fastapi import FastAPI app = FastAPI() @app.get("/items/", include_in_schema=False) async def read_items(): return [{"item_id": "Foo"}] trailing slash # [3] I’ve had better luck just routing both naked and trailing slash routes in fastapi [4]. I’ve had api’s deployed as a subroute to a site rather than a subdomain, and the automatic redirect betweens them tended to always get messed up. This is pretty easy fix for the pain is causes just give vim a yyp, and if you don’t want deuplicates in your docs, ignore one. from fastapi import FastAPI app = FastAPI() @app.get("/items") @app.get("/items/", include_in_schema=False) async def read_items(): return [{"item_id": "Foo"}] favicon.ico # [5] Now you do not need to deploy favicons to your api in any way, it is nice to have it in your browser tab, but more importantly ...
Protect API docs behind authentication? · Issue #364 · fastapi/fastapi Basic Question Does FastAPI provide a method for implementing authentication middleware or similar on the docs themselves (e.g. to protect access to /docs and /redoc)? Additional context My company... GitHub · github.com [1] You can protect your fastapi [2] docs behind auth so that not only can certain roles not run certain routes, but they cannot even see the docs at all. This way no one that shouldn’t be poking around can even discover routes they shouldn’t be using. Here is the soluteion provided by @kennylajara [3] from fastapi import FastAPI from fastapi.openapi.docs import get_redoc_html, get_swagger_ui_html from fastapi.openapi.utils import get_openapi import secrets from fastapi import Depends, FastAPI, HTTPException, status from fastapi.security import HTTPBasic, HTTPBasicCredentials app = FastAPI( title="FastAPI", version="0.1.0", docs_url=None, redoc_url=None, openapi_url = None, ) security = HTTPBasic() def get_current_username(credentials: HTTPBasicCredentials = Depends(security)): correct_username = secrets.compare_digest(credentials.username, "user") correct_password = secrets...
Looking for inspiration? llmware [1] by llmware-ai [2]. Unified framework for building enterprise RAG pipelines with small, specialized models References: [1]: https://github.com/llmware-ai/llmware [2]: https://github.com/llmware-ai
Cancel subscriptions Cancel subscriptions immediately or at the end of the subscription period with proration options, invoice handling, and automatic cancellation after failed payment attempts. stripe.com [1] This is a handy guide to cancelling stripe subscriptions. # Set your secret key. Remember to switch to your live secret key in production. # See your keys here: https://dashboard.stripe.com/apikeys import stripe stripe.api_key = "sk_test_51ODvHtB26msLKqCAPBAo1qkBBuIfT5tQBX6YFWCLMsPixIExxITCRVa9tNCIqkdQS8olhR79NYXsFWBPKsM3LbGO00zEcNQfNI" stripe.Subscription.modify( "sub_49ty4767H20z6a", cancel_at_period_end=True, ) You can even inverse it by flipping True to False and re activate the subscription. References: [1]: https://stripe.com/docs/billing/subscriptions/cancel#canceling

Today I am working on fokais.com, trying to get to a point where I can launch by workig through stripe integrations. This is my first time using stripe, so there has been quite a bit to learn, and I am probably building in more than I need to before launching, but I am learning, and not in a rush to launch.

I am building the fokais backent in python primarilyt with fastapi and sqlmodel on sqlite. My billing integration is going to be all Stripe.

Stripe Subscription Cancellations Docs #

Here is a link to the stripe docs for your refrence, especially if you want to see how to cancel subscriptions in other languages. They include code samples for many popular languages.

Cancel subscriptions | Stripe Documentation

User Model #

This is the part of the user model that includes the cancel and reactivate methods. It pretty much follows the stripe guide.

class UserBase(SQLModel, table=False):  # type: ignore[call-arg]
    username: str = Field(unique=True)
    full_name: str
    email: str
    email_verified: bool = False
    disabled: bool = False
    signup_date: Optional[datetime] = Field(default_factory=datetime.utcnow)
    stripe_customer_id: Optional[str]

    def cancel_subscription(self):
        for subscription in self.active_subscriptions:
            stripe.Subscription.modify(
                subscription.id,
                cancel_at_period_end=True,
            )
        self.refresh()

    def reactivate_subscription(self):
        for subscription in self.active_subscriptions:
            stripe.Subscription.modify(
                subscription.id,
                cancel_at_period_end=False,
            )
        self.refresh()

Cancellations api #

Here is the cancellations api. I created an are you sure form that I can link to from the accounts page with a normal anchor tag. Note that I am doing a POST request to do the cancellation from a form. I want this to work for any user whether there is js or not. This is an operation that will change the users data, and I want to make sure that it avoids all browser and cdn caching. As a scrappy startup we are running light on infrastructure and are caching hard at the CDN to avoid excessive server hits.

Note

 I am doing a `POST` request to do the cancellation from a form.
@pricing_router.get("/cancel")
@pricing_router.get("/cancel/")
def get_cancel(
    request: Request,
    current_user: Annotated[User, Depends(get_current_user_if_logged_in)],
):
    return config.templates.TemplateResponse(
        "cancel.html",
        {
            "request": request,
            "prices": products.prices,
            "products": products.products,
            "current_user": current_user,
        },
    )


@pricing_router.post("/cancel")
@pricing_router.post("/cancel/")
def post_cancel(
    request: Request,
    current_user: Annotated[User, Depends(get_current_user_if_logged_in)],
):
    current_user.cancel_subscription()
    return HTMLResponse('<p id="cancel" hx-swap-oob="outerHTML">Your Subscription has been Cancelled</p>')

Reactivations #

Reactivating accounts looks just about the same as cancelling, only flippng True to False.


@pricing_router.get("/reactivate")
@pricing_router.get("/reactivate/")
def get_reactivate(
    request: Request,
    current_user: Annotated[User, Depends(get_current_user_if_logged_in)],
):
    return config.templates.TemplateResponse(
        "reactivate.html",
        {
            "request": request,
            "prices": products.prices,
            "products": products.products,
            "current_user": current_user,
        },
    )


@pricing_router.post("/reactivate")
@pricing_router.post("/reactivate/")
def post_reactivate(
    request: Request,
    current_user: Annotated[User, Depends(get_current_user_if_logged_in)],
):
    current_user.reactivate_subscription()
    return HTMLResponse('<p id="reactivate" hx-swap-oob="outerHTML">Your Subscription has been reactivated</p>')

Full User Model #

This is the full user model, completely subject to change in the future, but it includes the cancel and reactivate methods.

class UserBase(SQLModel, table=False):  # type: ignore[call-arg]
    username: str = Field(unique=True)
    full_name: str
    email: str
    email_verified: bool = False
    disabled: bool = False
    signup_date: Optional[datetime] = Field(default_factory=datetime.utcnow)
    stripe_customer_id: Optional[str]

    @property
    def session(self):
        return next(get_session())

    @classmethod
    def get_by_id(cls, id):
        return next(get_session()).get(cls, id)

    def refresh(self):
        cache.set(f"active_subscriptions_{self.id}", None, 3600)
        cache.set(f"active_products_{self.id}", None, 3600)

    def get_checkout_sessions(self):
        return [
            stripe.checkout.Session.retrieve(s.stripe_checkout_session_id)
            for s in self.session.exec(select(CheckoutSession).where(CheckoutSession.user_id == self.id)).all()
            if s.stripe_checkout_session_id is not None
        ]

    def get_active_subscriptions(self):
        subscriptions = [
            s.subscription
            for s in [
                stripe.checkout.Session.retrieve(s.stripe_checkout_session_id)
                for s in self.session.exec(select(CheckoutSession).where(CheckoutSession.user_id == self.id)).all()
                if s.stripe_checkout_session_id is not None
            ]
            if s.status == "complete"
        ]
        active_subscriptions = [stripe.Subscription.retrieve(subscription) for subscription in subscriptions]
        return active_subscriptions

    def has_active_subscription(self):
        return len(self.active_subscriptions) > 0

    @property
    def active_subscriptions(self):
        active_subscriptions = cache.get(f"active_subscriptions_{self.id}")
        if active_subscriptions is not None:
            return active_subscriptions
        active_subscriptions = self.get_active_subscriptions()
        cache.set(f"active_subscriptions_{self.id}", active_subscriptions, 3600)

        return active_subscriptions

    @property
    def active_plans(self):
        subscriptions = self.active_subscriptions
        plans = [subscription.plan for subscription in subscriptions]
        return plans

    @property
    def subscription_to_plan(self):
        subscriptions = self.active_subscriptions
        plans = {subscription.id: subscription.plan.id for subscription in subscriptions}
        return plans

    @property
    def plan_to_subscription(self):
        plans = {v: k for k, v in self.subscription_to_plan.items()}

        return plans

    def get_active_products(self):
        plans = self.active_plans
        products = [stripe.Product.retrieve(plan.product) for plan in plans]
        return products

    @property
    def plan_to_product(self):
        plans = self.active_plans
        products = {plan.id: stripe.Product.retrieve(plan.product).id for plan in plans}
        return products

    @property
    def prodct_to_plan(self):
        plans = self.active_plans
        products = {stripe.Product.retrieve(plan.product).id: plan.id for plan in plans}
        return products

    @property
    def active_products(self):
        products = cache.get(f"active_products_{self.id}")
        if products is not None:
            return products
        products = self.get_active_products()
        cache.set(f"active_products_{self.id}", products, 3600)

        return products

    @property
    def best_active_subscription(self):
        subscriptions = self.active_subscriptions
        return subscriptions[0]

    @property
    def best_active_product(self):
        products = self.active_products
        products.sort(key=lambda p: p.metadata.get('level', 0))
        return products[0]

    @property
    def best_active_subscription(self):
        subscription_id = self.plan_to_subscription[self.prodct_to_plan[self.best_active_product.id]]
        return stripe.Subscription.retrieve(subscription_id)

    @property
    def config(self):
        product = self.best_active_product
        return product.metadata

    def subscription_status(self):
        subscriptions = self.active_subscriptions()

    def cancel_subscription(self):
        for subscription in self.active_subscriptions:
            stripe.Subscription.modify(
                subscription.id,
                cancel_at_period_end=True,
            )
        self.refresh()

    def reactivate_subscription(self):
        for subscription in self.active_subscriptions:
            stripe.Subscription.modify(
                subscription.id,
                cancel_at_period_end=False,
            )
        self.refresh()
External Link stripe.com [1] You can find your customers next billing date through the stripe api by using Invoice. and passing in customer, customer_details, subscription, or schedule. import stripe stripe.api_key = "sk_test_51ODvHtB26msLKqCAPBAo1qkBBuIfT5tQBX6YFWCLMsPixIExxITCRVa9tNCIqkdQS8olhR79NYXsFWBPKsM3LbGO00zEcNQfNI" invoice = stripe.Invoice.upcoming(customer="cus_NeZwdNtLEOXuvB") Within the invoice, you can find the next_payment_attempt as a epoch. date = datetime.fromtimestamp(invoice.next_payment_attempt) amount = invoice.amount_due currency = invoice.currency References: [1]: https://stripe.com/docs/api/invoices/upcoming
Search Use the search APIs to look up and retrieve objects in your Stripe data. Using search is a faster alternative to paginating through all resources. stripe.com [1] Stripe has it’s own query language for querying data. I’m just getting into using it and it seems pretty good so far. I needed to lookup the price for products. I was able to find prices for my product using the python api as shown below. stripe.Price.search(query="active: 'true' and product: 'prod_P8SfwtxJ45cWE2'") References: [1]: https://stripe.com/docs/search#search-query-language
stripe-keys-and-ids.tsv [1] tsv Prefix Description Notes ac_ Platform Client ID Identifier for an auth code/client id. acct_ Account ID Identifier for an Account object. aliacc_ Alipay Account ID Identifier for an Alipay account. ba_ Bank Account ID Identifier for a Bank Account object. btok_ Bank Token ID Identifier for a Bank Token object. card_ Card ID Identifier for a Card object. cbtxn_ Customer Balance Transaction ID Identifier for a Customer Balance Transaction object. ch_ Charge ID Identifier for a Charge object. cn_ Credit Note ID Identifier for a Credit Note object. cs_live_ Live Checkout Session ID Identifier for a checkout Session object in live mode. cs_test_ Test Checkout Session ID Identifier for a checkout Session object in test mode. cus_ Customer ID Identifier for a Customer object. dp_ Dispute ID Identifier for a Dispute object. evt_ Event ID Identifier for an Event object. fee_ Application Fee ID Identifier for an Application Fee object. file_ File ID Identifier for a File object. fr_ Application Fee Refund ID Identifier for an Application Fee Refund object. iauth_ Issuing Authorization ID Identifier for an Issuing Authorization object. ic_ Issuing Card ID ...

Looking for a Heroku replacement, What I found was shocking!

Your browser does not support the audio element. I’ve long hosted my personal blog as a static site on waylonwalker.com. It’s all markdown, converted to html [1], and shipped as is. It’s been great, I’ve moved it from GitHub Pages, to Netlify, tried Vercel for a minute, and have landed on Cloudflare Pages. Each migration has not really been that hard, it’s just pointing ci to a different host after the site has built. [2] What about server side # [3] Now the part that I have struggled with is how to cheaply host a server rendered application that can just live on forever without me paying for it. This is a harder problem as it costs more to keep servers spinning, memory, and disk all ready for you to use at a moments notice. Honestly # [4] I never really deployed anything that useful on heroku, but it seems like the klenex of the bunch that’s why they are in the title. I’ve moved between digital ocean and fly.io, and have had some great experiences with both. I just don’t want...
- Dang this is such a good message. I can’t exactly relate to being forced into the overworking situation that PirateSofware is talking about. I can relate to being conditioned to feeling a certain way and changing that is very difficult. I can also relate to not feeling like I am getting enough done in the day. Sometimes a bit of separation is good.