<?xml version="1.0" encoding="UTF-8"?><rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>Bruno Alla - TILs</title><description>Things I Learned (TILs)</description><link>https://browniebroke.com/</link><item><title>Configuring automatic generation of GitHub releases</title><link>https://browniebroke.com/tils/2026-02-02-configuring-automatic-generation-of-github-releases/</link><guid isPermaLink="true">https://browniebroke.com/tils/2026-02-02-configuring-automatic-generation-of-github-releases/</guid><pubDate>Mon, 02 Feb 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;On a few projects, I&apos;ve been using the GitHub automatic release notes generations for while to get a list of all the changes that have been merged since the last release:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./_2026-02-02-generate-release-notes.png&quot; alt=&quot;Generate release notes&quot; /&gt;&lt;/p&gt;
&lt;p&gt;I believe it&apos;s generated from the title of all the PRs which have been merged since the last release, and I usually need to edit it manually to omit internal changes which don&apos;t impact the code installed by the users, like dependabot updates.&lt;/p&gt;
&lt;p&gt;I only discovered today that &lt;a href=&quot;https://docs.github.com/en/repositories/releasing-projects-on-github/automatically-generated-release-notes#configuring-automatically-generated-release-notes&quot;&gt;this is configurable&lt;/a&gt; to exclude some authors or labels automatically. This is achieved via the &lt;code&gt;.github/release.yml&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;I stumbled upon this feature after seeing the config file &lt;a href=&quot;https://github.com/tox-dev/pyproject-fmt/commit/7a3d989b66e18d1350aa8073b13c14894eeb0864&quot;&gt;edited in the &lt;code&gt;pyproject-fmt&lt;/code&gt; project&lt;/a&gt;.&lt;/p&gt;
</content:encoded></item><item><title>Using uv.lock file in tox</title><link>https://browniebroke.com/tils/2025-12-05-using-uvlock-file-in-tox/</link><guid isPermaLink="true">https://browniebroke.com/tils/2025-12-05-using-uvlock-file-in-tox/</guid><pubDate>Fri, 05 Dec 2025 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;I&apos;ve migrated all my open source packages to uv a while back, and the ones which are Django packages are using tox to run the tests against all supported combination of Django + Python versions.&lt;/p&gt;
&lt;p&gt;However, I didn&apos;t realise that the tox-uv plugin does not install dependencies from the uv lockfile by default.&lt;/p&gt;
&lt;p&gt;The realisation came &lt;a href=&quot;https://github.com/django-money/django-money/pull/809#issuecomment-3583474445&quot;&gt;in the &lt;code&gt;django-money&lt;/code&gt; project&lt;/a&gt;: the test were running fine despite the lockfile being out of date.&lt;/p&gt;
&lt;p&gt;This is &lt;a href=&quot;https://github.com/tox-dev/tox-uv#uvlock-support&quot;&gt;explicitly documented&lt;/a&gt; on the tox plugin project, but I completely missed that, and assumed it would just magically work.&lt;/p&gt;
&lt;p&gt;The &lt;a href=&quot;https://github.com/django-money/django-money/pull/811&quot;&gt;fix&lt;/a&gt; I suggested is heavily inspired by Adam Johnson&apos;s setup (&lt;a href=&quot;https://github.com/adamchainz/django-cors-headers/blob/fe1065db7aad1051742695abe8ea4777be9fb7e9/pyproject.toml#L65-L80&quot;&gt;example&lt;/a&gt; from django-cors-headers). When I don&apos;t know how to do something in Django 3rd party packages, I often check one of his packages and always learn something from it.&lt;/p&gt;
&lt;p&gt;Here are the key points:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Add &lt;code&gt;runner = uv-venv-lock-runner&lt;/code&gt; to the tox test envs&lt;/li&gt;
&lt;li&gt;Move the Django versions from &lt;code&gt;tox.ini&lt;/code&gt; to &lt;code&gt;pyproject.toml&lt;/code&gt; in &lt;code&gt;dependency-groups&lt;/code&gt; and replace them by dependency groups&lt;/li&gt;
&lt;li&gt;Add &lt;code&gt;tool.uv.conflicts&lt;/code&gt; to mark the dependency groups as incompatible for uv (&lt;a href=&quot;https://docs.astral.sh/uv/reference/settings/#conflicts&quot;&gt;documentation&lt;/a&gt;)&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;That&apos;s it! Once again, thanks Adam!&lt;/p&gt;
</content:encoded></item><item><title>Detecting a repeated sequence in a string</title><link>https://browniebroke.com/tils/2025-12-03-detecting-a-repeated-sequence-in-a-string/</link><guid isPermaLink="true">https://browniebroke.com/tils/2025-12-03-detecting-a-repeated-sequence-in-a-string/</guid><pubDate>Wed, 03 Dec 2025 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;A challenge that came up during &lt;a href=&quot;https://adventofcode.com/2025&quot;&gt;Advent of Code 2025&lt;/a&gt; day 2, where one had to detect whether a string was made up of a repeated sequence: &lt;code&gt;ABCABCABC&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;I initially wrote the algorithm by hand, but wasn&apos;t pleased with the performance nor by how elegant the solution was. After having solved it, I wanted to learn more and &lt;a href=&quot;https://www.geeksforgeeks.org/python/python-check-if-string-repeats-itself/&quot;&gt;found this page&lt;/a&gt; listing a few option. This solution caught my attention:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;bool(re.fullmatch(r&quot;(.+)\1+&quot;, value))
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;I&apos;m more used to using the &lt;code&gt;re.match&lt;/code&gt; function, but in this case we need the &lt;code&gt;re.fullmatch&lt;/code&gt;:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;If the &lt;em&gt;whole&lt;/em&gt; string matches the regular expression &lt;em&gt;pattern&lt;/em&gt;, return a corresponding Match. Return None if the string does not match the pattern; note that this is different from a zero-length match.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;I never really needed that, but it&apos;s good to have found a use case for it. Hopefully, I will remember this trick next time I need it!&lt;/p&gt;
</content:encoded></item><item><title>HTML meta tag: fediverse:creator</title><link>https://browniebroke.com/tils/2025-02-22-html-meta-tag-fediversecreator/</link><guid isPermaLink="true">https://browniebroke.com/tils/2025-02-22-html-meta-tag-fediversecreator/</guid><pubDate>Sat, 22 Feb 2025 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;I came across a really nice tool to check your site meta tags,
icon, colors and RSS feeds. It&apos;s &lt;a href=&quot;https://lens.rknight.me/&quot;&gt;lens.rknight.me&lt;/a&gt; and found most of the infos when ran it against this site. However, it taught me about the &lt;code&gt;fediverse:creator&lt;/code&gt; meta tag which I didn&apos;t know about.&lt;/p&gt;
&lt;p&gt;I sadly don&apos;t remember how I heard about it, but this post will be how I remember it, and who knows maybe how someone else discovers it.&lt;/p&gt;
</content:encoded></item><item><title>Docker ADD vs COPY instructions</title><link>https://browniebroke.com/tils/2024-12-02-docker-add-vs-copy-instructions/</link><guid isPermaLink="true">https://browniebroke.com/tils/2024-12-02-docker-add-vs-copy-instructions/</guid><pubDate>Mon, 02 Dec 2024 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;While reviewing a change in Cookiecutter Django, a co-maintainer (Fábio C. Barrionuevo da Luz &lt;a href=&quot;https://github.com/luzfcb&quot;&gt;@luzfcb&lt;/a&gt;) &lt;a href=&quot;https://github.com/cookiecutter/cookiecutter-django/pull/5434#discussion_r1865740149&quot;&gt;pointed at&lt;/a&gt; a section of the Docker documentation outlining the subtle, but important, &lt;a href=&quot;https://docs.docker.com/build/building/best-practices/#add-or-copy&quot;&gt;differences between &lt;code&gt;ADD&lt;/code&gt; and &lt;code&gt;COPY&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;If you asked me yesterday, I wouldn&apos;t have been able to tell you the difference, and this was indeed enlightening. My takeaways:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Start with &lt;code&gt;COPY&lt;/code&gt; to copy files into the container, being from the host filesystem or from another stage.&lt;/li&gt;
&lt;li&gt;If the file is only needed temporarily, use a &lt;code&gt;RUN&lt;/code&gt; instruction with bind mount instead.&lt;/li&gt;
&lt;li&gt;Use &lt;code&gt;ADD&lt;/code&gt; if you need to put files into your container &lt;strong&gt;from a remote source&lt;/strong&gt;.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;I don&apos;t expect to remember this in one month time, but hopefully this page will help me find the resource quickly next time I need it.&lt;/p&gt;
</content:encoded></item><item><title>Add button to share on Mastodon to my posts</title><link>https://browniebroke.com/tils/2024-11-29-add-button-to-share-on-mastodon-to-my-posts/</link><guid isPermaLink="true">https://browniebroke.com/tils/2024-11-29-add-button-to-share-on-mastodon-to-my-posts/</guid><pubDate>Fri, 29 Nov 2024 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Similar to my earlier TIL about &lt;a href=&quot;/tils/2024-11-18-bluesky-action-intent-links&quot;&gt;adding &quot;share on Bluesky&quot; to my blog&lt;/a&gt;, I just added support for sharing my posts on Mastodon from the bottom of my posts and TILs.&lt;/p&gt;
&lt;p&gt;As far as I know, there is no official way to it, because there is no central place to direct people to, unlike BlueSky or Twitter/X. However, today I stumbled upon a neat solution on the &lt;a href=&quot;https://djangotv.com&quot;&gt;DjangoTV website&lt;/a&gt; and I liked it as a user so decided to implement it here.&lt;/p&gt;
&lt;p&gt;It works using a tool by &lt;a href=&quot;https://fosstodon.org/@kytta&quot;&gt;@kytta&lt;/a&gt; deployed at &lt;a href=&quot;https://toot.kytta.dev&quot;&gt;toot.kytta.dev&lt;/a&gt;. I initially tried to use my BlueSky snippet, but that prefilled the input with &lt;code&gt;+&lt;/code&gt; instead of spaces, because I used &lt;code&gt;new URLSearchParams(...).toString()&lt;/code&gt;, which was unnecessary in this case. Then I passed the text with hashtags, and noticed that the hashtags were lost. It makes sense as the &lt;code&gt;#&lt;/code&gt; has a special meaning in the URL. I replaced these with the URL encoded version manually and it worked.&lt;/p&gt;
&lt;p&gt;Here is the final snippet:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;function getMastodonSharingUrl(title, tags, postUrl, handle) {
  const hashtagsStr = tags
    ? tags.map((ht) =&amp;gt; `%23${ht.replace(&quot; &quot;, &quot;&quot;)}`).join(&quot; &quot;)
    : &quot;&quot;;
  const textParam = `${title} by ${handle} ${postUrl} ${hashtagsStr}`;
  return `https://toot.kytta.dev/?text=${textParam}`;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Again, &lt;a href=&quot;https://github.com/browniebroke/browniebroke.com/pull/1758&quot;&gt;the whole implementation&lt;/a&gt;, if you&apos;re curious.&lt;/p&gt;
</content:encoded></item><item><title>Javascript console.log() object string substitution</title><link>https://browniebroke.com/tils/2024-11-27-javascript-console-log-object-string-substitution/</link><guid isPermaLink="true">https://browniebroke.com/tils/2024-11-27-javascript-console-log-object-string-substitution/</guid><pubDate>Wed, 27 Nov 2024 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;A common issue with Javascript&apos;s &lt;code&gt;console.log&lt;/code&gt; is that if you log a template string that contains a complex JS object, you&apos;ll get a useless output:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;gt;&amp;gt; const obj = { test: 1 }
&amp;gt;&amp;gt; console.log(`obj=${obj}`)
obj=[object Object]
&amp;gt;&amp;gt; console.log(&apos;obj=%s&apos;, obj)
obj=[object Object]
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;I was trying to debug something and copilot suggested to use &lt;code&gt;%j&lt;/code&gt; formatting which seemed promising:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;gt;&amp;gt; console.log(&apos;obj=%j&apos;, obj)
obj=%j
Object { test: 1 }
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Unfortunately that was an hallucination, the &lt;code&gt;%j&lt;/code&gt; isn&apos;t doing anything, and is still present in the printed string.&lt;/p&gt;
&lt;p&gt;That got me curious, so I looked it up online and found the &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/API/console#using_string_substitutions&quot;&gt;dedicated MDN page&lt;/a&gt; with the &lt;code&gt;%o&lt;/code&gt; substitution:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;gt;&amp;gt; console.log(&apos;obj=%o&apos;, obj)
obj=Object { test: 1 }
&lt;/code&gt;&lt;/pre&gt;
</content:encoded></item><item><title>James Bennet&apos;s DjangoVer suggestion</title><link>https://browniebroke.com/tils/2024-11-25-james-bennets-djangover-suggestion/</link><guid isPermaLink="true">https://browniebroke.com/tils/2024-11-25-james-bennets-djangover-suggestion/</guid><pubDate>Mon, 25 Nov 2024 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;I saw &lt;a href=&quot;https://www.b-list.org/weblog/2024/nov/18/djangover/&quot;&gt;this post&lt;/a&gt; from James Bennet shared around in a few places last week, and only came around to read it now. The TL;DR is that it&apos;s making the case for &quot;DjangoVer&quot;, a versioning scheme to align 3rd party Django packages versions with the latest version of Django version they support.&lt;/p&gt;
&lt;p&gt;I think it&apos;s a great idea, it would make it much easier to see at a glance in your requirements.txt/pyproject.toml if you&apos;re ready to upgrade, or at least give you an idea on likely you&apos;re ready to upgrade:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;If most packages are a couple of major versions behind, you can assume you&apos;re not ready.&lt;/li&gt;
&lt;li&gt;If most package are aligned except a couple with one minor behind, it&apos;s probably worth a try.&lt;/li&gt;
&lt;li&gt;If a single package is dragging behind, time to find an alternative.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I&apos;ll think about it for a while, but I&apos;ll probably move my projects to this scheme at some point.&lt;/p&gt;
</content:encoded></item><item><title>New  argument to Django redirects</title><link>https://browniebroke.com/tils/2024-11-24-new-argument-to-django-redirects/</link><guid isPermaLink="true">https://browniebroke.com/tils/2024-11-24-new-argument-to-django-redirects/</guid><pubDate>Sun, 24 Nov 2024 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;A &lt;a href=&quot;https://github.com/django/django/pull/18616&quot;&gt;pull request&lt;/a&gt; has been merged into Django this week, to make it easier to do a redirect while preserving the HTTP method.&lt;/p&gt;
&lt;p&gt;It took me an embarrassingly long time in my career to learn that the most common redirects (301 and 302) don&apos;t preserve the HTTP method. I also often confuse the known temporary permanent, and it&apos;s even harder to remember which one is which between 307 and 308.&lt;/p&gt;
&lt;p&gt;With this new change, it will make these status code easier to learn, remember and use them. This higher level API is a much welcome addition in my opinion, replacing this:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;def my_old_vew(request):
    return HttpResponseRedirect(status_code=307)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;By:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;def my_old_vew(request):
    return HttpResponseRedirect(preserve_method=True)
&lt;/code&gt;&lt;/pre&gt;
</content:encoded></item><item><title>Improve Django development experience with django-fastdev</title><link>https://browniebroke.com/tils/2024-11-22-improve-django-development-experience-with-django-fastdev/</link><guid isPermaLink="true">https://browniebroke.com/tils/2024-11-22-improve-django-development-experience-with-django-fastdev/</guid><pubDate>Fri, 22 Nov 2024 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;I came across a &lt;a href=&quot;https://bsky.app/profile/trey.io/post/3lbfs6tusdk2n&quot;&gt;post from Trey Hunner&lt;/a&gt; which resonated with me, about how Django sometimes swallows errors in its templates. It can be convenient on prod, when the code is proven to be correct, but when I develop I&apos;d rather have louder errors to guard against typos. &lt;a href=&quot;https://bsky.app/profile/adamghill.com/post/3lbfuylqdik2z&quot;&gt;adamghill replied&lt;/a&gt; with the 3rd party package &lt;a href=&quot;https://github.com/boxed/django-fastdev&quot;&gt;django-fastdev&lt;/a&gt;. I&apos;ve never heard of before but it seems very useful.&lt;/p&gt;
&lt;p&gt;I&apos;ve inspected the code to understand a bit how it works, and it&apos;s pretty much all implemented in the &lt;a href=&quot;https://github.com/boxed/django-fastdev/blob/master/django_fastdev/apps.py&quot;&gt;&lt;code&gt;apps.py&lt;/code&gt;&lt;/a&gt; file. It&apos;s working by monkey patching various parts of Django. One may find this approach questionable but as long as it works, makes me more productive and that I don&apos;t have to maintain it, I&apos;d be more than happy to use it. It&apos;s also very &lt;a href=&quot;https://programmingisterrible.com/post/139222674273/write-code-that-is-easy-to-delete-not-easy-to&quot;&gt;easy to remove&lt;/a&gt; if it ends up not working any more.&lt;/p&gt;
&lt;p&gt;This is a creative and pragmatic way to solve the problem, bypassing entirely the much more difficult way to get thing into core. Underlines &lt;a href=&quot;https://buttondown.com/carlton/archive/thoughts-on-djangos-core/&quot;&gt;the points recently made by Carlton Gibson&lt;/a&gt; about the power of the 3rd party Django apps.&lt;/p&gt;
</content:encoded></item><item><title>Adding &quot;share on Bluesky&quot; to my blog</title><link>https://browniebroke.com/tils/2024-11-18-bluesky-action-intent-links/</link><guid isPermaLink="true">https://browniebroke.com/tils/2024-11-18-bluesky-action-intent-links/</guid><pubDate>Mon, 18 Nov 2024 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;I just added support for sharing my posts on Bluesky from the bottom of my posts and TIL.&lt;/p&gt;
&lt;p&gt;The &lt;a href=&quot;https://docs.bsky.app/docs/advanced-guides/intent-links&quot;&gt;way it works&lt;/a&gt; is quite similar &lt;a href=&quot;https://developer.x.com/en/docs/x-for-websites/tweet-button/overview&quot;&gt;to Tweeter/X&lt;/a&gt; and since I already had this feature, it was pretty quick to adapt.&lt;/p&gt;
&lt;p&gt;The main difference is that Tweeter has several parameters to provide your text, URL, hashtags and mentions separately, while the Bluesky method has a single field. Here is how it looks like:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;function getBskySharingUrl(title, tags, postUrl, bskyUsername) {
  const hashtagsStr = tags
    ? tags.map((ht) =&amp;gt; `#${ht.replace(&quot; &quot;, &quot;&quot;)}`).join(&quot; &quot;)
    : &quot;&quot;;
  const bskyParams = new URLSearchParams({
    text: `${title} by @${bskyUsername} ${postUrl} ${hashtagsStr}`,
  }).toString();
  return `https://bsky.app/intent/compose?${bskyParams}`;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You can see the &lt;a href=&quot;https://github.com/browniebroke/browniebroke.com/pull/1744&quot;&gt;whole implementation here&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;I pass my own username as &lt;code&gt;bskyUsername&lt;/code&gt; and that doesn&apos;t seem to pull through, it&apos;s showing as text instead of being recognised as my handle. Let me know if you know how to fix that.&lt;/p&gt;
</content:encoded></item><item><title>Trigger further workflows on pushes from GitHub actions</title><link>https://browniebroke.com/tils/2024-10-02-trigger-further-workflows-on-pushes-from-github-actions/</link><guid isPermaLink="true">https://browniebroke.com/tils/2024-10-02-trigger-further-workflows-on-pushes-from-github-actions/</guid><pubDate>Wed, 02 Oct 2024 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;GitHub actions aren&apos;t trigger by actions from other workflows by default. If you have a workflow that pushes back changes after linting/formatting, you can get it trigger another round of CI by checking out with a GitHub token:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;- uses: actions/checkout@v4
  with:
    token: ${{ secrets.GH_PAT }}
- name: Make some changes
  run: ...
- name: Push changes back
  uses: stefanzweifel/git-auto-commit-action@v5
&lt;/code&gt;&lt;/pre&gt;
</content:encoded></item><item><title>Opening the PyCharm registry</title><link>https://browniebroke.com/tils/2024-07-17-opening-the-pycharm-registry/</link><guid isPermaLink="true">https://browniebroke.com/tils/2024-07-17-opening-the-pycharm-registry/</guid><pubDate>Wed, 17 Jul 2024 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Today, I encountered an issue with my IDE (PyCharm) and its integration with Docker. It wouldn&apos;t refresh my remote interpreter, which broke most of the intellisense and go to definition. I found &lt;a href=&quot;https://youtrack.jetbrains.com/issue/PY-73909&quot;&gt;this Youtrack issue&lt;/a&gt; which described my problem close enough.&lt;/p&gt;
&lt;p&gt;Someone replied and suggested to change a &lt;code&gt;python.use.targets.api&lt;/code&gt; setting, which is a bit hidden, as it&apos;s a registry entry. I searched how to open the registry, but couldn&apos;t find much resources.&lt;/p&gt;
&lt;p&gt;The answer is:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&amp;lt;kbd&amp;gt;⌘&amp;lt;/kbd&amp;gt; + &amp;lt;kbd&amp;gt;⌥&amp;lt;/kbd&amp;gt; + &amp;lt;kbd&amp;gt;⇧&amp;lt;/kbd&amp;gt; + &amp;lt;kbd&amp;gt;/&amp;lt;/kbd&amp;gt; to open the
maintenance context menu&lt;/li&gt;
&lt;li&gt;Choose the first option, &quot;Registry...&quot; from it&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Done! Hopefully that&apos;ll help me and others in the future.&lt;/p&gt;
</content:encoded></item><item><title>Python print function keyword arguments</title><link>https://browniebroke.com/tils/2024-05-08-python-print-function-keyword-arguments/</link><guid isPermaLink="true">https://browniebroke.com/tils/2024-05-08-python-print-function-keyword-arguments/</guid><pubDate>Wed, 08 May 2024 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;The &lt;a href=&quot;https://docs.python.org/3/library/functions.html#print&quot;&gt;Python &lt;code&gt;print&lt;/code&gt; function&lt;/a&gt; is one of the first one folks learn about, since it&apos;s part of the &quot;Hello World&quot; program. It&apos;s often presented with its most basic usage, by passing a single argument to it:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;print(&quot;Hello World&quot;)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Simple, and readable.&lt;/p&gt;
&lt;p&gt;However, I recently rediscovered its keyword arguments, especially the &lt;code&gt;end&lt;/code&gt; one, that defaults to &lt;code&gt;\n&lt;/code&gt; (newline character), which is added at the end of whatever you want to print. That&apos;s why when you call the above snippet multiple times, you&apos;ll see each string printed on a new line, as opposed to have them on the same.&lt;/p&gt;
&lt;h2&gt;The problem&lt;/h2&gt;
&lt;p&gt;When running a long command, if you want to print the progress of the command, you might be tempted to do:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;def do_thing_over_many_entries(entries):
    total = len(entries)
    print(f&quot;Starting processing {total} entries&quot;)
    for index, entry in enumerate(entries, 1):
        print(f&quot;Processing entry {index}/{total}&quot;)
        # Do the thing
    print(&quot;All done&quot;)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This will print a wall of text, e.g.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Starting processing 4000 entries
Processing entry 1/4000
Processing entry 2/4000
Processing entry 3/4000
...
Processing entry 4000/4000
All done
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Which might be a bit disruptive as it&apos;ll fill up your terminal.&lt;/p&gt;
&lt;h2&gt;Solution wih standard library&lt;/h2&gt;
&lt;p&gt;Using the &lt;code&gt;end&lt;/code&gt; keyword argument is useful, you can pass carriage return &lt;code&gt;\r&lt;/code&gt; to instead update the same line in place:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;def do_thing_over_many_entries(entries):
    total = len(entries)
    print(f&quot;Starting processing {total} entries&quot;)
    for index, entry in enumerate(entries, 1):
        print(f&quot;Processing entry {index}/{total}&quot;, end=&quot;\r&quot;)
        # Do the thing
    print(&quot;All done&quot;)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Which will update the &quot;Processing entry...&quot; line as it goes:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Starting processing 4000 entries
Processing entry 4000/4000
All done
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Solutions with 3rd party packages&lt;/h2&gt;
&lt;p&gt;This is a simple example with vanilla Python, but other libraries are providing higher abstractions for this kind of operation:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://rich.readthedocs.io/en/stable/progress.html&quot;&gt;Rich&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/tqdm/tqdm&quot;&gt;TQDM&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;If you can afford to add a dependency to your project, check these out.&lt;/p&gt;
</content:encoded></item><item><title>Crossed app logo in macOS notifications</title><link>https://browniebroke.com/tils/2024-03-13-crossed-app-logo-in-macos-notifications/</link><guid isPermaLink="true">https://browniebroke.com/tils/2024-03-13-crossed-app-logo-in-macos-notifications/</guid><pubDate>Wed, 13 Mar 2024 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Occasionally, on macOS, I get some notifications with the app logo is crossed out, like this:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./_2024-03-13-notification.png&quot; alt=&quot;Crossed out Chrome notification&quot; /&gt;&lt;/p&gt;
&lt;p&gt;&amp;lt;br /&amp;gt;&lt;/p&gt;
&lt;p&gt;I thought it was because of permissions issues, but couldn&apos;t find the problem in my settings. Turns out, the fix is to kill the Notification Center, and let it starts again automatically. This can be done via the Activity Monitor, or via the command line:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;killall NotificationCenter
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;I found the solution &lt;a href=&quot;https://www.reddit.com/r/mac/comments/qghppk/chrome_logo_is_crossed_out_for_some_reason_in/&quot;&gt;on Reddit&lt;/a&gt;, but since I&apos;ll inevitably have forgotten next time it happens, making note of it here.&lt;/p&gt;
</content:encoded></item><item><title>Running Python from subprocess cross platforms</title><link>https://browniebroke.com/tils/2023-11-04-running-python-from-subprocess-cross-platforms/</link><guid isPermaLink="true">https://browniebroke.com/tils/2023-11-04-running-python-from-subprocess-cross-platforms/</guid><pubDate>Sat, 04 Nov 2023 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;In one of my Python package offering a CLI, I added the ability to run it as &lt;code&gt;python -m mypackage&lt;/code&gt;. Natuarally, I wanted to write a small test to make sure it worked, which used the &lt;code&gt;subprocess&lt;/code&gt; module to run python with the &lt;code&gt;-m&lt;/code&gt; flag. Simple enough, right?&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;result = subprocess.run([&quot;python&quot;, &quot;-m&quot;, ...], ...)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Well, when I pushed to CI, I realised that the &lt;code&gt;python&lt;/code&gt; executable couldn&apos;t be found on Windows... It&apos;s probably called &lt;code&gt;python.exe&lt;/code&gt; or somthing like that. Do I need to introduce a condition in my test depending on the platform? Surely, there must be a better way.&lt;/p&gt;
&lt;p&gt;After a quick search, &lt;a href=&quot;https://stackoverflow.com/a/912847/2261637&quot;&gt;I found about &lt;code&gt;sys.executable&lt;/code&gt;&lt;/a&gt;, which does exactly that. Neat!&lt;/p&gt;
&lt;p&gt;Here is how my test looks like now:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import subprocess
import sys


def test_can_run_as_python_module():
    result = subprocess.run(
        [
            sys.executable,
            &quot;-m&quot;,
            &quot;mypackage&quot;,
            &quot;--help&quot;
        ],
        check=True,
        capture_output=True,
    )
    assert result.returncode == 0
    assert b&quot;my-package [OPTIONS]&quot; in result.stdout
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Hope that helps!&lt;/p&gt;
</content:encoded></item><item><title>Integrating django-extensions shell plus with Pycharm console</title><link>https://browniebroke.com/tils/2023-09-27-integrating-django-extensions-shell-plus-with-pycharm-console/</link><guid isPermaLink="true">https://browniebroke.com/tils/2023-09-27-integrating-django-extensions-shell-plus-with-pycharm-console/</guid><pubDate>Wed, 27 Sep 2023 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;The &lt;code&gt;django-extensions&lt;/code&gt; is a package which I install in all my Django projects, it&apos;s a toolbox full of useful commands and utilities. One of the commands is &lt;code&gt;shell_plus&lt;/code&gt; which is a replacement for the standard Django &lt;code&gt;shell&lt;/code&gt; command, than provides a better DX, close the iPython, as well as automatically importing all the models as well as the most common utilities. As a Pycharm user, I often use the integrated console, which responds to the IDE shortcuts and provides other small DX improvements, similar to what &lt;code&gt; shell_plus&lt;/code&gt; offers.&lt;/p&gt;
&lt;p&gt;By default, Pycharm integrated console does not do the automatic imports from &lt;code&gt;shell_plus&lt;/code&gt;, but it&apos;s quite easy to add them, hence getting the best of both worlds. Here are the steps:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Open the settings&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Go to the &lt;code&gt;Build, Execution, Deployment&lt;/code&gt; &amp;gt; &lt;code&gt;Console&lt;/code&gt; &amp;gt; &lt;code&gt;Django Console&lt;/code&gt; page&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Add the following to the end of the &lt;code&gt;Starting script&lt;/code&gt; field:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import sys; print(&apos;Python %s on %s&apos; % (sys.version, sys.platform))
import django; print(&apos;Django %s&apos; % django.get_version())
sys.path.extend([WORKING_DIR_AND_PYTHON_PATHS])
if &apos;setup&apos; in dir(django): django.setup()
import django_manage_shell; django_manage_shell.run(PROJECT_ROOT)

from django_extensions.management import shells
from django.core.management.color import color_style
imported_items = shells.import_objects({}, color_style())
for k, v in imported_items.items(): globals()[k] = v
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;And that&apos;s it! Now when you open the Pycharm console, all your models and utilities will be available, using the &lt;code&gt;shell_plus&lt;/code&gt; logic.&lt;/p&gt;
</content:encoded></item><item><title>Debugging redirect cycle error in Django tests</title><link>https://browniebroke.com/tils/2023-08-30-debugging-redirect-cycle-error-in-django-tests/</link><guid isPermaLink="true">https://browniebroke.com/tils/2023-08-30-debugging-redirect-cycle-error-in-django-tests/</guid><pubDate>Wed, 30 Aug 2023 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;I recently joined a new team with a medium-sized Django project, so naturally I tried to run django-upgrade on it. It modified a lot of URL routes and upgraded the syntax from &lt;code&gt;re_path&lt;/code&gt; to &lt;code&gt;path&lt;/code&gt;. However, when I ran the tests, I got some failures with the following message:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;django.test.client.RedirectCycleError: Redirect loop detected.
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;I could see the URL being used in the test, but nothing obvious came to mind. The patterns that were changed looked fine, and the problem persisted if I reverted to the pattern in for that URL. I tried to set a breakpoint in the affected view, but it wasn&apos;t reached, where I was expecting it to. It quickly became clear that my test suddenly resolved to a different view, but which one? There are hundreds of routes in the project! I figured that the answer might be in the exception that Django raises in the test.&lt;/p&gt;
&lt;p&gt;I wrapped my call to &lt;code&gt;self.client.get(...)&lt;/code&gt; in my test into a try/except block and inspected the &lt;code&gt;RedirectCycleError&lt;/code&gt; being raised:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;class MyViewTest(TestCase):
    def test_page_not_accessible(self):
        url = reverse(&quot;my-view&quot;)
        try:
            response = self.client.get(url, follow=True)
        except RedirectCycleError as exc:
            breakpoint()

    assert response.status_code == 404
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;I ran the test again and inspected the exception from the debugger. the exception has a &lt;code&gt;last_response&lt;/code&gt; attribute, which is a &lt;code&gt;HttpResponseRedirectBase&lt;/code&gt;, which itself give the URL name that was used by accessing the &lt;code&gt;resolver_match.url_name&lt;/code&gt; attribute. Putting it all together:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;class MyViewTest(TestCase):
    def test_page_not_accessible(self):
        url = reverse(&quot;my-view&quot;)
        try:
            response = self.client.get(url, follow=True)
        except RedirectCycleError as exc:
            print(exc.last_response.resolver_match.url_name)

    assert response.status_code == 404
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;That gave me a URL name which was defined as follows:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;from django.urls import re_path

from .views import product_redirect_view

app_name = &quot;site&quot;

urlpatterns = [
    re_path(r&quot;^&quot;, product_redirect_view, name=&quot;product-redirect&quot;),
]
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;It&apos;s higher in the list of routes, so higher priority, and is missing a trailing &lt;code&gt;$&lt;/code&gt; hence matching anything. I&apos;m not sure how it worked before, to be honest, but that&apos;s another story I need to figure out next. For now, I&apos;m happy that I found the cause of the problem with debugging.&lt;/p&gt;
</content:encoded></item><item><title>Using Python&apos;s inspect module to load test data</title><link>https://browniebroke.com/tils/2023-08-07-using-the-python-inspect-module-to-load-test-data/</link><guid isPermaLink="true">https://browniebroke.com/tils/2023-08-07-using-the-python-inspect-module-to-load-test-data/</guid><pubDate>Mon, 07 Aug 2023 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;The problem&lt;/h2&gt;
&lt;p&gt;In one of my Django project, a pattern started to emerge in my tests, where I needed mock data coming out a 3rd party API. The 3rd party API docs often shows an example, which is great to get a basic payload to use in tests. However, I couldn&apos;t paste it directly on my test code:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;It is in JSON format, which isn&apos;t necessarily valid Python. For example if the example contains some &lt;code&gt;null&lt;/code&gt; or boolean values (&lt;code&gt;false&lt;/code&gt; in JSON vs &lt;code&gt;False&lt;/code&gt; in Python).&lt;/li&gt;
&lt;li&gt;It might be a few 100 lines long, distracting from the test code&lt;/li&gt;
&lt;li&gt;May contain a lot of data irrelevant to the test.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Still, I like to have a snapshot of the example, in my code: some fields might be useful in the future, and having the full payload in git enables me see if some fields are being added or removed by the 3rd party.&lt;/p&gt;
&lt;h2&gt;Initial solution&lt;/h2&gt;
&lt;p&gt;My solution is to paste the example in a JSON file, and write a small utility function to load the json from the file. To separate it from my test, while leaving it close, I started to put it in a &lt;code&gt;data&lt;/code&gt; directory directly adjacent to my test file:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;tests/
├── data/
│   └── example.json
└── test_example.py
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then, I wrote a small utility function to load the data, in the test module:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import json
from pathlib import Path


def load_data(filename):
    fixture_path = Path(__file__).parent / &quot;data&quot; / filename
    return json.loads(fixture_path.read_text())
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;I used it once, and moved on. Then I needed something similar, but also wanted the ability to override some fields, so I wrote another one, adding a &lt;code&gt;overrides&lt;/code&gt; parameter:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;def load_data(filename, **overrides):
    fixture_path = Path(__file__).parent / &quot;data&quot; / filename
    base_data = json.loads(fixture_path.read_text())
    return {**base_data, **overrides}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;After a while, as I added more integrations, a few versions popped up in my codebase. They were all similar, but because of the &lt;code&gt;__file__&lt;/code&gt; magic, I couldn&apos;t easily reuse my function: the fixture would be loaded from directory adjacent to my utility function, not from the test module. However, I knew that Python introspection would probably let me do what I wanted.&lt;/p&gt;
&lt;h2&gt;The reusable solution&lt;/h2&gt;
&lt;p&gt;A quick search brought up &lt;a href=&quot;https://stackoverflow.com/a/60297932/2261637&quot;&gt;a Stack Overflow answer&lt;/a&gt; describing exactly what I needed, using the &lt;a href=&quot;https://docs.python.org/3/library/inspect.html#inspect.stack&quot;&gt;&lt;code&gt;inspect&lt;/code&gt; module&lt;/a&gt;. Here is how my final solution looks like:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import inspect
import json
from pathlib import Path
from typing import Any


def load_json(
    fixture_name: str,
    **overrides: Any,
) -&amp;gt; dict[str, Any]:
    &quot;&quot;&quot;
    Load JSON from a fixture located a `data` directory.

    The directory is adjacent to the caller.
    &quot;&quot;&quot;
    caller_file = inspect.stack()[1].filename
    data_path = Path(caller_file).parent / &quot;data&quot;
    fixture_path = data_path / fixture_name
    loaded_data = json.loads(fixture_path.read_text())
    return {
        **loaded_data,
        **overrides,
    }
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This solution has some limitations (the caller must be the Python module adjacent to the test data, doesn&apos;t work with a top level array, ...), but it works fine for my use case. I can now reuse it across my codebase and improve it as my need evolve.&lt;/p&gt;
</content:encoded></item><item><title>Terraform and ECS task revisions</title><link>https://browniebroke.com/tils/2023-05-05-terraform-and-ecs-task-revisions/</link><guid isPermaLink="true">https://browniebroke.com/tils/2023-05-05-terraform-and-ecs-task-revisions/</guid><pubDate>Fri, 05 May 2023 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Today, I came across a problem with &lt;a href=&quot;https://www.terraform.io&quot;&gt;Terraform&lt;/a&gt; and ECS task revisions. The setup is that the ECS task definition and service are both defined in a Terraform, with a fixed revision tag of &lt;code&gt;latest&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;resource &quot;aws_ecs_task_definition&quot; &quot;app_task&quot; {
  family = &quot;app-task&quot;

  container_definitions = jsonencode([
    {
      name  = &quot;myapp&quot;
      image = &quot;XXXX.dkr.ecr.us-east-1.amazonaws.com/myapp:latest&quot;
      ...
    }
  ])
  ...
}

resource &quot;aws_ecs_service&quot; &quot;app_service&quot; {
  name            = &quot;app-service&quot;
  task_definition = aws_ecs_task_definition.app_task.arn
  ...
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The application code lives elsewhere, and on the app&apos;s CI, we build the Docker image, tag it as &lt;code&gt;latest&lt;/code&gt;, push to ECR and update the task definition using the AWS CLI by forcing a redeployment. The same image tag is running, but ECS adds a number at the end of the revision, so the task definition revision is something like &lt;code&gt;app-task:1&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;The problem is that Terraform doesn&apos;t know about these deployments. After a few releases, the task will look like &lt;code&gt;app-task:12&lt;/code&gt;, and when running &lt;code&gt;terraform plan&lt;/code&gt; without any changes in the infra code, I can see that Terraform is trying to roll back to the earlier version:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;~ task_definition  = &quot;arn:aws:ecs:us-east-1:XXXX:task-definition/app-task:12&quot; -&amp;gt; &quot;arn:aws:ecs:us-east-1:XXXX:task-definition/app-task:1&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;I found a solution in &lt;a href=&quot;https://www.reddit.com/r/aws/comments/nlco6r/terraform_and_ecs_dont_change_task_revision/&quot;&gt;this reddit post&lt;/a&gt;, where they describe the same issue, and the answer suggest a few solutions. I went with the first one, which works for now:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;resource &quot;aws_ecs_service&quot; &quot;app_service&quot; {
  name            = &quot;app-service&quot;
  task_definition = aws_ecs_task_definition.app_task.arn
  force_new_deployment = true

  lifecycle {
    ignore_changes = [task_definition]
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;a href=&quot;https://www.reddit.com/r/aws/comments/nlco6r/comment/gzjtzm4/&quot;&gt;Someone commented&lt;/a&gt; a drawback of using that solution, since it prevents actual Terraform changes to the task definition, but we might be able to live with that limitation by commenting out that bit when we need to do it.&lt;/p&gt;
</content:encoded></item><item><title>Bulk resize images with sips</title><link>https://browniebroke.com/tils/2023-05-04-bulk-resize-images-with-sips/</link><guid isPermaLink="true">https://browniebroke.com/tils/2023-05-04-bulk-resize-images-with-sips/</guid><pubDate>Thu, 04 May 2023 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Today, I needed to bulk resize a bunch of images that I had in a folder. I decided to ask ChatGPT to solve my problem for me and it did it beautifully. Here is the prompt I used:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;What&apos;s he quickest way to resize some JPEG images in a given folder on macOS? The original dimensions are 3984×3984 and want to get them around 1000×1000, with an average quality, while staying in JPEG format.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;It suggested to use &lt;code&gt;sips&lt;/code&gt; (Scriptable Image Processing System) and not only gave me the solution, but also the explanation of the command, and explained how to open the terminal app on macOS, which is great for people who are not familiar. Here is the command it suggested:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;for img in *.jpg
  do sips -Z 1000 --setProperty format jpeg --setProperty formatOptions 65 &quot;$img&quot; --out &quot;Resized/$img&quot;
done
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And here are the explanations:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;This command does the following:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Loops through all JPEG files in the folder with the &lt;code&gt;*.jpg&lt;/code&gt; file extension.&lt;/li&gt;
&lt;li&gt;Resizes the images to fit within a 1000x1000 pixel bounding box, maintaining their aspect ratio (&lt;code&gt;-Z 1000&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;Sets the output format to JPEG and the quality to approximately 65% (average quality) using the &lt;code&gt;--setProperty&lt;/code&gt; flags.&lt;/li&gt;
&lt;li&gt;Saves the resized images in a new folder named &quot;Resized&quot; within the current folder (&lt;code&gt;--out &quot;Resized/$img&quot;&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;After the command has completed, you should see a new folder named &quot;Resized&quot; in your original folder, containing all the resized JPEG images.&lt;/li&gt;
&lt;/ol&gt;
&lt;/blockquote&gt;
&lt;p&gt;I initially got an error, because the &lt;code&gt;Resized&lt;/code&gt; folder didn&apos;t exist, so I just created it. After that, it worked like a charm.&lt;/p&gt;
</content:encoded></item><item><title>Translated database content with Django</title><link>https://browniebroke.com/tils/2023-04-20-translated-database-content-with-django/</link><guid isPermaLink="true">https://browniebroke.com/tils/2023-04-20-translated-database-content-with-django/</guid><pubDate>Thu, 20 Apr 2023 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Recently, while working on a Django project, I needed to have content in multiple languages. Django has a good support for &lt;a href=&quot;https://docs.djangoproject.com/en/stable/topics/i18n/translation/&quot;&gt;translations&lt;/a&gt;, but that&apos;s only strings which are in the codebase. It&apos;s not uncommon to need models with content in the database that will be displayed to end user, getting into CMS territory, and these need to be translated. However, for my use case, I was just modeling a couple of fields, and it&apos;s an API backend, with an SPA frontend, so I didn&apos;t want to install something too complicated like Wagtail. I just needed a lightweight solution, that can work from the admin.&lt;/p&gt;
&lt;p&gt;At a previous company, we wrote a custom implementation which relied on JSON field on the same model, and it worked well, but it wasn&apos;t fully integrated into the admin and we never open sourced it. So I was left with trying to find a similar implementation.&lt;/p&gt;
&lt;h2&gt;Rejected options&lt;/h2&gt;
&lt;p&gt;I looked at a few options, and rejected them for the following reasons:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://django-parler.readthedocs.io&quot;&gt;django-parler&lt;/a&gt;: I&apos;ve used it in the past, and it posed some issues. The main annoyance was that the translations were stored in a separate model, causing joins and extra queries. It also doesn&apos;t appear well maintained: at time of writing, the last release is 1,5 years old, and it&apos;s 2 Django versions behind in terms of support.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://django-modeltranslation.readthedocs.io&quot;&gt;django-modeltranslation&lt;/a&gt;: a popular option, but creates one column per translated field per language, which is not optimal.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;The solution&lt;/h2&gt;
&lt;p&gt;I ended up on &lt;a href=&quot;https://django-modeltrans.readthedocs.io&quot;&gt;django-modeltrans&lt;/a&gt;, which is using a single JSONField for all translations, is well integrated into the admin and supports modern versions of Django. Adding a model with translated fields is as simple as:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;from django.db import models
from modeltrans.fields import TranslationField

class MyModel(models.Model):
    name = models.CharField(max_length=255)
    description = models.TextField()

    i18n = TranslationField(fields=(&quot;name&quot;, &quot;description&quot;))

    class Meta:
        verbose_name = &quot;My model&quot;
        verbose_name_plural = &quot;My models&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This is pretty straightforward and very obvious that this model has translations and which are translated.&lt;/p&gt;
&lt;p&gt;The admin integration can be customised with the built-in &lt;code&gt;ActiveLanguageMixin&lt;/code&gt;, which limits shows the default language + the active language. In my case I had only 2 languages, so I copied that mixin in my codebase and tweaked it for my need. It&apos;s short (~10 lines, with comments), so that was relatively easy to do.&lt;/p&gt;
&lt;p&gt;For simple cases, I highly recommend this package, it&apos;s simple and efficient. If your app is content heavy though, I&apos;d recommend looking into a more fully fledged solution like Wagtail.&lt;/p&gt;
</content:encoded></item><item><title>Python descriptors</title><link>https://browniebroke.com/tils/2023-02-07-python-descriptors/</link><guid isPermaLink="true">https://browniebroke.com/tils/2023-02-07-python-descriptors/</guid><pubDate>Tue, 07 Feb 2023 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;I only learned today about a neat Python feature: descriptors. I came across that feature twice in the same day: once at work on a custom Django field for translations and a second time in &lt;a href=&quot;https://lukeplant.me.uk/blog/posts/pythons-disappointing-superpowers/&quot;&gt;this great article&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;From the article, I dived a bit deeper and landed on the amazing &lt;a href=&quot;https://docs.python.org/3/howto/descriptor.html&quot;&gt;&quot;Descriptor How To Guide&quot;&lt;/a&gt; from the Python documentation. The example of logging is a simple, yet practical one, that helped me &quot;get&quot; it:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;class LoggedAgeAccess:
    &quot;&quot;&quot;Descriptor class.&quot;&quot;&quot;

    def __get__(self, obj, objtype=None):
        value = obj._age
        print(f&apos;Accessing age={value}&apos;)
        return value

    def __set__(self, obj, value):
        print(f&apos;Updating age to {value=}&apos;)
        obj._age = value


class Person:
    &quot;&quot;&quot;Class using the descriptor.&quot;&quot;&quot;

    # Descriptor instance
    age = LoggedAgeAccess()

    def __init__(self, name, age):
        self.name = name
        # Calls __set__()
        self.age = age

    def birthday(self):
        # Calls __get__() then __set__()
        self.age += 1
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The guide expands on more advanced usages. I love how it starts simple, gets more complicated as you go, before expanding on the more formal definitions. A great resource from Raymond Hettinger.&lt;/p&gt;
</content:encoded></item><item><title>Detecting unchanged pipx packages with Ansible</title><link>https://browniebroke.com/tils/2022-11-18-detecting-unchanged-pipx-packages-with-ansible/</link><guid isPermaLink="true">https://browniebroke.com/tils/2022-11-18-detecting-unchanged-pipx-packages-with-ansible/</guid><pubDate>Fri, 18 Nov 2022 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;I &lt;a href=&quot;/tils/provisioning-my-mac-with-ansible/&quot;&gt;recently moved&lt;/a&gt; provisioning of my MacBook to Ansible and one of the task that didn&apos;t work perfectly was installation of CLI packages using &lt;a href=&quot;https://pypa.github.io/pipx/&quot;&gt;pipx&lt;/a&gt;: the playbook always detected the packages as changed, even if they were already installed.&lt;/p&gt;
&lt;p&gt;I &lt;a href=&quot;https://github.com/browniebroke/mac-ansible/commit/3c24278079defbc11d63241edabd5b43378f891d&quot;&gt;solved this&lt;/a&gt; by making use of the &lt;code&gt;changed_when&lt;/code&gt; option, and checking whether the output of &lt;code&gt;pipx install&lt;/code&gt; was saying that the package is already installed:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;- name: Install pipx packages
  command: pipx install {{ item }} --python ~/.pyenv/versions/{{ pyenv_python_versions[0] }}/bin/python
  register: pipx_install_result
  changed_when: &amp;gt;
    pipx_install_result.rc == 0 and
    &quot;&apos;&quot; + item + &quot;&apos; already seems to be installed.&quot; not in pipx_install_result.stdout
  loop: &quot;{{ pipx_packages }}&quot;
  tags:
    - pipx
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now my packages appear as &quot;ok&quot; instead of &quot;changed&quot; when running the playbook.&lt;/p&gt;
&lt;p&gt;Victory 🎉&lt;/p&gt;
</content:encoded></item><item><title>Provisioning my MacBook with Ansible</title><link>https://browniebroke.com/tils/2022-11-17-provisioning-my-mac-with-ansible/</link><guid isPermaLink="true">https://browniebroke.com/tils/2022-11-17-provisioning-my-mac-with-ansible/</guid><pubDate>Thu, 17 Nov 2022 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;&lt;a href=&quot;https://browniebroke.com/blog/new-year-new-laptop/&quot;&gt;I wrote before&lt;/a&gt; how I scripted the setup of my Apple MacBook pro. At the time, I used a combination of bash scripts, &lt;code&gt;Brewfile&lt;/code&gt; and &lt;a href=&quot;https://github.com/lra/mackup&quot;&gt;Mackup&lt;/a&gt;. This was better than nothing, but it was not really runnable over and over.&lt;/p&gt;
&lt;p&gt;In the meantime, &lt;a href=&quot;https://adamj.eu/tech/2019/03/20/how-i-provision-my-macbook-with-ansible/&quot;&gt;Adam Johnson blogged&lt;/a&gt; about his setup, using Ansible, and I was curious about it. I never used Ansible by was interested to play with it, and it seemed like a good use case. I tried to adapt it for my need, but I quickly realised that I wasn&apos;t ready to actually run it on my machine (and therefore test it): I didn&apos;t want to mess anything.&lt;/p&gt;
&lt;p&gt;Recently, my Mac had a hardware issue, and it came back from repairs wiped, so I decided to give it another go. I forked &lt;a href=&quot;https://github.com/adamchainz/mac-ansible&quot;&gt;Adam&apos;s repo&lt;/a&gt; and started by removing everything I didn&apos;t understand/needed. I ended up keeping mostly the brew packages, casks and the pyenv versions. And with a fresh machine, I was able to test it out.&lt;/p&gt;
&lt;p&gt;Once my machine was setup, I was able to add more tasks to install some packages with pipx, and by running the playbook again, Ansible added what was missing. I&apos;m still using Mackup to back up &amp;amp; restore my preferences, but I&apos;m considering moving that to Ansible as well.&lt;/p&gt;
</content:encoded></item><item><title>Sudo with TouchID on macOS</title><link>https://browniebroke.com/tils/2022-08-31-sudo-with-touchid-on-macos/</link><guid isPermaLink="true">https://browniebroke.com/tils/2022-08-31-sudo-with-touchid-on-macos/</guid><pubDate>Wed, 31 Aug 2022 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;A nifty trick learned from &lt;a href=&quot;https://it.digitaino.com/use-touchid-to-authenticate-sudo-on-macos/&quot;&gt;this blog post&lt;/a&gt;. This is mostly writing here for my own future reference:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Edit &lt;code&gt;/etc/pam.d/sudo&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Add the line &lt;code&gt;auth sufficient pam_tid.so&lt;/code&gt; below the &lt;code&gt;pam_smartcard.so&lt;/code&gt; line&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Done!&lt;/p&gt;
</content:encoded></item><item><title>Using Codespell to check for typos</title><link>https://browniebroke.com/tils/2022-05-07-using-codespell-to-check-for-typos/</link><guid isPermaLink="true">https://browniebroke.com/tils/2022-05-07-using-codespell-to-check-for-typos/</guid><pubDate>Sat, 07 May 2022 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Today, I stumbled upon &lt;a href=&quot;https://github.com/codespell-project/codespell&quot;&gt;Codespell&lt;/a&gt;, a tool to check for spelling mistakes in your code. I found out about it by looking at the &lt;a href=&quot;https://github.com/pyscript/pyscript&quot;&gt;PyScript repo&lt;/a&gt;, which they define in their pre-commit config. Since most of my repos are set up with pre-commit.ci already, I went ahead and added the config, which just a couple of lines:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;repos:
  - repo: https://github.com/codespell-project/codespell
    rev: v2.1.0
    hooks:
      - id: codespell
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;It didn&apos;t raise a lot of issues, but it did catch a couple of typos here and there.&lt;/p&gt;
</content:encoded></item><item><title>Using a temporary directory for media files while running tests in Django</title><link>https://browniebroke.com/tils/2022-05-05-using-a-temporary-directory-for-media-files-while-running-tests-in-django/</link><guid isPermaLink="true">https://browniebroke.com/tils/2022-05-05-using-a-temporary-directory-for-media-files-while-running-tests-in-django/</guid><pubDate>Thu, 05 May 2022 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Our Django test suite uses &lt;a href=&quot;https://factoryboy.readthedocs.io/en/stable/index.html&quot;&gt;factory-boy&lt;/a&gt; to simplify generating mocked data for our tests. Some of our models are creating images, and by default they are all called &apos;example.jpg&apos;, and some suffix is added avoid collision. As a result the media folder was being filled with &lt;code&gt;example_&amp;lt;radom suffix&amp;gt;.jpg&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Not only this filled the disk on our development machines, but it also may cause some flakiness in our tests if the filename is part of the assertion.&lt;/p&gt;
&lt;p&gt;To avoid that, I configured our test settings to use a temporary directory as media root:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;MEDIA_ROOT = tempfile.TemporaryDirectory().name
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Since we use pytest-xdist to run test in parallel on the same machine, I&apos;ve also included &lt;a href=&quot;https://pytest-xdist.readthedocs.io/en/latest/how-to.html#envvar-PYTEST_XDIST_WORKER&quot;&gt;the worker name&lt;/a&gt; in the suffix:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;PYTEST_XDIST_WORKER = env(
    &apos;PYTEST_XDIST_WORKER&apos;,
    default=&apos;gw0&apos;,
)
MEDIA_ROOT = tempfile.TemporaryDirectory(
    suffix=f&apos;_{PYTEST_XDIST_WORKER}&apos;,
).name
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Once the session is complete, the temporary file is deleted, along with its content.&lt;/p&gt;
</content:encoded></item><item><title>Tracking down a bug with tox and git</title><link>https://browniebroke.com/tils/2022-05-03-tracking-down-bug-with-tox-and-git/</link><guid isPermaLink="true">https://browniebroke.com/tils/2022-05-03-tracking-down-bug-with-tox-and-git/</guid><pubDate>Tue, 03 May 2022 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Today I wanted to track down what broke the CI of an open source library (&lt;a href=&quot;https://github.com/nedbat/django_coverage_plugin&quot;&gt;django-coverage-plugin&lt;/a&gt;) which is testing against Django main branch.&lt;/p&gt;
&lt;p&gt;The build was passing recently, but broke a few days ago. I needed to run something like &lt;a href=&quot;(https://git-scm.com/docs/git-bisect)&quot;&gt;a git bisect&lt;/a&gt;, except it was not on the project itself (which hadn&apos;t changed much) but on Django, while running the project&apos;s test suite.&lt;/p&gt;
&lt;p&gt;The project is tested with &lt;a href=&quot;https://tox.wiki/en/latest/&quot;&gt;tox&lt;/a&gt;, which I used to run an isolated environment to install a version of Django from an archive based on a git commit, which GitHub exposes:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;[testenv]
deps =
    djangorev: https://github.com/django/django/archive/dcebc5d.tar.gz
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;I looked at the recent commits from &lt;a href=&quot;https://github.com/django/django/commits/main&quot;&gt;the main branch&lt;/a&gt;, picked one from a few days ago, made sure it worked, and saved it as &lt;code&gt;djangook&lt;/code&gt; env. I then selected the most recent one, made sure that it failed, and saved it as &lt;code&gt;djangoko&lt;/code&gt; env.&lt;/p&gt;
&lt;p&gt;I chose the middle revision and replaced &lt;code&gt;djangook&lt;/code&gt; or &lt;code&gt;djangoko&lt;/code&gt;, depending on the result, which led me to find the commit which broke the test for the coverage plugin.&lt;/p&gt;
&lt;p&gt;This is more or less the steps that happens when doing a git bisect -or a dichotomy in mathematics- except here I couldn&apos;t use this tool, so I had to do it manually. Tox saved me ton of time as I was able to create several isolated environments quickly.&lt;/p&gt;
</content:encoded></item><item><title>Adding TILs to my site</title><link>https://browniebroke.com/tils/2022-05-01-adding-til-to-my-site/</link><guid isPermaLink="true">https://browniebroke.com/tils/2022-05-01-adding-til-to-my-site/</guid><pubDate>Sun, 01 May 2022 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Inspired by &lt;a href=&quot;https://til.simonwillison.net/&quot;&gt;Simon Willison&lt;/a&gt;, and driven by the will to write more on this site, I&apos;ve decided to add a TILs section, where the format is shorter and requires less introduction than a fully fledge blog post.&lt;/p&gt;
</content:encoded></item></channel></rss>