Posts tagged: python

All posts with the tag "python"

312 posts latest post 2026-05-06
Publishing rhythm
Jan 2026 | 3 posts
Fields Pydantic Docs · docs.pydantic.dev [1] exclude=True and repr=False is a good pydantic combination for secret attributes such as user passwords, or hashed passwords. exclude keeps it out of model_dumps, and repr keeps it out of the logs. from pydantic import BaseModel, Field class User(BaseModel): name: str = Field(repr=True) age: int = Field(repr=False) user = User(name='John', age=42) print(user) #> name='John' Note This post is a thought [2]. It’s a short note that I make about someone else’s content online #thoughts References: [1]: https://docs.pydantic.dev/2.7/concepts/fields/#field-representation [2]: /thoughts/
Hatch v1.10.0 - Hatch hatch.pypa.io [1] Hatch be flyin. This new release of hatch includes support for the new package installer uv which is just mind blowing fast compared to anything else we have in python right now. [tool.hatch.envs.default] installer = "uv" The other features are cool too, check them out. I’ll probably be using the test runner, but I’ve been waiting for the uv support since uv launched. Note This post is a thought [2]. It’s a short note that I make about someone else’s content online #thoughts References: [1]: https://hatch.pypa.io/latest/blog/2024/05/02/hatch-v1100/ [2]: /thoughts/
[1] This is my go to rich response container for clis written in python. It creates a nice box around the content on the screen and provides some nice separation in the output. It can be overdone, but comes in clutch when looking for that print statement in a long output. Note This post is a thought [2]. It’s a short note that I make about someone else’s content online #thoughts References: [1]: /static/https://rich.readthedocs.io/en/stable/reference/panel.html [2]: /thoughts/
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} --- This post sat in draft for months. I stumbled upon it again and found great success returning good error messages based on user preferences. the default remains json, but if a user requests text/html it will be an html [3] response, and text for ...
To allow access only to the , you can pass add the Resource field to the User Policy when you create a new token. { "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Action": [ "admin:*" ] }, { "Effect": "Allow", "Action": [ "kms:*" ] }, { "Effect": "Allow", "Action": [ "s3:*" ], "Resource": [ "arn:aws:s3:::<bucket>", "arn:aws:s3:::<bucket>/*" ] } ] }
You can inspect sqlite tables with the sqlite shell. note that you get into the shell with sqlite3 database.db .tables I also learned that .tables, .index and .schema are helper functions that query the sqlite_master table on the main database. Here is an output from my redka database. The sqlite_master table contains all the sqlite objects type, name, tbl_name, rootpage, and sql to create them. ❯ sqlite3 database.db "SELECT * from sqlite_master;" table|rkey|rkey|2|CREATE TABLE rkey ( id integer primary key, key text not null, type integer not null, version integer not null, etime integer, mtime integer not null ) index|rkey_key_idx|rkey|3|CREATE UNIQUE INDEX rkey_key_idx on rkey (key) index|rkey_etime_idx|rkey|4|CREATE INDEX rkey_etime_idx on rkey (etime) where etime is not null trigger|rkey_on_type_update|rkey|0|CREATE TRIGGER rkey_on_type_update before update of type on rkey for each row when old.type is not new.type begin select raise(abort, 'key type mismatch'); end table|rstring|rstring|5|CREATE TABLE rstring ( key_id integer not null, value blob not null, foreign key (key_id) references rkey (id) on delete cascade ) index|rstring_pk_idx|rstring|6|CREATE UN...
I recently had to update my copier-gallery command to trust my own templates because some of them have shell scripts that run afterwards. Be warned that this could be a dangerous feature to run on random templates you get off the internet, but these are all mine, so if I wreck it its my own fault. copier copy --trust <template> <destination> All the the copier copy api can be found with help. ❯ copier copy --help copier copy 8.3.0 Copy from a template source to a destination. Usage: copier copy [SWITCHES] template_src destination_path Hidden-switches: -h, --help Prints this help message and quits --help-all Prints help messages of all sub-commands and quits -v, --version Prints the program's version and quits Switches: -C, --no-cleanup On error, do not delete destination if it was created by Copier. --UNSAFE, --trust Allow templates with unsafe features (Jinja extensions, migrations, tasks) -a, --answers-file VALUE:str Update using this path (relative to `destination_path`) to find the answers file -d, --data VARIABLE=VALUE:str Make VARIABLE available as VALUE when rendering the template; may be given multiple times -f, --force Same as `--defaults --overwrite`...
Today I accidentally ran f2 in ipython to discover that it opens your $EDITOR! I use this feature quite often in zsh, it is bound to <c-e> for me, and since I have my environment variable EDITOR set to nvim it opens nvim when I hit <c-e>. Today I discovered that Ipython has this bound to F2. If you know how to set it to <c-e> let me know I’ve tried, a lot. export EDITOR=nvim ipython <F2> better yet add export EDITOR=nvim to your .zshrc # ~/.zshrc export EDITOR=nvim
I’ve really been enjoying using sqlmodel for my projects that need a database. One thing that I definitely lacked on for too long was indexing my database. I hit a point with one database where it was taking 7s for pretty simple paginated queries to return 10 records. For every field that you will be querying on, you can create an index, by setting it equal to Field(index=True) class Hero(SQLModel, table=True): id: int | None = Field(default=None, primary_key=True) name: str = Field(index=True) secret_name: str age: int | None = Field(default=None, index=True) example courtesy of the docs Note primary keys are indexed by default. The docs cover this pretty well, and in quite depth - Optimizing Queries [1] References: [1]: https://sqlmodel.tiangolo.com/tutorial/indexes/
Redirecting 15r10nk.github.io [1] This is a cool snapshot testing tool that automatically creates, and updates test values for you. Starting with some test code. from inline_snapshot import snapshot def something(): return 1548 * 18489 def test_something(): assert something() == snapshot() now if I run pytest my tests will fail because my assert will fail, but if I run pytest --inline-snapshot=create it will fill out my snapshot values and the file will then look like this. from inline_snapshot import snapshot def something(): return 1548 * 18489 def test_something(): assert something() == snapshot(28620972) Note This post is a thought [2]. It’s a short note that I make about someone else’s content online #thoughts References: [1]: https://15r10nk.github.io/inline-snapshot/ [2]: /thoughts/
inline-snapshot is a new tool that I am trying out for python testing. It takes snapshots of your outputs and places them inline with the test. Here is the most basic starter. import inline_snapshot def test_one(): assert 1 == snapshot() Now when I run pytest my tests will fail because my assert has no value, but if I run pytest --inline-snapshot=create it will fill out my snapshot values and the file will then look like this. import inline_snapshot def test_one(): assert 1 == snapshot(1) It also works with pydantic models. class MyModel(BaseModel): name: str age: int nickname: str | None = None def test_my_model_instance(): assert MyModel(name="Waylon", age=1) == snapshot(MyModel(name="Waylon", age=1)) def test_my_model_fields(): me = MyModel(name="Waylon", age=1, nickname='Waylon') assert me.name == snapshot("Waylon") assert me.age == snapshot(1) assert me.nickname == snapshot("Waylon")
Today I learned how to VACUUM a sqlite database and cut its size in about half. It’s a database that I have had running for quite awhile and has some decent traffic on it. Why is it important to do a VACUUM? In short its becuase the file system gets fragmented with as data is updated. On delete the files are removed from the database and marked as available for reuse in the filesystem, but the space is not reclaimed. To VACUUM a database, run the following sql command. You can do it right form the sqlite shell by running sqlite3. You will need about double the current size of the database as free space to do the VACUUM, you need space for a full copy, journaling or write ahead logs, and the existing database. VACUUM; The docs are fantastic for vacuum [1]. References: [1]: https://www.sqlite.org/lang_vacuum.html
Typer makes it easy to compose your cli applications, like you might with a web router if you are more familiar with that. This allows you to build smaller applications that compose into a larger application. You will see similar patterns in the wild, namely the aws cli which always has the aws <command> <subcommand> pattern. Lets setup the cli app itself first. You can put it in project/cli/cli.py. import typer from project.cli.api import api_app from project.cli.config import config_app from project.cli.user import user_app from project.cli.run import run_app app = typer.Typer() app.add_typer(api_app, name="api") app.add_typer(config_app, name="config") app.add_typer(user_app, name="user") app.add_typer(run_app, name="run") Creating an app that will become a command is the same as creating a regular app in Typer. We need to create a callback that will become our command, and a command that will become our subcommand in the parent app. import typer from rich.console import Console from project.config import get_config config_app = typer.Typer() @config_app.callback() def config(): "model cli" @config_app.command() def show( ): project_config = get_config(env) Cons...

One Day Build - Play Outside

Inspired by Adam Savage and his One Day builds on youtube. I often build things, and want to make them generally useful for others and over configure out of the gate. This project is purely for me inspired by a need I have. - play-outside [1] !How-To # [2] This post will not directly show how to make a weather app, but document the process that I went through to make mine. It will show the tools that I used to make it, and the final result. The Situation # [3] It often goes in our house ask dad while he is busy and he will probably just say yes without thinking much. This happens a lot when kids ask to go outside. I think sure, go for it, you will figure it out. Then my wife walks in and asks where they are, followed by, did you even check the weather, its -11 degrees outside right now. I need a tool for this decision making process Lungs # [4] You we have a family of not the most heathly lungs, we have my wife with lung cancer, one lung missing, and kids with asthma. We nee...
6 min read
I am working on a page for htmx-patterns [1] and I ran into a situation with lots of duplication. Especially when i am using tailwind I run into situations where the duplication can get tedious to maintiain. The solution I found is macros. Now I can use the same code for all of my links, and call the macro to use it. {% macro link(id, text, boosted=false) -%} <a class=" {% if id is none %} pointer-events-none bg-terminal-950 text-terminal-900 ring-terminal-900 {% else %} bg-terminal-950 hover:bg-terminal-900 hover:text-terminal-400 text-terminal-500 shadow-lg shadow-terminal-300/20 hover:shadow-terminal-300/30 ring-terminal-300 {% endif %} cursor-pointer block text-center font-bold py-2 px-4 rounded w-full ring-1 " {% if id is not none %} href="{{ url_for('boosted', id=id) }}" {% endif %} {% if boosted %} hx-boost="true" {% endif %}> {{ text }} </a> {%- endmacro %} <h2 class='text-3xl font-light mt-0 max-w-xl text-center prose-xl mt-8 text-terminal-500'> Boosted Links </h2> <div class='flex flex-row gap-4'> {{ link(prev_id, 'Previous', boosted=True) }} {{ link(next_id, 'Next', boosted=True) }} </div> <h2 class='text-3xl font-light mt-0 max-w-xl text-center...
jinja has a loop variable that is very handy to use with htmx [1]. Whether you want to implement a click to load more or an infinite scroll this loop variable is very handy. {% for person in persons %} <li {% if loop.last %} hx-get="{{ url_for('infinite', page=next_page) }}" hx-trigger="intersect once" hx-target="#persons" hx-swap='beforeend' hx-indicator="#persons-loading" {% endif %} {{ person.name.upper() }} - {{ person.phone_number }} </li> {% endfor %} Now for every chunk of contacts that we load we will trigger the infinite scroll by loading more once the last one has intersected the screen. References: [1]: /htmx/
Out of the box FastAPI [1].">Starlette does not support url_for with query params. When trying to use url_for with query params it throws the following error. starlette.routing.NoMatchFound: No route exists for name "infinite" and params "page" In my searching for this I found starlette issue #560 [2] quite helpful, but not complete, as it did not work for me. import jinja2 if hasattr(jinja2, "pass_context"): pass_context = jinja2.pass_context else: pass_context = jinja2.contextfunction @pass_context def url_for_query(context: dict, name: str, **params: dict) -> str: request = context["request"] url = str(request.url_for(name)) if params == {}: return url from urllib.parse import parse_qs, urlencode, urlparse, urlunparse # Parse the URL parsed_url = urlparse(url) # Parse the query parameters query_params = parse_qs(parsed_url.query) # Update the query parameters with the new ones query_params.update(params) # Rebuild the query string updated_query_string = urlencode(query_params, doseq=True) # Rebuild the URL with the updated query string updated_url = urlunparse( ( parsed_url.scheme, parsed_url.netloc, parsed_url.path, parsed_url.params, updated_...
Kind (Kubernetes in Docker) is a tool that makes it easy to create and tear down local clusters quickly. I like to use it to test out new workflows. Argocd is a continuous delivery tool that makes it easy to setup gitops workflows in kubernetes. Here is how you can setup a new kind cluster and install argocd into it using helm, the kubernetes package manager. kind create cluster --name argocd # your first time through you need to add the argocd repo helm repo add argo https://argoproj.github.io/argo-helm helm repo update # install argocd into the cluster helm install argo argo/argo-cd --namespace argocd --create-namespace # deploy the app of apps kubectl apply -f apps/apps.yaml If you want to add repos and apps to your cluster you can use the argo cli to do that, but first you will need forward the argocd port and login. # Wait until Argo CD API server is available echo "Waiting for Argo CD API server to be available..." while ! kubectl wait --for=condition=available --timeout=60s deployment/argo-argocd-server -n argocd; do echo "Waiting for Argo CD API server to be ready..." sleep 10 done kubectl port-forward svc/argo-argocd-server -n argocd 8080:443 & argocd_admin_pa...
![[None]] I’ve been using these decorators to modify the behavior of specific routes. It will do things like 404 admin only routes in a way that looks just like fastapi [1]’s default, or only allow certain roles into the route, or redirect unauthenticated users to login. After listening to yesterday’s syntaxfm I’m now really thinking about middleware and the benefits it might have. middleware would make it easy to apply things like admin to an entire admin router, so you wont forget it on any one admin route. It will look cleaner as the admin checker is only applied once per router, not once per route. import inspect import time from functools import wraps from inspect import signature from fastapi import Request from fastapi.responses import FileResponse, JSONResponse, RedirectResponse from starlette import status from fokais.config import get_config from fokais.models.user import Role config = get_config() admin_routes = [] authenticated_routes = [] not_cached_routes = [] cached_routes = [] def not_found(request): hx_request_header = request.headers.get("hx-request") user_agent = request.headers.get("user-agent", "").lower() if "mozilla" in user_agent or "webkit" i...
![[None]] jinja’s url_for in fastapi [1] does not account for https by default, there is probably a better way, but this is a way that allows me to configure when I use http vs https. @pass_context def https_url_for(context: dict, name: str, **path_params: Any) -> str: """ always convert http to https """ request = context["request"] http_url = request.url_for(name, **path_params) return str(http_url).replace("http", "https", 1) def get_templates(config: BaseSettings) -> Jinja2Templates: templates = Jinja2Templates(directory="templates") templates.env.globals["https_url_for"] = https_url_for ## only use the default url_for for local development, for dev, qa, and prod use https if os.environ.get("ENV") in ["dev", "qa", "prod"]: templates.env.globals["url_for"] = https_url_for console.print("Using HTTPS") else: console.print("Using HTTP") return templates Note This post is a thought [2]. It’s a short note that I make about someone else’s content online #thoughts References: [1]: /fastapi/ [2]: /thoughts/