<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/">
	<channel>
		<title>Dan&#39;s Blog</title>
		<link>https://danstewart.dev/</link>
		<description>Writing about software development, tools, debugging, and AI-assisted workflows.</description>
		<language>en-GB</language>
		<lastBuildDate>Thu, 03 Sep 2026 01:00:00 +0000</lastBuildDate>
		<atom:link href="https://danstewart.dev/feed.xml" rel="self" type="application/rss+xml" />
		
			
			<item>
				<title>My AI Development Setup</title>
				<link>https://danstewart.dev/posts/my-ai-development-setup/</link>
				<guid isPermaLink="true">https://danstewart.dev/posts/my-ai-development-setup/</guid>
				<pubDate>Thu, 03 Sep 2026 01:00:00 +0000</pubDate>
				<content:encoded>&lt;h2&gt;tl;dr&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://supacode.sh/&quot;&gt;Supacode&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://pi.dev/&quot;&gt;Pi&lt;/a&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://pi.dev/packages/@gotgenes/pi-permission-system&quot;&gt;pi-permission-system&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://plannotator.ai/&quot;&gt;Plannotator&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://zed.dev/&quot;&gt;Zed&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://revdiff.com/&quot;&gt;revdiff&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://ollama.com/pricing&quot;&gt;Ollama Cloud&lt;/a&gt;
&lt;ul&gt;
&lt;li&gt;Primarily with &lt;a href=&quot;https://ollama.com/library/glm-5.3-flash&quot;&gt;glm-5.3-flash&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;My Workflow&lt;/h2&gt;
&lt;h3&gt;1. &lt;a href=&quot;https://supacode.sh/&quot;&gt;Supacode&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;I start with &lt;a href=&quot;https://supacode.sh/&quot;&gt;supacode&lt;/a&gt;, it&#39;s basically the &lt;a href=&quot;https://ghostty.org/&quot;&gt;ghostty terminal&lt;/a&gt; but with some extra UI for creating and managing &lt;a href=&quot;https://git-scm.com/docs/git-worktree&quot;&gt;git worktrees&lt;/a&gt;. With AI I feel I am switching tasks a lot more frequently and worktrees make it easier to isolate work than regular branches do, especially on projects that use sqlite, since each worktree has its own database.&lt;/p&gt;
&lt;p&gt;I use this &lt;code&gt;.git/hooks/post-checkout&lt;/code&gt; script for initialising projects on new worktrees, but supacode also has support for defining setup and teardown scripts:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;#!/bin/bash

base_repo_git=$(git rev-parse --path-format=absolute --git-common-dir)
base_repo=$(dirname &amp;quot;$base_repo_git&amp;quot;)

# This check only runs on worktree creation
if [[ &amp;quot;$1&amp;quot; == &amp;quot;0000000000000000000000000000000000000000&amp;quot; ]]; then
    cp &amp;quot;$base_repo/config/master.key&amp;quot; &amp;quot;$(pwd)/config/master.key&amp;quot;
    cp &amp;quot;$base_repo/.envrc&amp;quot; &amp;quot;$(pwd)/.envrc&amp;quot;

    mise trust
    direnv allow

    bundle install
    npm install

    rails db:migrate
    rails db:seed
fi
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;2. &lt;a href=&quot;https://pi.dev/&quot;&gt;pi&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;I am currently using &lt;a href=&quot;https://pi.dev/&quot;&gt;pi&lt;/a&gt; as my agent harness, so far I&#39;ve kept things very light, I haven&#39;t noticed the need for any plugins except for &lt;a href=&quot;https://pi.dev/packages/@gotgenes/pi-permission-system&quot;&gt;@gotgenes/pi-permission-system&lt;/a&gt;. This allows me to control which commands (and on which paths) pi can run without approval. By default everything asks for approval and I am building up the list of safe commands and paths that can auto-approve. You can see &lt;a href=&quot;https://github.com/danstewart/dots/blob/963c557649223ef74ecc639e28a4393ff01b3a36/config/pi/agent/extensions/pi-permission-system/config.json&quot;&gt;my config file here&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;I mainly use the &lt;a href=&quot;https://ollama.com/library/glm-5.3-flash&quot;&gt;glm-5.3-flash&lt;/a&gt; model, it&#39;s fast, cheap and pretty reliable. It&#39;s not as good as &lt;a href=&quot;https://developers.openai.com/api/docs/models/gpt-5.6-sol&quot;&gt;Sol&lt;/a&gt; or &lt;a href=&quot;https://www.anthropic.com/claude/fable&quot;&gt;Fable&lt;/a&gt; but it&#39;s good enough for most work. I will often use &lt;a href=&quot;https://developers.openai.com/api/docs/models/gpt-5.6-sol&quot;&gt;Sol&lt;/a&gt; or &lt;a href=&quot;https://www.anthropic.com/claude/fable&quot;&gt;Fable&lt;/a&gt; to review the code generated by glm-5.3-flash, using &lt;a href=&quot;https://github.com/danstewart/dots/blob/963c557649223ef74ecc639e28a4393ff01b3a36/config/claude/skills/local-review/SKILL.md&quot;&gt;my local-review skill&lt;/a&gt;.&lt;/p&gt;
&lt;h3&gt;3. &lt;a href=&quot;https://plannotator.ai/&quot;&gt;Plannotator&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Plannotator is the real star and is the missing piece I&#39;ve been waiting for.&lt;/p&gt;
&lt;p&gt;My workflow is to ask the agent to make a plan and write it to &lt;code&gt;PLAN.md&lt;/code&gt;, I can then use &lt;code&gt;/plannotator-annotate PLAN.md&lt;/code&gt; to open the plan in plannotator where I can comment on it in a Google Docs-style interface:
&lt;img src=&quot;https://danstewart.dev/static/images/my-ai-development-setup/plannotator-annotate.png&quot; alt=&quot;Annotating a plan in Plannotator&quot; /&gt;&lt;/p&gt;
&lt;p&gt;After I submit, the feedback is automatically sent back to the agent and it will make a new plan. Once I&#39;m happy with the plan I tell the agent to get started. After the changes are complete I run &lt;code&gt;/plannotator-review&lt;/code&gt; to review the changes in &lt;code&gt;plannotator&lt;/code&gt;, where I can ask questions and provide feedback in a GitHub PR-style interface:
&lt;img src=&quot;https://danstewart.dev/static/images/my-ai-development-setup/plannotator-review.png&quot; alt=&quot;Reviewing code changes in Plannotator&quot; /&gt;&lt;/p&gt;
&lt;p&gt;The questions get answered live but the feedback is sent after clicking &amp;quot;Send Feedback&amp;quot; where it is again automatically sent back to the agent and it works on the changes.&lt;/p&gt;
&lt;p&gt;Plannotator also has a &amp;quot;Guided Review&amp;quot; function which splits the changes up into chunks and adds some extra context for each, this can be really helpful for larger changes:
&lt;img src=&quot;https://danstewart.dev/static/images/my-ai-development-setup/plannotator-guide.png&quot; alt=&quot;A guided review in Plannotator&quot; /&gt;&lt;/p&gt;
&lt;h3&gt;4. &lt;a href=&quot;https://zed.dev/&quot;&gt;Zed&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;If I want to drop in and edit some code myself (which is becoming increasingly rare) I will use &lt;a href=&quot;https://zed.dev/&quot;&gt;zed&lt;/a&gt;. I also use &lt;a href=&quot;https://revdiff.com/&quot;&gt;revdiff&lt;/a&gt; as my terminal diff tool.&lt;/p&gt;
&lt;h2&gt;Other tools I&#39;ve tried&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://maki.sh/&quot;&gt;maki&lt;/a&gt; - Maki is another lightweight harness, I really like it, I haven&#39;t uninstalled it yet but I have found &lt;a href=&quot;https://pi.dev/&quot;&gt;pi&lt;/a&gt; to be consistently quicker&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://herdr.dev/&quot;&gt;herdr&lt;/a&gt; - It&#39;s a bit like &lt;a href=&quot;https://supacode.sh/&quot;&gt;supacode&lt;/a&gt; but inside the terminal, which is nice if you aren&#39;t using &lt;a href=&quot;https://ghostty.org/&quot;&gt;ghostty&lt;/a&gt;, I just prefer the UX of supacode&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://omp.sh/&quot;&gt;omp&lt;/a&gt; - It&#39;s pi but with batteries included, it just felt a bit heavy to me and does a lot more than I need&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.onorca.dev/&quot;&gt;orca&lt;/a&gt; - Orca is kind of a command centre for different harnesses and handles worktrees too, it&#39;s pretty impressive, but again I felt like it did too much, I preferred the simplicity of supacode and pi&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;
&lt;p&gt;I&#39;m happy with this current setup, it really makes the AI agent feel more like a coworker, which is a bit weird but is a much nicer experience than copying and pasting things all over the place to get tools to link up. I think the biggest issue I have is reviewing and understanding all this AI-generated code to have the confidence to ship it, but plannotator is the best way I&#39;ve found so far.&lt;/p&gt;
</content:encoded>
			</item>
		
			
			<item>
				<title>Sandboxed Claude with Zed</title>
				<link>https://danstewart.dev/posts/sandboxed-claude-with-zed/</link>
				<guid isPermaLink="true">https://danstewart.dev/posts/sandboxed-claude-with-zed/</guid>
				<pubDate>Fri, 24 Apr 2026 01:00:00 +0000</pubDate>
				<content:encoded>&lt;p&gt;Here are my settings for running &lt;a href=&quot;https://claude.ai/&quot;&gt;claude&lt;/a&gt; in &lt;a href=&quot;https://zed.dev/&quot;&gt;zed&lt;/a&gt; using the &lt;a href=&quot;https://nono.sh/&quot;&gt;nono&lt;/a&gt; sandbox.&lt;br /&gt;
This allows me to safely(-ish) run claude and skip the permission checks.&lt;/p&gt;
&lt;h3&gt;Setup&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;# Install claude-agent-acp
npm i -g @agentclientprotocol/claude-agent-acp

# Find the path to claude-agent-acp for the next step
which claude-agent-acp
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Zed Settings&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-json&quot;&gt;{
  &amp;quot;agent_servers&amp;quot;: {
    &amp;quot;Claude Code (sandboxed)&amp;quot;: {
      &amp;quot;type&amp;quot;: &amp;quot;custom&amp;quot;,
      &amp;quot;command&amp;quot;: &amp;quot;nono&amp;quot;,
      &amp;quot;args&amp;quot;: [
        &amp;quot;run&amp;quot;,
        &amp;quot;--allow-cwd&amp;quot;,
        &amp;quot;--profile&amp;quot;,
        &amp;quot;my-claude-code&amp;quot;,
        &amp;quot;--&amp;quot;,
        &amp;quot;/Users/dan/.local/share/mise/installs/node/22/bin/claude-agent-acp&amp;quot;,
      ],
      &amp;quot;default_config_options&amp;quot;: {
        &amp;quot;mode&amp;quot;: &amp;quot;bypassPermissions&amp;quot;,
      },
    },
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;&lt;code&gt;nono&lt;/code&gt; Profile&lt;/h3&gt;
&lt;p&gt;This file should be created in &lt;code&gt;~/.config/nono/profiles/my-claude-code.json&lt;/code&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-json&quot;&gt;{
  &amp;quot;meta&amp;quot;: {
    &amp;quot;name&amp;quot;: &amp;quot;my-claude-code&amp;quot;,
    &amp;quot;version&amp;quot;: &amp;quot;1.0.0&amp;quot;,
    &amp;quot;description&amp;quot;: &amp;quot;Claude Code with additional project access&amp;quot;
  },
  &amp;quot;extends&amp;quot;: &amp;quot;claude-code&amp;quot;,
  &amp;quot;groups&amp;quot;: {
    &amp;quot;include&amp;quot;: [&amp;quot;git_config&amp;quot;]
  },
  &amp;quot;filesystem&amp;quot;: {
    &amp;quot;allow&amp;quot;: [&amp;quot;$WORKDIR&amp;quot;, &amp;quot;$HOME/.claude&amp;quot;, &amp;quot;$HOME/.superset&amp;quot;, &amp;quot;$HOME/projects&amp;quot;],
    &amp;quot;read&amp;quot;: [&amp;quot;$HOME/.local/share/mise/installs&amp;quot;]
  },
  &amp;quot;network&amp;quot;: {
    &amp;quot;block&amp;quot;: false
  },
  &amp;quot;env_credentials&amp;quot;: {}
}
&lt;/code&gt;&lt;/pre&gt;
&lt;hr /&gt;
&lt;p&gt;To double check it&#39;s all working I ask claude to list the files in my home directory - which should be blocked by &lt;code&gt;nono&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;If you have an issue then try running the full &lt;code&gt;nono&lt;/code&gt; command in your shell, then you can see the full error message, you will probably need to tweak the paths in your &lt;code&gt;nono&lt;/code&gt; profile.&lt;/p&gt;
</content:encoded>
			</item>
		
			
			<item>
				<title>Pipenv Crash Course</title>
				<link>https://danstewart.dev/posts/pipenv-crash-course/</link>
				<guid isPermaLink="true">https://danstewart.dev/posts/pipenv-crash-course/</guid>
				<pubDate>Mon, 05 Dec 2022 00:00:00 +0000</pubDate>
				<content:encoded>&lt;h3&gt;Installing packages&lt;/h3&gt;
&lt;p&gt;You can install packages with &lt;code&gt;pipenv install&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;The first time you do this it will create:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;A new virtual environment&lt;/li&gt;
&lt;li&gt;A file named &lt;code&gt;Pipfile&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;A file named &lt;code&gt;Pipfile.lock&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-shell&quot;&gt;pipenv install rich
pipenv install rich==12.6.0
pipenv install &amp;quot;rich&amp;lt;=12.5.1&amp;quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;fieldset class=&quot;admonition info mt-15&quot;&gt;
    &lt;legend&gt;ℹ️&lt;/legend&gt;
    When adding new dependencies &lt;code&gt;pipenv&lt;/code&gt; will update all other unpinned dependencies too.&lt;br /&gt;
    You can use &lt;code&gt;pipenv upgrade rich&lt;/code&gt; to install a package without updating everything else.
&lt;/fieldset&gt;
&lt;h3&gt;The &lt;code&gt;Pipfile&lt;/code&gt; and &lt;code&gt;Pipfile.lock&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;The &lt;code&gt;Pipfile&lt;/code&gt; defines all of your dependencies and any versions you want to pin.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-toml&quot;&gt;[[source]]
url = &amp;quot;https://pypi.org/simple&amp;quot;
verify_ssl = true
name = &amp;quot;pypi&amp;quot;

[packages]
rich = &amp;quot;*&amp;quot;

[dev-packages]

[requires]
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The &lt;code&gt;Pipfile.lock&lt;/code&gt; keeps track of the installed versions of your dependencies and any transitive dependencies, as well as hashes of the downloaded files.&lt;/p&gt;
&lt;p&gt;You should never need to touch this file, it is managed by &lt;code&gt;pipenv&lt;/code&gt;.&lt;/p&gt;
&lt;h3&gt;Updating&lt;/h3&gt;
&lt;p&gt;You can use &lt;code&gt;pipenv update&lt;/code&gt; to update all dependencies and re-generate your lock file.
If your &lt;code&gt;Pipfile&lt;/code&gt; does not specify any version constraints then the latest versions will be installed.&lt;/p&gt;
&lt;p&gt;You can use &lt;code&gt;pipenv upgrade &amp;lt;some-package&amp;gt;&lt;/code&gt; to upgrade a specific package, only that package and it&#39;s dependencies will be updated.&lt;/p&gt;
&lt;h3&gt;Deploying&lt;/h3&gt;
&lt;p&gt;You should use &lt;code&gt;pipenv sync&lt;/code&gt; when deploying your code to install dependencies as specified in the &lt;code&gt;Pipenv.lock&lt;/code&gt;.
You can use &lt;code&gt;pipenv sync --system&lt;/code&gt; to install the packages without a virtual environment too.&lt;/p&gt;
&lt;h3&gt;Installing from another repository&lt;/h3&gt;
&lt;p&gt;One of the main benefits over regular &lt;code&gt;pip&lt;/code&gt; is that you can specify where to install each individual dependency from, here is an example from the &lt;a href=&quot;https://pipenv.pypa.io/en/latest/advanced/#specifying-package-indexes&quot;&gt;pipenv documentation&lt;/a&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-toml&quot;&gt;[[source]]
url = &amp;quot;https://pypi.org/simple&amp;quot;
verify_ssl = true
name = &amp;quot;pypi&amp;quot;

[[source]]
url = &amp;quot;https://download.pytorch.org/whl/cu113/&amp;quot;
verify_ssl = false
name = &amp;quot;pytorch&amp;quot;

[dev-packages]

[packages]
torch = {version=&amp;quot;*&amp;quot;, index=&amp;quot;pytorch&amp;quot;}
numpy = {version=&amp;quot;*&amp;quot;}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This helps to avoid &lt;a href=&quot;https://www.activestate.com/resources/quick-reads/dependency-confusion/&quot;&gt;dependency confusion&lt;/a&gt;, an issue which &lt;a href=&quot;https://www.bleepingcomputer.com/news/security/pytorch-discloses-malicious-dependency-chain-compromise-over-holidays/&quot;&gt;recently bit pytorch&lt;/a&gt;.&lt;/p&gt;
</content:encoded>
			</item>
		
			
			<item>
				<title>Upgrading and housekeeping python dependencies</title>
				<link>https://danstewart.dev/posts/upgrading-pip-dependencies/</link>
				<guid isPermaLink="true">https://danstewart.dev/posts/upgrading-pip-dependencies/</guid>
				<pubDate>Sun, 05 Dec 2021 00:00:00 +0000</pubDate>
				<content:encoded>&lt;p&gt;This post will provide give a quick overview into two tools to help keep your &lt;code&gt;requirements.txt&lt;/code&gt; up to date and clean.&lt;/p&gt;
&lt;h2&gt;Updating dependencies using &lt;a href=&quot;https://github.com/simion/pip-upgrader&quot;&gt;pip-upgrader&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;The first tool is &lt;a href=&quot;https://github.com/simion/pip-upgrader&quot;&gt;pip-upgrader&lt;/a&gt; which provides an easy interface for finding out of date dependencies and updating them.&lt;/p&gt;
&lt;p&gt;Install via pip:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-shell&quot;&gt;python3 -m pip install pip-upgrader
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then run:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-shell&quot;&gt;python3 -m pip-upgrade
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You will be presented with a list of your dependencies which have new versions and you can select which to upgrade.&lt;/p&gt;
&lt;p&gt;You will of course still need to check for breaking changes in your dependencies but this tool makes it easy to find what packages have new versions.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;Figuring out what depends on what with &lt;a href=&quot;https://pypi.org/project/pipdeptree/&quot;&gt;pipdeptree&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;The second tool is &lt;a href=&quot;https://pypi.org/project/pipdeptree/&quot;&gt;pipdeptree&lt;/a&gt; which displays a dependency tree, making it easy to see which dependencies depend on other packages and help you clean up unused transitive dependencies.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-shell&quot;&gt;python3 -m pip install pipdeptree
python3 -m pipdeptree
&lt;/code&gt;&lt;/pre&gt;
</content:encoded>
			</item>
		
			
			<item>
				<title>A weird bug due to having Excel installed</title>
				<link>https://danstewart.dev/posts/weird-excel-bug/</link>
				<guid isPermaLink="true">https://danstewart.dev/posts/weird-excel-bug/</guid>
				<pubDate>Sun, 07 Nov 2021 00:00:00 +0000</pubDate>
				<content:encoded>&lt;p&gt;A while ago I was working on building a basic web page that allowed a user to upload a CSV file to an S3 bucket using a &lt;a href=&quot;https://docs.aws.amazon.com/AmazonS3/latest/userguide/using-presigned-url.html&quot;&gt;presigned S3 upload URL&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;This was easy enough, I tell AWS I want to upload a file with a &lt;code&gt;Content-Type&lt;/code&gt; of &lt;code&gt;text/csv&lt;/code&gt; and it will give me a pre-signed URL that I can use to upload without authenticating at the time of upload - nice and easy.&lt;/p&gt;
&lt;p&gt;The confusing part came when I gave it to the user and they got an error that the presigned URL was invalid, which was odd since it ran on my machine perfectly fine and I sent the user the exact file I was testing with.&lt;/p&gt;
&lt;p&gt;After some debugging I noticed the &lt;code&gt;Content-Type&lt;/code&gt; from the users request was not &lt;code&gt;text/csv&lt;/code&gt; but was &lt;code&gt;application/vnd.ms-excel&lt;/code&gt;, for the &lt;strong&gt;exact&lt;/strong&gt; same file.&lt;/p&gt;
&lt;p&gt;It turns out that if you have Microsoft Excel installed (which I did not but the user did) it &amp;quot;hijacks&amp;quot; the MIME-Type of CSV files - since Excel will handle the file.&lt;/p&gt;
&lt;p&gt;On Windows the MIME-Type of any given file type is stored in the registry, under &lt;code&gt;HKEY_LOCAL_MACHINE\Software\Classes&lt;/code&gt; and when using Excel as the default application for &lt;code&gt;.csv&lt;/code&gt; files it will update their MIME-Type to &lt;code&gt;application/vnd.ms-excel&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;In the end I just blanked out the &lt;code&gt;Content-Type&lt;/code&gt; header (probably not the best solution but this was a only used by a single user and was behind authentication) and everything started working again.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://stackoverflow.com/a/7076079&quot;&gt;Source&lt;/a&gt;&lt;/p&gt;
</content:encoded>
			</item>
		
			
			<item>
				<title>How to re-use SQLAlchemy models across multiple tables</title>
				<link>https://danstewart.dev/posts/re-use-sql-alchemy-models/</link>
				<guid isPermaLink="true">https://danstewart.dev/posts/re-use-sql-alchemy-models/</guid>
				<pubDate>Thu, 22 Apr 2021 01:00:00 +0000</pubDate>
				<content:encoded>&lt;p&gt;&lt;a href=&quot;https://github.com/danstewart/blog-snippets/tree/main/reuse-alchemy-models&quot;&gt;Source Code&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;When using SQLAlchemy declarative models you might find yourself wanting to re-use a model for multiple tables, this post will show you how to do it.&lt;/p&gt;
&lt;h3&gt;A new base model&lt;/h3&gt;
&lt;p&gt;A lot of tables share common columns, like an &lt;code&gt;id&lt;/code&gt;, &lt;code&gt;last_updated&lt;/code&gt;, etc...
To handle this we can declare a new base model that all others inherit from:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;def snake_caser(text):
    &amp;quot;&amp;quot;&amp;quot;
    Converts camelCase/PascalCase to snake_case
    &amp;quot;&amp;quot;&amp;quot;
    return re.sub(r&#39;(?&amp;lt;!^)(?=[A-Z])&#39;, &#39;_&#39;, text).lower()


class CustomBase(db.Base):
    &amp;quot;&amp;quot;&amp;quot;
    This is our customised base table
    It includes a table name generator that converts the ClassName to class_name
    an ID primary key column and a basic repr() method

    Any models that inherit from this will automatically get these things
    &amp;quot;&amp;quot;&amp;quot;
    __abstract__ = True

    @declared_attr
    def __tablename__(cls):
        return snake_caser(cls.__name__)

    id = Column(Integer, primary_key=True)

    def __repr__(self):
        return f&#39;&amp;lt;{self.__class__.__name__} {self.id}&amp;gt;&#39;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The key part to this is &lt;code&gt;__abstract__ = True&lt;/code&gt;, this tells SQLAlchemy that this isn&#39;t a real table (this is documented &lt;a href=&quot;https://docs.sqlalchemy.org/en/14/orm/inheritance.html?highlight=__abstract__#abstract-concrete-classes&quot;&gt;here&lt;/a&gt;).&lt;/p&gt;
&lt;p&gt;This base also includes an &lt;code&gt;id&lt;/code&gt; column, a basic &lt;code&gt;repr&lt;/code&gt; method and a &lt;code&gt;__tablename__&lt;/code&gt; declared attribute (more on this in a bit) that takes the class name and converts it to snake case.&lt;/p&gt;
&lt;h3&gt;The re-usable models&lt;/h3&gt;
&lt;p&gt;Now we can create our abstract base for our real tables:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;class UserBase(CustomBase):
    __abstract__ = True

    label = &#39;&#39;

    # Columns
    name = Column(String(40))
    email = Column(String(120))

    # Relationship
    @declared_attr
    def posts(cls):
        return relationship(f&#39;Post{cls.label}&#39;, backref=cls.__tablename__, lazy=True)


class PostBase(CustomBase):
    __abstract__ = True

    label = &#39;&#39;

    # Columns
    body = Column(Text(240))
    private = Column(Boolean)

    @declared_attr
    def user_id(cls):
        return Column(Integer, ForeignKey(f&#39;user_{snake_caser(cls.label)}.id&#39;))
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;These are both similar to the original &lt;code&gt;CustomBase&lt;/code&gt; but have dynamic foreign keys and relationships based on the &lt;code&gt;label&lt;/code&gt; property.&lt;/p&gt;
&lt;p&gt;These are declared using the &lt;code&gt;@declared_attr&lt;/code&gt; decorator which let&#39;s us use methods as attributes, which let&#39;s us dynamically create the mappings (documented &lt;a href=&quot;https://docs.sqlalchemy.org/en/14/orm/mapping_api.html#sqlalchemy.orm.declared_attr&quot;&gt;here&lt;/a&gt;).&lt;/p&gt;
&lt;p&gt;Now we can declare our real models, inheriting from &lt;code&gt;UserBase&lt;/code&gt; and &lt;code&gt;PostBase&lt;/code&gt; and all they need is a &lt;code&gt;label&lt;/code&gt; property:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;# ProductA Models
class UserProductA(UserBase):
    &amp;quot;&amp;quot;&amp;quot;
    This extends the UserBase model so is a copy of that but with the table name user_product_a

    We can overwrite or extend however we want but the only the we NEED is to define the label
    The label should be cased the same as the class name
    &amp;quot;&amp;quot;&amp;quot;
    label = &#39;ProductA&#39;


class PostProductA(PostBase):
    label = &#39;ProductA&#39;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;It&#39;s as easy as that, if you add a label property to the child models everything will just work ™️.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;UserProductA&lt;/code&gt; will have a relationship with &lt;code&gt;PostProductA&lt;/code&gt; and &lt;code&gt;PostProductA&lt;/code&gt; will have a foreign key to &lt;code&gt;user_product_a&lt;/code&gt;.&lt;/p&gt;
</content:encoded>
			</item>
		
	</channel>
</rss>
