<?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&apos;s Blog</title><description>My personal blog</description><link>https://browniebroke.com/</link><item><title>Introducing django-remake-migrations</title><link>https://browniebroke.com/blog/2025-03-03-introducing-django-remake-migrations/</link><guid isPermaLink="true">https://browniebroke.com/blog/2025-03-03-introducing-django-remake-migrations/</guid><description>Squashing Django migrations in a medium to large Django project can be tedious and error-prone. Introducing a pluggable Django app to make it quick and easy.</description><pubDate>Mon, 03 Mar 2025 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Django &lt;a href=&quot;https://docs.djangoproject.com/en/stable/topics/migrations/&quot;&gt;migrations&lt;/a&gt; framework is a great tool to evolve your database schema over time allowing you to make pretty much any change to your data model (create/drop tables, add, change or remove columns, create or drop index) as well as writing data migrations when you need to move data around. Each time you make a change to your model, Django creates a new migration file with the diff. Sometimes it changes the database, but sometimes it&apos;s only changing the model state.&lt;/p&gt;
&lt;p&gt;However, it can easily become an append-only system, and on a project of a decent size (medium to large), historical migrations can start to slow down your development cycle, for example if you run all migrations on CI, or for deployment preview environment linked to each pull request. In these 2 cases, it&apos;s quite common to run all migrations from scratch, and once the project went through enough model changes, it can easily take several minutes.&lt;/p&gt;
&lt;h2&gt;The built-in way&lt;/h2&gt;
&lt;p&gt;Django provides a built-in &lt;code&gt;squashmigrations&lt;/code&gt; command to help reduce the amount of migrations files, it works on a single app and takes a range of migrations to merge together into a single file, trying to optimize the operations along the way. However, Django - having the user base it has - needs to be absolutely certain and correct about which optimizations to perform, and can only do so much.&lt;/p&gt;
&lt;p&gt;A bigger problem, in my opinion, is the limitation to work a single app only. Projects tend to make use of Django apps to modularize the codebase, but without strict guardrails, it&apos;s very easy to introduce circular inter-apps dependencies and after enough time, it becomes practically impossible to squash migrations. You want to squash migrations in app A, but they depend on app B. App B depends on app C which in turn depends on app A. If your project has a dozen apps, you quickly get many cycles like this.&lt;/p&gt;
&lt;h2&gt;How it works&lt;/h2&gt;
&lt;p&gt;The built-in squash migration command works by looking at all the operations in the migration files, putting them all together in a single file. But what happens when the squash migrations are deployed? How does Django know to not run them again and create a table that in fact already exists? It&apos;s marking the new migration file as replacing the original migrations. Here is an example, consider the following 2 migrations files:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# 0001_initial.py
class Migration(migrations.Migration):
    initial = True

    operations = [
        migrations.CreateModel(
            name=&quot;Book&quot;,
            fields=[
                (
                    &quot;id&quot;,
                    models.BigAutoField(
                        auto_created=True,
                        primary_key=True,
                        serialize=False,
                        verbose_name=&quot;ID&quot;,
                    ),
                ),
            ],
        ),
    ]

# 0002_add_title.py
class Migration(migrations.Migration):
    dependencies = [
        (&quot;library&quot;, &quot;0001_initial&quot;),
    ]

    operations = [
        migrations.AddField(
            model_name=&quot;book&quot;,
            name=&quot;title&quot;,
            field=models.CharField(
                max_length=255,
            ),
        ),
    ]
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If we squash them, we&apos;d probably get something along these lines:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# 0001_squashed_initial
class Migration(migrations.Migration):
    initial = True

    replaces = [
        (&quot;library&quot;, &quot;0001_initial&quot;),
        (&quot;library&quot;, &quot;0002_add_title&quot;),
    ]

    operations = [
        migrations.CreateModel(
            name=&quot;Book&quot;,
            fields=[
                (
                    &quot;id&quot;,
                    models.BigAutoField(
                        auto_created=True,
                        primary_key=True,
                        serialize=False,
                        verbose_name=&quot;ID&quot;,
                    ),
                ),
            ],
        ),
        migrations.AddField(
            model_name=&quot;book&quot;,
            name=&quot;title&quot;,
            field=models.CharField(
                max_length=255,
            ),
        ),
    ]
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Notice how the 2 operations are put together in the same file, and the squashed migration file is marked as replacement of the previous 2 using the &lt;code&gt;replaces&lt;/code&gt; attribute. This is what tells Django not actually execute the operations in the squashed migration file, unless the migrations it replaces haven&apos;t run.&lt;/p&gt;
&lt;h2&gt;Introducing django-remake-migrations&lt;/h2&gt;
&lt;p&gt;Building on this feature, &lt;a href=&quot;https://github.com/browniebroke/django-remake-migrations&quot;&gt;django-remake-migrations&lt;/a&gt; bring the power of &lt;code&gt;squashmigrations&lt;/code&gt; to all the first party apps in your project. It provides a new command called &lt;code&gt;remakemigrations&lt;/code&gt; that:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Delete all the migration files&lt;/li&gt;
&lt;li&gt;Call Django&apos;s &lt;code&gt;makemigrations&lt;/code&gt; command to generate the minimum number of files and operations from scratch&lt;/li&gt;
&lt;li&gt;Mark all these new migrations as squashed using the &lt;code&gt;replaces&lt;/code&gt; attribute, to prevent them from executing on system which are fully migrated.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;However, this comes with a pretty big caveat: the &lt;code&gt;replaces&lt;/code&gt; field is pretty much guaranteed to be wrong in terms of which migrations are replaced by which, but if all your deployments are fully migrated, it doesn&apos;t matter much in practice.&lt;/p&gt;
&lt;p&gt;On a medium-sized project, it can be tricky to time such deployment and avoid conflicting with another teammate who might add a migration somewhere. For that, I highly recommend using &lt;a href=&quot;https://github.com/adamchainz/django-linear-migrations&quot;&gt;django-linear-migrations&lt;/a&gt; as it will help you flag such conflict in git, hence preventing unknowingly concurrent merges. The library provides an integration hook to regenerate max-migrations files at the end. There are a few options if you need to include some database extensions or if you want to prioritize certain apps to generate migrations for.&lt;/p&gt;
&lt;h2&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;This probably won&apos;t work on very large codebases with 100&apos;s of devs, or for projects that don&apos;t fully control all the deployments. However, I believe it works quite well for a vast majority of Django codebases, where a few dozen people work on the project at once.&lt;/p&gt;
</content:encoded></item><item><title>Project-level shareable live templates in PyCharm (or other IntelliJ IDEs)</title><link>https://browniebroke.com/blog/2025-02-21-project-level-shareable-live-templates-in-pycharm/</link><guid isPermaLink="true">https://browniebroke.com/blog/2025-02-21-project-level-shareable-live-templates-in-pycharm/</guid><description>Easily share small snippets of boilerplate code with your team when working on a large project.</description><pubDate>Fri, 21 Feb 2025 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Most code editors have the ability to scaffold boilerplate with small pieces of templates: type a keyword and get some code generated for you, with the cursor moved to where you need to fill in the blank. While LLMs are getting better at suggesting code for us, these snippets are usually more precise, faster and easier to direct/nudge than an LLM, and serve a different purpose than what an LLM does.&lt;/p&gt;
&lt;p&gt;IntelliJ calls them &lt;a href=&quot;https://www.jetbrains.com/help/pycharm/using-live-templates.html&quot;&gt;live templates&lt;/a&gt;, VS Code calls them &lt;a href=&quot;https://code.visualstudio.com/docs/editor/userdefinedsnippets&quot;&gt;snippets&lt;/a&gt;. &lt;a href=&quot;https://emmet.io&quot;&gt;Emmet&lt;/a&gt; works as a plugin for all editors in this category too.&lt;/p&gt;
&lt;h2&gt;The missing feature&lt;/h2&gt;
&lt;p&gt;One feature that VS Code has is the ability to define &lt;a href=&quot;https://code.visualstudio.com/docs/editor/userdefinedsnippets#_project-snippet-scope&quot;&gt;project level snippets&lt;/a&gt;. While working on a big codebase with a team, common patterns tend to emerge and having them encoded in custom snippets help to nudge everyone in following them.&lt;/p&gt;
&lt;p&gt;However, not everyone uses VS Code. I use PyCharm and I figured I was missing out on these project level snippets. I had some custom live templates but sharing them with my fellow PyCharm users wasn&apos;t as straightforward.&lt;/p&gt;
&lt;h2&gt;Finding a solution&lt;/h2&gt;
&lt;p&gt;After a bit of research, I discovered where my custom live templates are stored by my editor. Turns out that, on macOS, they are stored as xml in &lt;code&gt;~/Library/Application Support/JetBrains/PyCharm2024.3/templates/&lt;/code&gt; (with PyCharm2024.3 whatever edit and version you&apos;re on). I wondered if I could make one a symbolic link to a folder in my repo and sure enough it worked.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;From the editor UI, under Settings &amp;gt; Editor &amp;gt; Live templates&lt;/li&gt;
&lt;li&gt;Add a new Template Group, e.g. &quot;MyGroup&quot;&lt;/li&gt;
&lt;li&gt;Add a live template in it&lt;/li&gt;
&lt;li&gt;Go to the templates directory from earlier, a new XML file with the same name should appear&lt;/li&gt;
&lt;li&gt;Copy that file into your repo, in a memorable location. I went with &lt;code&gt;.pycharm/&lt;/code&gt; folder at the root of my repo.&lt;/li&gt;
&lt;li&gt;Create a symlink from where PyCharm looks pointing at your repo (assuming it&apos;s at &lt;code&gt;/path/to/repo/&lt;/code&gt;):&lt;pre&gt;&lt;code&gt;ln -s \
 /path/to/repo/.pycharm/MyGroup.xml \
 ~/Library/Application\ Support/JetBrains/PyCharm2024.3/templates/MyGroup.xml
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;After that, I had to restart my editor for the new file to be picked up. I did a few updates and the symlink was correctly followed on writes: the live templates updated in the UI appeared in the file stored in the repo.&lt;/p&gt;
&lt;h2&gt;Install script&lt;/h2&gt;
&lt;p&gt;That worked well, but the instructions to install them were a bit fiddly, mainly because it requires to pass some absolute paths which are dynamic and prone to change in future versions. I figured I could write a simple bash script to do create the symlink in the right place and make a simple installation.&lt;/p&gt;
&lt;p&gt;With a bit of help from the Codium plugin in my editor, I ended with a good enough script that looks like this:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;#!/usr/bin/env bash

# Resolve path to the Legl.xml file in this directory
PARENT_DIR=&quot;$(dirname &quot;$0&quot;)&quot;
FULL_PATH=&quot;$(pwd)/$PARENT_DIR/MyGroup.xml&quot;

# Get folder with highest number matching
# ~/Library/Application\ Support/JetBrains/PyCharm20*
LARGEST_VERSION_DIR=$(ls -d ~/Library/Application\ Support/JetBrains/PyCharm20* | sort -V | tail -1)

ln -s \
  &quot;$FULL_PATH&quot; \
  &quot;$LARGEST_VERSION_DIR/templates/MyGroup.xml&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;it&apos;s probably not catering for all use cases but as I said it&apos;s good enough. Now my teammates can run that install script with &lt;code&gt;.pycharm/install.sh&lt;/code&gt; and the live templates are synced back and forth.&lt;/p&gt;
</content:encoded></item><item><title>Migrating from Chakra UI to Tailwind CSS</title><link>https://browniebroke.com/blog/2025-01-24-migrating-from-chakra-ui-to-tailwind-css/</link><guid isPermaLink="true">https://browniebroke.com/blog/2025-01-24-migrating-from-chakra-ui-to-tailwind-css/</guid><description>How I migrated this blog from Chakra UI to tailwind CSS in a matter of hours using Cursor</description><pubDate>Fri, 24 Jan 2025 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;I wrote before how &lt;a href=&quot;/blog/migrate-my-site-design-system-to-chakra-ui-with-chatgpt/&quot;&gt;I migrated to ChakraUI&lt;/a&gt; using ChatGPT to guide me and how that sped up the task. Less than a year later, I migrated again my style system but this time it was even more impressive, and even faster.&lt;/p&gt;
&lt;h2&gt;The migration&lt;/h2&gt;
&lt;p&gt;Having heard about &lt;a href=&quot;https://www.cursor.com/&quot;&gt;Cursor&lt;/a&gt; a lot lately, and having seen it in action with some colleagues at work, I decided to give it a proper go and was really impressed. I opened the &quot;composer&quot; tool in &quot;agent&quot; mode, opened my codebase and asked to &quot;Replace ChakraUI by Tailwind CSS with the minimum amount of visual differences possible&quot;.&lt;/p&gt;
&lt;p&gt;The agent went off and started looking at the structure of the repo, and my dependencies, detected that I was running Gatsby and suggested a plan. Then it suggested running &lt;code&gt;npm install ...&lt;/code&gt; command which sadly failed with permissions issues. I ran them myself in another terminal, and told it that to proceed. I sat and watch him make modifications to the codebase, going through my components and migrating them one by one. It then it claimed to have completed it and ended by uninstalling the old dependency (I had to run this manually again due to permissions issues).&lt;/p&gt;
&lt;p&gt;I this point I tried to start the site, and it crashed as the migration was in fact not fully completed. I asked it to finish migrating the components and it finally did it. It then asked whether it should proceed with other folders which I instructed it to do it, and eventually reached a point where Chakra UI was gone.&lt;/p&gt;
&lt;p&gt;To my amazement, the site started and looked pretty close to what I had before. This whole process took maybe 15 or 20 mins and I didn&apos;t type or supervised it very much. I was really impressed. I then only had to do some final tweaks to get the styles closer to what I wanted. I never used Tailwind CSS before so it was a massive speed up.&lt;/p&gt;
&lt;h2&gt;Why migrate&lt;/h2&gt;
&lt;p&gt;As time of writing, this blog is built with GatsbyJS. Unfortunately, the project has been dormant for quite a while now, pretty much since Netlify bought them. At this point, I consider the project dead and while there is no urgency to migrate, some of the tolling starts to fall apart, and with the velocity of Javascript development, I&apos;m concerned that might not be able to migrate in a couple of months time if I don&apos;t do it now, as the migration guide might go away one day.&lt;/p&gt;
&lt;p&gt;An interesting framework that I might want to try is &lt;a href=&quot;https://docs.astro.build/en/guides/migrate-to-astro/from-gatsby/&quot;&gt;Astro&lt;/a&gt;, but from what I can tell, there is not real integration with Chakra UI (not mentioning that Chakra UI released a new version which doesn&apos;t support GatsbyJS AFAIK).&lt;/p&gt;
&lt;p&gt;All this made me realise that my design system is too much coupled with the framework I use (React/Gatsby), and that I would benefit to switch to something a bit more framework-agnostic. I never tried Tailwind before, as it wasn&apos;t appealing to me, I still don&apos;t get the whole utility classes, but I&apos;m giving it a try. Perhaps it&apos;s because I&apos;m not a CSS developer or because I&apos;m not working on a big team...&lt;/p&gt;
&lt;p&gt;Anyway, I&apos;m hoping that Tailwind, with its huge community will have support in most places, and won&apos;t tie me to any framework.&lt;/p&gt;
&lt;h2&gt;Next steps&lt;/h2&gt;
&lt;p&gt;I&apos;ll do some more iterations to limit my Tailwind usage to reusable components only, which I think should help if I decide to move off something React-based. That experiment taught me that while doing these kind of migrations used to be a really painful, it will be getting easier and easier, these AI tools are great application for them.&lt;/p&gt;
</content:encoded></item><item><title>Testing your Python logging like a boss</title><link>https://browniebroke.com/blog/2024-12-13-testing-your-python-logging-like-a-boss/</link><guid isPermaLink="true">https://browniebroke.com/blog/2024-12-13-testing-your-python-logging-like-a-boss/</guid><description>How to properly test that your Python code logs what you expect.</description><pubDate>Fri, 13 Dec 2024 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;The problem&lt;/h2&gt;
&lt;p&gt;I recently wanted to add a test to some piece of my code to make sure I would be properly notified in case of certain problem occurred. The system in question uses Sentry, so I was planning to use &lt;code&gt;logger.error()&lt;/code&gt; from the standard library, which would create an issue for me.&lt;/p&gt;
&lt;p&gt;I wanted to make sure I had the relevant details in my message, hence why I wanted a test for it.&lt;/p&gt;
&lt;h2&gt;The naive solution&lt;/h2&gt;
&lt;p&gt;The initial solution I reached for was to mock the logger and assert calls matched what I needed:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# script.py
import logging

logger = logging.getLogger(__name__)

def run(group):
    if missing_fields := group.has_missing_fields():
        logger.error(
            &quot;Missing data for group %s: %r&quot;,
            group,
            missing_fields
        )
        return

    # ... Proceed
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And here is how my test looked like:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# test_script.py
def test_error_logging(mocker):
    group_with_missing_data = Group()
    logger = mocker.patch(&quot;script.logger&quot;, autospec=True)

    run(group_with_missing_data)

    logger.exception.assert_called_once_with(
        &quot;Missing data for group %s: %r&quot;,
        group_with_missing_data,
        [&quot;name&quot;],
    )
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The problem was that the test was not very meaningful:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;it was very close from the implementation, almost mirroring it, and felt a bit like writing &quot;2+2 == 2+2&quot; instead of &quot;2+2 == 4&quot;.&lt;/li&gt;
&lt;li&gt;it didn&apos;t convey how the final formatted message would look like, I wanted to make sure that the structure were readable&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Another alternative was to format the message using an f-string, but that&apos;s &lt;a href=&quot;https://docs.astral.sh/ruff/rules/logging-f-string/&quot;&gt;a discouraged practice&lt;/a&gt;, mainly because it&apos;s eagerly formatting the string. I also noticed that monitoring tools like Sentry tend have a harder time at grouping multiple instances of the same error when the message is formatted as f-string, as opposed to passing parameters separately.&lt;/p&gt;
&lt;h2&gt;The better solution&lt;/h2&gt;
&lt;p&gt;I wasn&apos;t happy with my solution, and kept thinking that there&apos;s got to be a better way of doing this. I had a bit of spare time to explore so I researched a bit and found a much better solution: pytest provides a &lt;a href=&quot;https://docs.pytest.org/en/stable/how-to/logging.html#caplog-fixture&quot;&gt;&lt;code&gt;caplog&lt;/code&gt; fixture&lt;/a&gt;. This is exactly the right tool for the job here, which enabled me to change my test to:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# test_script.py
def test_error_logging(caplog):
    caplog.set_level(logging.ERROR)
    group_with_missing_data = Group()

    run(group_with_missing_data)

    assert len(caplog.records) == 1
    assert caplog.records[0].levelname == &quot;ERROR&quot;
    assert caplog.records[0].message == (
        &quot;Missing data for group &amp;lt;Group &amp;gt;: &apos;name&apos;&quot;
    )
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This enables to set the level of logging you&apos;re interested in capturing, and make assertions on each log record. Each log record has the final formatted message, the unformatted messages, its parameters, the log level as well as a number of other things.&lt;/p&gt;
&lt;h2&gt;Without pytest&lt;/h2&gt;
&lt;p&gt;The standard library &lt;code&gt;TestCase&lt;/code&gt; class from the &lt;code&gt;unittests&lt;/code&gt; module has &lt;a href=&quot;https://docs.python.org/3/library/unittest.html#unittest.TestCase.assertLogs&quot;&gt;an &lt;code&gt;assertLogs&lt;/code&gt; method&lt;/a&gt; which offers a similar functionality.&lt;/p&gt;
&lt;h2&gt;Closing words&lt;/h2&gt;
&lt;p&gt;It was a nice discovery and I felt more confident that the alert from my monitoring will be useful when something fails. It&apos;s something I&apos;ll definitely use again next time I need it.&lt;/p&gt;
</content:encoded></item><item><title>Manage deprecations with Python warnings in a Django project</title><link>https://browniebroke.com/blog/2024-11-22-manage-deprecations-with-python-warnings-in-a-django-project/</link><guid isPermaLink="true">https://browniebroke.com/blog/2024-11-22-manage-deprecations-with-python-warnings-in-a-django-project/</guid><description>A post with a few tips on how to leverage the Python warnings module in a Django project</description><pubDate>Fri, 22 Nov 2024 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Python has a &lt;a href=&quot;https://docs.python.org/3/library/warnings.html&quot;&gt;&lt;code&gt;warnings&lt;/code&gt; module&lt;/a&gt; in its standard library that can be very helpful tool to manage deprecations. It&apos;s commonly used by libraries but can also be useful to manage internal changes on big projects with lots of concurrent changes.&lt;/p&gt;
&lt;h2&gt;The library use case&lt;/h2&gt;
&lt;p&gt;Libraries with lots of users sometimes want to notify their users of upcoming changes that they plan break in the future. On the receiving end, users can control which action to take, and this action may be different depending on the context.&lt;/p&gt;
&lt;p&gt;For instance, Django defines (at time of writing) a few warnings &lt;code&gt;RemovedInDjango60Warning&lt;/code&gt;, &lt;code&gt;RemovedInDjango61Warning&lt;/code&gt;, aliased to respectively &lt;code&gt;RemovedInNextVersionWarning&lt;/code&gt; and &lt;code&gt;RemovedAfterNextVersionWarning&lt;/code&gt;. While it may be useful to see these warnings in local development or while running tests on CI, it could be noisy to have them on production, potentially distracting us from other important signals.&lt;/p&gt;
&lt;p&gt;Warnings can be filtered in various ways:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;with the &lt;code&gt;-W&lt;/code&gt; command line argument when running Python (&lt;code&gt;python -W manage.py ...&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;with the &lt;code&gt;PYTHONWARNINGS&lt;/code&gt; environment variable&lt;/li&gt;
&lt;li&gt;programmatically, in your Python code with &lt;code&gt;warnings.filterwarnings(...)&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;in &lt;a href=&quot;https://docs.pytest.org/en/7.1.x/how-to/capture-warnings.html#controlling-warnings&quot;&gt;pytest&lt;/a&gt;, using the &lt;code&gt;-W&lt;/code&gt; option or via the &lt;code&gt;filterwarnings&lt;/code&gt; config.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I usually try to enable all warnings during my CI runs, and not show them anywhere else. If there is too much noise in my CI, then it&apos;s a sign that I should fix the warnings. If you need some more advanced filtering, &lt;a href=&quot;https://docs.python.org/3/library/warnings.html#warning-filter&quot;&gt;the filtering syntax&lt;/a&gt; is quite flexible, the pytest documentation does a good job at giving a few examples of filters.&lt;/p&gt;
&lt;h2&gt;Using warnings internally&lt;/h2&gt;
&lt;p&gt;Warnings can also be useful to manage deprecations within the bound of an application, without external downstream consumers of the code, but with lots of developers. In big project, it&apos;s quite common to have a shared internal library of utilities to make things easier to do, or just have service layers that other parts of the system use. After a while, some of these parts can end up being used 100s or 1000s of times.&lt;/p&gt;
&lt;p&gt;When we inevitably reach the limit of a given system and want to refactor it towards a better approach, it can take time to transition. In some cases, data might need to be migrated over in the middle, and should to be released in stages to avoid downtime. While the 2 or 3 stages are implemented, the rest of team keeps moving at full speed, and may keep using the old pattern instead of using the new way of doing things.&lt;/p&gt;
&lt;p&gt;Emitting a custom warning is a good way to manage these gradual deprecations. It&apos;s much easier to control what happens on CI that it is on production, and you can opt to treat your warning as an error in this context, hence breaking the tests and signaling others towards the new way of doing things.&lt;/p&gt;
&lt;h3&gt;Example&lt;/h3&gt;
&lt;p&gt;Imagine you have a &lt;code&gt;is_user_allowed&lt;/code&gt; function in an internal library that you want to deprecate in favour of a &lt;code&gt;new_is_user_allowed&lt;/code&gt;. This function may be used in lots of places, to do higher level operations. You could add a call to emit a warning in your deprecated function:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# consumer.py
from lib import is_user_allowed

def do_high_level_stuff(user):
    is_user_allowed(user)
    ...

# lib.py
import warnings

def is_user_allowed(user):
    warnings.warn(
        &quot;is_user_allowed is deprecated&quot;,
        category=DeprecationWarning,
        stacklevel=2,
    )
    return new_is_user_allowed(user)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;It&apos;s a good idea to pass a &lt;code&gt;stacklevel&lt;/code&gt; of at least 2, otherwise the warning will appear to come from &lt;code&gt;lib.py&lt;/code&gt;. By passing a &lt;code&gt;stacklevel&lt;/code&gt; of 2, the warning will appear to come from &lt;code&gt;consumer.py&lt;/code&gt;, hence making it much easier to locate usages.&lt;/p&gt;
&lt;p&gt;You can configure pytest to treat this warning as error, except in existing places where it&apos;s already used:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# pyproject.toml
[tool.pytest.ini_options]
filterwarnings = [
    &quot;once&quot;,
    &quot;error:is_user_allowed is deprecated:DeprecationWarning&quot;,
    &quot;once:is_user_allowed is deprecated:DeprecationWarning:consumer&quot;,
    &quot;...&quot;,
]
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If other teammates working on other branches start using it in new places, their test will start failing, preventing them to introduce new usages of the deprecated function. This first step can be released while you gradually fix all the existing usages, removing them from the pytest&apos;s &lt;code&gt;filterwarnings&lt;/code&gt; list as you go.&lt;/p&gt;
&lt;p&gt;You may want to go one step further and log usages on production for a little while. Warnings aren&apos;t integrated with the standard logging module by default, but it&apos;s not very hard to setup. Here is how you can modify your Django &lt;code&gt;LOGGING&lt;/code&gt; setting to do it:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# settings.py
import logging

logging.captureWarnings(True)
LOGGING = {
    &quot;version&quot;: 1,
    &quot;disable_existing_loggers&quot;: False,
    &quot;handlers&quot;: {
        &quot;console&quot;: {
            &quot;class&quot;: &quot;logging.StreamHandler&quot;,
        },
    },
    &quot;root&quot;: {
        &quot;handlers&quot;: [&quot;console&quot;],
        &quot;level&quot;: &quot;WARNING&quot;,
    },
    &quot;loggers&quot;: {
        &quot;apps&quot;: {
            &quot;handlers&quot;: [&quot;console&quot;],
            &quot;level&quot;: &quot;INFO&quot;,
            &quot;propagate&quot;: False,
        },
        &quot;py.warnings&quot;: {
            &quot;handlers&quot;: [&quot;console&quot;],
            &quot;level&quot;: &quot;WARNING&quot;,
            &quot;propagate&quot;: False,
        },
    },
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Integration is enabled with &lt;code&gt;logging.captureWarnings(True)&lt;/code&gt; and use the &lt;code&gt;py.warnings&lt;/code&gt; logger, hence the last logger definition. Here are &lt;a href=&quot;https://docs.python.org/3/library/logging.html#integration-with-the-warnings-module&quot;&gt;the official docs&lt;/a&gt; on how to integrate that, if you&apos;re interested to read more.&lt;/p&gt;
&lt;p&gt;The final piece to get our warning actually emitted, is to set the filter via the &lt;code&gt;PYTHONWARNINGS&lt;/code&gt; environment variable:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;PYTHONWARNINGS=&quot;all:is_user_allowed is deprecated:DeprecationWarning:&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If you need to set multiple filters in the env variable, they need be comma separated:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;PYTHONWARNINGS=&quot;all:is_user_allowed is deprecated:DeprecationWarning:,all:old_function is also deprecated:DeprecationWarning:&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;Hope this post will help you get up to speed with warnings management, the last bit to integrate with the logging module was new to me.&lt;/p&gt;
</content:encoded></item><item><title>Keep uv.lock file up-to-date with Dependabot updates</title><link>https://browniebroke.com/blog/2024-10-02-keep-uv-lock-file-up-to-date-with-dependabot-updates/</link><guid isPermaLink="true">https://browniebroke.com/blog/2024-10-02-keep-uv-lock-file-up-to-date-with-dependabot-updates/</guid><description>A short post on how I augmented the lack of uv supported in Dependabot using GitHub actions.</description><pubDate>Wed, 02 Oct 2024 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;In the past couple of weeks, there has been &lt;a href=&quot;https://micro.webology.dev/2024/09/19/uv-roundup-five.html&quot;&gt;a lot of interest&lt;/a&gt; in the Python community for what started as a new Python package manager, but is now slowly growing into so much more, shipping features faster that people can blog about them.&lt;/p&gt;
&lt;p&gt;I&apos;ve been looking at adding it to some projects, but one of the main blocker is that &lt;a href=&quot;https://github.com/dependabot/dependabot-core/issues/10478&quot;&gt;Dependabot doesn&apos;t support it yet&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;However, as the project dependencies rely on the standard &lt;a href=&quot;https://peps.python.org/pep-0621/&quot;&gt;PEP-621&lt;/a&gt;, updating these is already supported by Dependabot, and it sends some PRs for them, however, the lock file (&lt;code&gt;uv.lock&lt;/code&gt;) is not updated automatically yet.&lt;/p&gt;
&lt;p&gt;To workaround that, I came up with a small workflow powered by GitHub actions:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;name: uv

on:
  pull_request:
    paths:
      - &quot;pyproject.toml&quot;

permissions:
  contents: write
  pull-requests: write

jobs:
  lock:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
        with:
          token: ${{ secrets.GH_PAT }}
      - uses: astral-sh/setup-uv@v3
        with:
          enable-cache: true
      - run: uv lock
      - uses: stefanzweifel/git-auto-commit-action@v5
        with:
          commit_message: Regenerate uv.lock
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Here are a few things to note:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The workflow only runs on changes to &lt;code&gt;pyproject.toml&lt;/code&gt;, which is AFAIK the only file that could cause a lock file change.&lt;/li&gt;
&lt;li&gt;I run the checkout with a Personal Access Token, to trigger the CI build on push. This is explained in &lt;a href=&quot;https://github.com/stefanzweifel/git-auto-commit-action#commits-made-by-this-action-do-not-trigger-new-workflow-runs&quot;&gt;the GitHub action&lt;/a&gt; I use to push.&lt;/li&gt;
&lt;li&gt;Finally, &lt;code&gt;stefanzweifel/git-auto-commit-action&lt;/code&gt; commit and pushes any changes back to the branch, if any.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The fact that I need to set a GitHub token in the repo secrets isn&apos;t ideal, but hopefully support will be added soon.&lt;/p&gt;
&lt;p&gt;Alternatively, if you&apos;ve not tied to Dependabot, you can use &lt;a href=&quot;https://docs.renovatebot.com/&quot;&gt;Renovate&lt;/a&gt; instead, which &lt;a href=&quot;https://docs.renovatebot.com/modules/manager/pep621/#additional-information&quot;&gt;already supports&lt;/a&gt; the &lt;code&gt;uv.lock&lt;/code&gt; file.&lt;/p&gt;
&lt;p&gt;It&apos;s quite nice that we can extend GitHub features like this, the product was quite different a few years ago before actions were introduced.&lt;/p&gt;
</content:encoded></item><item><title>Narrow state of a Django model using Python TypeGuard</title><link>https://browniebroke.com/blog/2024-08-31-narrow-state-of-a-django-model-using-python-typeguard/</link><guid isPermaLink="true">https://browniebroke.com/blog/2024-08-31-narrow-state-of-a-django-model-using-python-typeguard/</guid><pubDate>Sat, 31 Aug 2024 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Today, I came across a problem with type checking in a Django project which led me to use &lt;code&gt;TypeGuard&lt;/code&gt; for the first time. I&apos;ve heard about them in TypeScript on the &lt;a href=&quot;https://syntax.fm/&quot;&gt;Syntax podcast&lt;/a&gt;, so I was aware of the concept, but never had a practical situation where I needed to use them in Python.&lt;/p&gt;
&lt;h2&gt;The problem&lt;/h2&gt;
&lt;p&gt;So I was doing some refactoring to eliminate a bit of code duplication between 3 functions. As features were added, I copy-pasted the first into the second, and then later in the 3rd. I wasn&apos;t sure at the time if they were going to stay exactly the same, so copying made sense at the time. The &lt;a href=&quot;https://en.wikipedia.org/wiki/Don%27t_repeat_yourself&quot;&gt;DRY principle&lt;/a&gt; is well known, but optimising too early isn&apos;t good either, and I&apos;ve heard some folks say that DRY should be applied when you repeat yourself 3 times, so here we are.&lt;/p&gt;
&lt;p&gt;The code is in a Django codebase, dealing with Django models which can be in different states. As a simplified example, there was an &lt;code&gt;Order&lt;/code&gt; model, with what the customer purchased. When the order is being prepared, a lot of fields are empty, which get filled as it&apos;s being fulfilled and shipped:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;from django.db import models

class Order(models.Model):
    received_at = models.DateTimeField(null=True)
    shipped_at = models.DateTimeField(null=True)
    delivery_address = models.ForeignKey(
        Address,
        on_delete=models.PROTECT,
        related_name=&quot;orders&quot;,
        blank=True,
        null=True,
    )
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;My duplicated piece of code was checking for these empty fields before moving on to the main logic where the fields are needed:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;def send_order_shipped_email(order: Order) -&amp;gt; None:
    if not (
        order.received_at
        and order.shipped_at
        and order.delivery_address
    ):
        return

    context = {
        &quot;order_id&quot;: order.id,
        &quot;received_date&quot;: format_date(order.received_at),
        &quot;received_time&quot;: format_time(order.received_at),
        &quot;shipped_date&quot;: format_date(order.shipped_at),
        &quot;shipped_time&quot;: format_time(order.shipped_at),
        &quot;delivery_address&quot;: order.delivery_address.short_address(),
    }

    send_template_email(&quot;emails/order_shipped.html&quot;, context)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The last line differed depending on the shipping status, but you get the idea. So I was trying to extract the pre-conditions and the building of the context. This was my initial solution:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;def is_order_shipping(order: Order) -&amp;gt; bool:
    return bool(
        order.received_at
        and order.shipped_at
        and order.delivery_address
    )


def build_context(order: Order) -&amp;gt; dict[str, str]:
    if not is_order_shipping(order):
        return {}

    return {
        &quot;order_id&quot;: order.id,
        &quot;received_date&quot;: format_date(order.received_at),
        &quot;received_time&quot;: format_time(order.received_at),
        &quot;shipped_date&quot;: format_date(order.shipped_at),
        &quot;shipped_time&quot;: format_time(order.shipped_at),
        &quot;delivery_address&quot;: order.delivery_address.short_address(),
    }


def send_order_shipped_email(order: Order) -&amp;gt; None:
    context = build_context(order)
    if not context:
        return

    send_template_email(&quot;emails/order_shipped.html&quot;, context)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The tests passed, but mypy wasn&apos;t happy! In my &lt;code&gt;build_context&lt;/code&gt; function, it complained that &lt;code&gt;received_at&lt;/code&gt;, &lt;code&gt;shipped_at&lt;/code&gt; and &lt;code&gt;delivery_address&lt;/code&gt; may be &lt;code&gt;None&lt;/code&gt;, which could happen, but I was checking for this case before using them. However, because it&apos;s done in a separate function that simply returned a &lt;code&gt;bool&lt;/code&gt;, mypy wasn&apos;t able to infer that types were checked properly.&lt;/p&gt;
&lt;h2&gt;The solution&lt;/h2&gt;
&lt;p&gt;This is where I remembered hearing about the concept of type guards. I found the &lt;a href=&quot;https://mypy.readthedocs.io/en/stable/type_narrowing.html#user-defined-type-guards&quot;&gt;mypy docs&lt;/a&gt; for it, which gives a few good examples.&lt;/p&gt;
&lt;p&gt;However, in my case, I needed to declare that the &lt;code&gt;Order&lt;/code&gt; type had some fields checked and ensured that they were not nullable. I ended up defining a specialised order type and using it as type guard argument:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;from __future__ import annotations

import datetime as dt
from typing import TYPE_CHECKING, TypeGuard

if TYPE_CHECKING:
    class ShippedOrder(Order):
        received_at: dt.datetime
        shipped_at: dt.datetime
        delivery_address: Address


def is_order_shipping(order: Order) -&amp;gt; TypeGuard[ShippedOrder]:
    return bool(
        order.received_at
        and order.shipped_at
        and order.delivery_address
    )
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;However, that didn&apos;t fully work, mypy complained that the Django fields were incompatible with the types of my specialised class:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;error: Incompatible types in assignment (expression has type &quot;Address&quot;, base class &quot;Order&quot; defined the type as &quot;ForeignKey[Address | Combinable | None, Address | None]&quot;)  [assignment]
error: Incompatible types in assignment (expression has type &quot;datetime&quot;, base class &quot;Order&quot; defined the type as &quot;DateTimeField[str | datetime | date | Combinable | None, datetime | None]&quot;)  [assignment]
error: Incompatible types in assignment (expression has type &quot;datetime&quot;, base class &quot;Order&quot; defined the type as &quot;DateTimeField[str | datetime | date | Combinable | None, datetime | None]&quot;)  [assignment]
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;I&apos;m not sure how to properly resolve these yet, so I marked the fields of my &lt;code&gt;ShippedOrder&lt;/code&gt; with &lt;code&gt;# type: ignore[assignment]&lt;/code&gt;.&lt;/p&gt;
&lt;h2&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;The solution isn&apos;t perfect, and it can be a bit cumbersome to define these types, I&apos;m glad there was a solution for it. I&apos;m glad I could take the knowledge I got from a podcast mostly focused on another language and apply it to Python.&lt;/p&gt;
</content:encoded></item><item><title>Attest build provenance for a Python package in GitHub actions</title><link>https://browniebroke.com/blog/2024-08-08-attest-build-provenance-for-a-python-package-in-github-actions/</link><guid isPermaLink="true">https://browniebroke.com/blog/2024-08-08-attest-build-provenance-for-a-python-package-in-github-actions/</guid><description>Using GitHub action to attest build provenance of a Python package published on PyPI</description><pubDate>Thu, 08 Aug 2024 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;As you may have noticed, supply chain attacks ae on the rise. These attacks usually target a small piece of software infrastructure that the target depends on and inject some malicious code. They regularly make the news and this problem is becoming evermore present in a world where software relies more and more on open source. Supply chains can be difficult to audit and reading every line of code you depend can be very time consuming at best and practically impossible at worst (good luck with your &lt;code&gt;npm_modules&lt;/code&gt; folder). The alternative of building software without any sort of dependencies is several order of magnitude harder.&lt;/p&gt;
&lt;p&gt;There is an initiative to try to improve the current state of affairs and aim to make the supply chain easier to audit. One of the main output as it stands is the &lt;a href=&quot;https://slsa.dev/spec/v1.0/&quot;&gt;SLSA specification&lt;/a&gt;, which provides guidelines to help both producers and consumers of software. I invite you to visit their website if you&apos;re interested to dive more.&lt;/p&gt;
&lt;p&gt;I maintain a few Python packages, and while they&apos;re not used by many people, I wanted to explore what I could do as a maintainer to help the consumers of my packages. One thing that seems to gain traction is &lt;a href=&quot;https://slsa.dev/spec/v1.0/provenance&quot;&gt;the build provenance attestation&lt;/a&gt;, which records where and how the package was built.&lt;/p&gt;
&lt;h2&gt;Attest build provenance action&lt;/h2&gt;
&lt;p&gt;Conveniently, there is an &lt;a href=&quot;https://github.com/actions/attest-build-provenance&quot;&gt;official GitHub action&lt;/a&gt; for that, which seems easy to use:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Ensure that the following permissions are set:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;permissions:
  id-token: write
  attestations: write
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The id-token permission gives the action the ability to mint the OIDC token necessary to request a Sigstore signing certificate. The attestations permission is necessary to persist the attestation.&lt;/p&gt;
&lt;p&gt;Add the following to your workflow after your artifact has been built:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;- uses: actions/attest-build-provenance@v1
  with:
    subject-path: &quot;&amp;lt;PATH TO ARTIFACT&amp;gt;&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The subject-path parameter should identify the artifact for which you want to generate an attestation.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Cool, so it seems like I just need to point my wheel and source distribution at the subject path, and we&apos;re good? Let&apos;s try it!&lt;/p&gt;
&lt;h2&gt;Integration with my project&lt;/h2&gt;
&lt;p&gt;I already build my packages on CI, using &lt;a href=&quot;https://docs.pypi.org/trusted-publishers/&quot;&gt;PyPI&apos;s trusted publisher&lt;/a&gt;, and my publish job looks something like this (this is a simplified example, omitted some parts for brevity):&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;jobs:
  publish:
    name: Publish package
    runs-on: ubuntu-latest
    permissions:
      id-token: write
    steps:
      - name: Build package
        run: poetry build

      - name: Publish package distributions to PyPI
        uses: pypa/gh-action-pypi-publish@release/v1
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The key point, is that after I run &lt;code&gt;poetry build&lt;/code&gt;, my CI worker has a &lt;code&gt;dist/&lt;/code&gt; folder with the packages files, that the following step is uploading to PyPI. I use Poetry, but most Python packaging tools use this convention, I think.&lt;/p&gt;
&lt;p&gt;Following the guide from the previous section, I get:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;jobs:
  publish:
    name: Publish package
    runs-on: ubuntu-latest
    permissions:
      id-token: write
      attestations: write
    steps:
      - name: Build package
        run: poetry build

      - uses: actions/attest-build-provenance@v1
        with:
          subject-path: &quot;dist/*&quot;

      - name: Publish package distributions to PyPI
        uses: pypa/gh-action-pypi-publish@release/v1
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;I made &lt;a href=&quot;https://github.com/browniebroke/stsmfa-cli/pull/692&quot;&gt;the change on one of my repos&lt;/a&gt;, and made a new PyPI release to test it, and the build finished with a new dedicated attestation section:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;_build-result.png&quot; alt=&quot;build Result&quot; /&gt;&lt;/p&gt;
&lt;p&gt;I also noticed that there is &lt;a href=&quot;https://github.com/browniebroke/stsmfa-cli/attestations&quot;&gt;new page on my repo&lt;/a&gt; listing all the attestations.&lt;/p&gt;
&lt;h2&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;I first heard about this a few weeks back and wanted to try it, and it seemed more complicated at first glance, but it was in fact pretty easy to do. I&apos;ve also updated my &lt;a href=&quot;https://github.com/browniebroke/pypackage-template/pull/785&quot;&gt;python package template&lt;/a&gt; so all my projects should be using that moving forward.&lt;/p&gt;
&lt;p&gt;I&apos;m not entirely clear whether this will be integrated deeper into PyPI at some point, but this is still early days.&lt;/p&gt;
</content:encoded></item><item><title>Bulk updating multiple repos with all-repos</title><link>https://browniebroke.com/blog/2023-07-10-bulk-updating-multiple-repos-with-all-repos/</link><guid isPermaLink="true">https://browniebroke.com/blog/2023-07-10-bulk-updating-multiple-repos-with-all-repos/</guid><description>A walkthrough of my setup that allows me to maintain many GitHub repos at once</description><pubDate>Mon, 10 Jul 2023 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;I like the open source communities and all the developments that they enable. As a developer, I like to have the ability to solve my own problems, but when someone else already solved the problem, it&apos;s even better. However, when nobody did, I like to share my solution with others, if possible and practical. I&apos;ve done it enough times that I&apos;m beginning to have a decent setup with a project template that gives me the boilerplate I need to get started quickly, and not waste too much time packaging and publishing my solution.&lt;/p&gt;
&lt;h2&gt;The problem&lt;/h2&gt;
&lt;p&gt;However, after a while, maintaining all these projects can become overwhelming. And when external things change, like adding support for a new version of a language/framework, dropping an old one, or adapting to how a tool works, it can be tedious to do bulk updates. Of course, in this case, I&apos;m not the first one to hit this problem, and there are a number of solutions out there.&lt;/p&gt;
&lt;h2&gt;The solution&lt;/h2&gt;
&lt;p&gt;I&apos;ve been using &lt;a href=&quot;https://github.com/asottile/all-repos&quot;&gt;all-repos&lt;/a&gt; for about a year and a half, and I&apos;m quite happy with it. It&apos;s a tool that allows you to run a command on all or some of your GitHub repositories. It&apos;s a command line tool written in Python, that&apos;s overall quite easy to use, with an API that has a bit of a learning curve, but is very powerful. In this post, I&apos;ll show you how I use it to update my projects.&lt;/p&gt;
&lt;h2&gt;Repository setup&lt;/h2&gt;
&lt;p&gt;I like to keep all my code in git and save it in GitHub, so I&apos;ve created a &lt;a href=&quot;https://github.com/browniebroke/all-my-repos&quot;&gt;repo&lt;/a&gt; for using it. So, here we are, one more repo... to manage all my repos. One step back to move forward, I guess.&lt;/p&gt;
&lt;h3&gt;Splitting the config&lt;/h3&gt;
&lt;p&gt;The &lt;a href=&quot;https://github.com/asottile/all-repos#configuring&quot;&gt;recommended usage&lt;/a&gt; is to create a config file, &lt;code&gt;all-repos.json&lt;/code&gt;, that contains some config controlling behaviour, as well as your credentials.&lt;/p&gt;
&lt;p&gt;However, often switch machine, and I wanted to store the config in source control to be able to share it more easily and only keep the credentials outside. This isn&apos;t supported by all-repos, and it seems to be an intentional design decision, and the author showed no intention to support it, so I created a package to do it: &lt;a href=&quot;https://github.com/browniebroke/all-repos-envvar&quot;&gt;all-repos-envvar&lt;/a&gt;. That&apos;s now 2 more repos to maintain... I hope that&apos;s worth it!&lt;/p&gt;
&lt;h3&gt;Learning the library&lt;/h3&gt;
&lt;p&gt;The library ships several separate CLI tools, but I must be missing their point, and actually I don&apos;t even use them.&lt;/p&gt;
&lt;p&gt;What I really find useful is the &lt;a href=&quot;https://github.com/asottile/all-repos/tree/main#writing-an-autofixer&quot;&gt;autofixer&lt;/a&gt;, which is documented at the bottom of the README. The documentation describes the API, and then finally gives &lt;a href=&quot;https://github.com/asottile/all-repos/tree/main#example-autofixer&quot;&gt;an example autofixer&lt;/a&gt;, which has the boilerplate you need. The documentation then links to the built-in autofixers which are great examples on what you can do.&lt;/p&gt;
&lt;h2&gt;My usage&lt;/h2&gt;
&lt;p&gt;So in my project, I have:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;A &lt;code&gt;pyproject.toml&lt;/code&gt; file with the dependencies I need, managed by Poetry.&lt;/li&gt;
&lt;li&gt;A &lt;code&gt;all-repos.json&lt;/code&gt; file that contains the config for all-repos, in git. It controls how I fetch the repos, how changes are submitted (pull request), the organisations I want to include, and some repos I want to exclude.&lt;/li&gt;
&lt;li&gt;My GitHub credentials, in a &lt;code&gt;.env&lt;/code&gt; file that I keep outside of source control.&lt;/li&gt;
&lt;li&gt;Finally, I copied the given boilerplate and wrote a &lt;code&gt;run_fix.py&lt;/code&gt; file which I keep changing for my need of the day.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Example&lt;/h2&gt;
&lt;p&gt;Python 3.7 recently reached EOL, so that was a good opportunity to use the tool to update all my projects in bulk. Here is how the script looked like:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import argparse
from pathlib import Path

from all_repos import autofix_lib
from all_repos.grep import repos_matching

# Find repos that have this file...
FILE_NAME = &quot;pyproject.toml&quot;
# ... and which content contains this string.
FILE_CONTAINS = &quot;tool.poetry&quot;
# Git stuff
GIT_COMMIT_MSG = &quot;feat: drop support for Python 3.7&quot;
GIT_BRANCH_NAME = &quot;feat/drop-python-3.7&quot;


def apply_fix():
    &quot;&quot;&quot;Apply fix to a matching repo.&quot;&quot;&quot;
    pyproject_toml = Path(&quot;pyproject.toml&quot;)
    content = pyproject_toml.read_text()

    # Be idempotent
    if &apos;python = &quot;^3.8&quot;&apos; in content:
        return

    new_content = content.replace(
        &apos;python = &quot;^3.7&quot;&apos;,
        &apos;python = &quot;^3.8&quot;&apos;,
    )
    pyproject_toml.write_text(new_content)

    ci_yml = Path(&quot;.github/workflows/ci.yml&quot;)
    if ci_yml.exists():
        content = ci_yml.read_text()
        content = content.replace(
            &apos;python-version:\n          - &quot;3.7&quot;\n&apos;,
            &quot;python-version:\n&quot;,
        )
        ci_yml.write_text(content)

    pc_yml = Path(&quot;.pre-commit-config.yaml&quot;)
    if pc_yml.exists():
        content = pc_yml.read_text()
        content = content.replace(
            &apos;--py37-plus&apos;,
            &apos;--py38-plus&apos;,
        )
        pc_yml.write_text(content)


# You shouldn&apos;t need to change anything below this line


def find_repos(config) -&amp;gt; set[str]:
    &quot;&quot;&quot;Find matching repos using git grep.&quot;&quot;&quot;
    repos = repos_matching(
        config,
        (FILE_CONTAINS, &quot;--&quot;, FILE_NAME),
    )
    return repos


def main():
    &quot;&quot;&quot;Entry point.&quot;&quot;&quot;
    # Needed to add all-repos command line options
    # and ability to parse config
    parser = argparse.ArgumentParser()
    autofix_lib.add_fixer_args(parser)
    args = parser.parse_args(None)

    repos, cfg, commit, stg = autofix_lib.from_cli(
        args,
        find_repos=find_repos,
        msg=GIT_COMMIT_MSG,
        branch_name=GIT_BRANCH_NAME,
    )
    autofix_lib.fix(
        repos,
        apply_fix=apply_fix,
        config=cfg,
        commit=commit,
        autofix_settings=stg,
    )
    return 0


if __name__ == &quot;__main__&quot;:
    raise SystemExit(main())
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Ok that&apos;s a lot of code, so let&apos;s break it down.&lt;/p&gt;
&lt;h3&gt;Constants&lt;/h3&gt;
&lt;p&gt;Fist off, I defined a few constants at the top of the file, which I always need to change:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;The &lt;code&gt;FILE_...&lt;/code&gt; ones are used to detect whether the repository is suitable for this change: in this example, a node project would be filtered out with this mechanism, in the &lt;code&gt;find_repos()&lt;/code&gt; function.&lt;/li&gt;
&lt;li&gt;the &lt;code&gt;GIT_...&lt;/code&gt; ones are for the git commit message and branch. If anything changes, the tool opens a pull request which I can review before merging.&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;&lt;code&gt;apply_fix()&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;This is where the actual code change happens. This function runs in the context of a matching repo, and the current directory is the top of the repo. You can read and write file as you please or run commands with &lt;code&gt;autofix_lib.run(...)&lt;/code&gt;. It&apos;s a good idea to start with a check to verify that the fix hasn&apos;t run yet, just in case.&lt;/p&gt;
&lt;h3&gt;&lt;code&gt;find_repos()&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;This is mainly boilerplate, it&apos;s essentially a wrapper around &lt;code&gt;git grep&lt;/code&gt; and uses 2 of the constants I explained earlier. If you want to learn more, I suggest to go &lt;a href=&quot;https://github.com/asottile/all-repos/blob/24911de91215a214adfe2e0ea3d4d1eb5e67a42a/all_repos/grep.py#L19-L44&quot;&gt;read the source code of &lt;code&gt;repos_matching&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;
&lt;h3&gt;Entry point&lt;/h3&gt;
&lt;p&gt;Finally, the &lt;code&gt;main()&lt;/code&gt; function is the entry point. It&apos;s called when running &lt;code&gt;python run_fix.py&lt;/code&gt;, and it mostly calls the autofix API with our parameters. This is a slightly simplified version from the boilerplate.&lt;/p&gt;
&lt;h2&gt;Lifecycle of the script&lt;/h2&gt;
&lt;p&gt;I keep this script around and just change it in place, committing each time I change it to make a bulk change. It&apos;s usually some one-off operations, but it&apos;s quite useful to have some history in git in case I need to do a similar task again, like when I&apos;ll need to drop Python 3.8. You can check &lt;a href=&quot;https://github.com/browniebroke/all-my-repos/commits/main/run_fix.py&quot;&gt;its history on GitHub&lt;/a&gt; to see how it evolved.&lt;/p&gt;
&lt;h2&gt;Trade-offs&lt;/h2&gt;
&lt;p&gt;On the downside, one might argue that the code is a bit verbose, but in my opinion it&apos;s a good thing. I&apos;m comfortable with Python, it does the job and let me use all of its power to do the changes I need, and step into a debugger when I need to.&lt;/p&gt;
&lt;p&gt;I came across other approaches, for example, &lt;a href=&quot;https://adamj.eu/tech/2020/04/02/maintaining-multiple-python-projects-with-myrepos/&quot;&gt;Adam Johnson described&lt;/a&gt; how he uses a tool called &lt;a href=&quot;https://myrepos.branchable.com/&quot;&gt;&lt;code&gt;myrepos&lt;/code&gt;&lt;/a&gt; to do something similar. It didn&apos;t fit my way of doing thing, but it might work for you.&lt;/p&gt;
&lt;h2&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;I think that&apos;s it! I hope this was useful, and will help make the all-repos tool more accessible. It&apos;s quite powerful, although I found that it had lots of things I didn&apos;t need.&lt;/p&gt;
</content:encoded></item><item><title>Nested ViewSets with Django REST Framework</title><link>https://browniebroke.com/blog/2023-05-05-nested-viewsets-with-django-rest-framework/</link><guid isPermaLink="true">https://browniebroke.com/blog/2023-05-05-nested-viewsets-with-django-rest-framework/</guid><description>How to place a ViewSet under a parent resource with Django REST Framework</description><pubDate>Fri, 05 May 2023 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;I&apos;ve been using Django REST Framework for a while now but mainly in old projects where lots of code have been written. In most cases, these projects didn&apos;t make good use of &lt;a href=&quot;https://www.django-rest-framework.org/api-guide/viewsets&quot;&gt;&lt;code&gt;ViewSets&lt;/code&gt;&lt;/a&gt;, due to a variety of reasons. It may be due to the original author not knowing about them (that was me at some point), or because the operation behind each HTTP verb needs to be specialised or simply that a single operation is needed. However, in a recent project, I got the opportunity to start a lot of new code and make the most of them. So despite having used DRF for years, I feel like I&apos;m learning a lot of new tricks.&lt;/p&gt;
&lt;p&gt;import { Callout } from &quot;../../../components/callout&quot;;&lt;/p&gt;
&lt;p&gt;&amp;lt;Callout level=&quot;note&quot;&amp;gt;
This post used to be a much shorter version as &quot;TIL&quot;, but a few days after
writing it, I found a much more elegant solution, which turned into a longer
form post. The first solution was the TIL.
&amp;lt;/Callout&amp;gt;&lt;/p&gt;
&lt;h2&gt;The problem&lt;/h2&gt;
&lt;p&gt;In a project, I needed to add multiple HTTP verbs to a nested resource in a ViewSet. Let&apos;s take an example to illustrate. Let&apos;s imagine an application to manage some galleries of photos. The galleries need to be only accessible to the user that ows them. Assuming the models look like this (simplified for brevity):&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;class Gallery(models.Model):
    user = models.ForeignKey(User, on_delete=models.CASCADE)


class Photo(models.Model):
    gallery = models.ForeignKey(Gallery, on_delete=models.CASCADE)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;I needed the following API endpoints:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;GET    /galleries/
POST   /galleries/
GET    /galleries/:id/
GET    /galleries/:id/photos/
POST   /galleries/:id/photos/
GET    /galleries/:id/photos/:photoId/
PUT    /galleries/:id/photos/:photoId/
PATCH  /galleries/:id/photos/:photoId/
DELETE /galleries/:id/photos/:photoId/
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;I already had a &lt;code&gt;ViewSet&lt;/code&gt; for the getting and creating galleries, meaning the first 3 endpoints existed. I now needed to add photos management to it. I tried to look for how to nest a &lt;code&gt;ViewSet&lt;/code&gt; inside another, but I couldn&apos;t find anything supported out of the box.&lt;/p&gt;
&lt;h2&gt;The first solution&lt;/h2&gt;
&lt;p&gt;I knew about the &lt;code&gt;@action&lt;/code&gt; decorator, and while looking closer at the DRF docs, I found out about how to route multiple HTTP verbs to an existing action, which I didn&apos;t before. This seemed pretty clean, so I decided to give it a go. I ended up with the following:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;class GalleryViewSet(viewsets.ModelViewSet):
    queryset = Gallery.objects.all()
    serializer_class = GallerySerializer
    permission_classes = (IsAuthenticated,)

    def get_queryset(self):
        return self.queryset.filter(user=self.request.user)

    @action(
        detail=True,
        methods=[&apos;get&apos;],
        url_path=&apos;photos&apos;,
        url_name=&apos;photos-list&apos;,
        serializer_class=PhotoSerializer
    )
    def list_photos(self, request):
        gallery = self.get_object()
        photos_qs = gallery.photos.all()
        serializer = self.get_serializer(photos_qs, many=True)
        return Response(serializer.data)

    @list_photos.mapping.post
    def create_photo(self, request):
        ...

    @action(
        detail=True,
        methods=[&apos;get&apos;],
        url_path=&apos;photos/(?P&amp;lt;photo_id&amp;gt;[^/.]+)&apos;,
        url_name=&apos;photos-detail&apos;,
        serializer_class=PhotoSerializer
    )
    def get_photo(self, request, pk=None):
        ...

    @get_photo.mapping.patch
    def update_photo(self, request, pk=None):
        ...

    @get_photo.mapping.delete
    def delete_photo(self, request, pk=None):
        ...
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;I liked how it was declarative and how each HTTP verb was separated into its own method. However, it&apos;s quite a lot of code, with some bits duplicated, especially each method body. I also needed to check ownership of the gallery. It felt like I was re-implementing all the code for a photo ViewSet as individual methods.&lt;/p&gt;
&lt;p&gt;Also, I didn&apos;t realise at the time, but all these actions are using the QuerySet from the base ViewSet. This is a simplified example, but in my case, the base ViewSet had several &lt;code&gt;select_related&lt;/code&gt; and &lt;code&gt;prefetch_related&lt;/code&gt; to optimise the fetching of the gallery, but these were not needed for the photo management part. So I was fetching a lot of data that I didn&apos;t need.&lt;/p&gt;
&lt;h2&gt;The second solution&lt;/h2&gt;
&lt;p&gt;After a couple of days, while not being happy with my solution, I decided to give it another go. I noticed while implementing my initial solution that I could add extra URL parameters in the &lt;code&gt;url_path&lt;/code&gt; of the &lt;code&gt;get_photo&lt;/code&gt; method, which made me think that maybe I could do the same when registering the ViewSet, and sure enough, I could! So I ended up with the following:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# views.py
class GalleryViewSet(viewsets.ModelViewSet):
    queryset = Gallery.objects.all()
    serializer_class = GallerySerializer
    permission_classes = (IsAuthenticated,)
    lookup_url_kwarg = &quot;post_id&quot;

    def get_queryset(self):
        return self.queryset.filter(user=self.request.user)

    # Note: Removed all the actions methods for photos


class PhotoViewSet(viewsets.ModelViewSet):
    queryset = Photo.objects.all()
    serializer_class = PhotoSerializer
    permission_classes = (IsAuthenticated,)
    lookup_url_kwarg = &quot;photo_id&quot;

    def get_queryset(self):
        return self.queryset.filter(gallery_id=self.kwargs[&quot;gallery_id&quot;])

    def perform_create(self, serializer):
        serializer.save(gallery_id=self.kwargs[&quot;gallery_id&quot;])



# routers.py
router = SimpleRouter()
router.register(
    &quot;galleries&quot;,
    GalleryViewSet,
    basename=&quot;gallery&quot;,
)
router.register(
    &quot;galleries/(?P&amp;lt;gallery_id&amp;gt;[^/.]+)/photos&quot;,
    PhotoViewSet,
    basename=&quot;gallery-photo&quot;,
)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;As a bonus, I added a &lt;code&gt;lookup_url_kwarg&lt;/code&gt; to both ViewSets, to make the URL parameters more consistent and self-explanatory in the API docs. This solution is much more DRY and use the full power of DRF. I was pretty satisfied with it.&lt;/p&gt;
&lt;h2&gt;Bug: access control&lt;/h2&gt;
&lt;p&gt;However, I soon realised that this solution had a bug. The &lt;code&gt;get_queryset&lt;/code&gt; method of the &lt;code&gt;PhotoViewSet&lt;/code&gt; was no longer checking the ownership of the gallery! This meant that a user could access photos from galleries that they don&apos;t own, and even create photos in them! This was a big no-no. I tried to write a simple method to fetch the gallery and check ownership, but I wanted to do it pretty early and for all methods, including &lt;code&gt;create()&lt;/code&gt;. I thought of overriding the &lt;code&gt;dispatch&lt;/code&gt; method, but it felt like there wasn&apos;t a good hook for that.&lt;/p&gt;
&lt;p&gt;After a bit of thinking, I realised that I needed to control the user had permissions to access the gallery, and DRF has a built-in solution for that, with permissions classes. I implemented my own permission class, which looked like this:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;class UserOwnsGallery(permissions.BasePermission):

    def has_permission(self, request, view):
        gallery_id = view.kwargs.get(&quot;gallery_id&quot;)
        if gallery_id is None:
            return False

        get_object_or_404(
            Gallery,
            id=gallery_id,
            user_id=request.user.id,
        )
        return True
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If the user doesn&apos;t own the gallery, I&apos;m returning a HTTP 404 as I think it&apos;s a security best practice. If I were to return &quot;403 Forbidden&quot; instead, I would reveal some information to a potential attacker, that the gallery with the ID exists. It&apos;s probably not a big risk in our application, but it&apos;s best to be safe.&lt;/p&gt;
&lt;p&gt;And here is how I use it on my ViewSet:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;class PhotoViewSet(viewsets.ModelViewSet):
    queryset = Photo.objects.all()
    serializer_class = PhotoSerializer
    permission_classes = (
        IsAuthenticated,
        UserOwnsGallery,
    )
    lookup_url_kwarg = &quot;photo_id&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;I&apos;m pretty happy with the final solution. It&apos;s DRY, it&apos;s using the full power of DRF, and it&apos;s secure. I&apos;m not sure if it&apos;s the best solution, but it&apos;s probably very close from it. I hope this article will help you if you&apos;re in a similar situation.&lt;/p&gt;
</content:encoded></item><item><title>Migrate my site design system to Chakra UI with ChatGPT</title><link>https://browniebroke.com/blog/2023-04-27-migrate-my-site-design-system-to-chakra-ui-with-chatgpt/</link><guid isPermaLink="true">https://browniebroke.com/blog/2023-04-27-migrate-my-site-design-system-to-chakra-ui-with-chatgpt/</guid><description>How I used Chat GPT to take on this endeavour and how it went</description><pubDate>Thu, 27 Apr 2023 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;A short story on how I migrated this site from a custom design system to Chakra UI with the help of ChatGPT. Wasn&apos;t a complicated migration, but AI was a great help to get it done quicker and discover their great documentation.&lt;/p&gt;
&lt;h1&gt;Initial state&lt;/h1&gt;
&lt;p&gt;Before I started, this site was using a custom design system based on &lt;a href=&quot;https://styled-components.com&quot;&gt;styled-components&lt;/a&gt;. It was a small library of components that I had created for a few projects almost 3 years ago, in 2020. Back then, the world was in lockdown, and I had lots of time at home to toy around some fun little projects. I also wanted to work on my CSS skills and didn&apos;t need many components, but I needed them on a few projects. Instead of copying them around, I decided to created my own mini design system with &lt;a href=&quot;https://www.npmjs.com/package/@browniebroke/react-ui-components&quot;&gt;a small React component library&lt;/a&gt;. This site was one of the first to use it.&lt;/p&gt;
&lt;h1&gt;Problems&lt;/h1&gt;
&lt;p&gt;Creating a design system requires to have some basic knowledge in design theory and typography to get spacing right while giving flexibility to the consumers of the library. I didn&apos;t have that knowledge back then, and I still don&apos;t have it now. I did some research and learnt by taking some inspirations in other projects to learn, but it was and remain a very basic design system. It looked ok, but I&apos;m sure someone more proficient in design would spot a lot of problems.&lt;/p&gt;
&lt;p&gt;I migrated the 3 or 4 sites I had to use that library and didn&apos;t touch it since then. Recently (~2 years later), I wanted to add a new section to this site, with a different layout. Shortly after I started, I realised that I didn&apos;t remember the syntax my own self created a few years back, and it was barely documented in my design system. Sure, I could work it out by looking at the code, but it didn&apos;t work when I tried: this site wasn&apos;t configured exactly how the library was expecting. Basically, it was as if I was using someone else&apos;s broken library, and it wasn&apos;t fun anymore!&lt;/p&gt;
&lt;h1&gt;Solution&lt;/h1&gt;
&lt;p&gt;Writing my own design system was a fun exercise, but it was time to move on. I wanted to use a mature library, with good documentation, widely used in the React community and which worked with my toolchain (Gatsby.js). I heard a few times of &lt;a href=&quot;https://chakra-ui.com&quot;&gt;Chakra UI&lt;/a&gt;, and it seemed to tick all the boxes, and a lot more. I used to see &quot;doing too much&quot; as a drawback, but today it&apos;s no longer the case, I just want something that works.&lt;/p&gt;
&lt;p&gt;On top of the requirements I needed, I also got sold on these features specifically:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Themeable design system&lt;/li&gt;
&lt;li&gt;CSS in JS&lt;/li&gt;
&lt;li&gt;Provides Atomic components as well as complex ones&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Challenges&lt;/h2&gt;
&lt;p&gt;However, migrating came with its own set of challenges. The New syntax, different from styled-components, would take some time to get used to. The library, with all its component was a bit overwhelming at first, it looked like there was a component for everything, and like many components could be used to do the same thing.&lt;/p&gt;
&lt;h2&gt;How AI can help&lt;/h2&gt;
&lt;p&gt;That challenge seemed like a perfect job for ChatGPT: lots of data to crunch though, lots of content online to back it up. Let&apos;s go!&lt;/p&gt;
&lt;h1&gt;Migration&lt;/h1&gt;
&lt;h2&gt;Component level&lt;/h2&gt;
&lt;p&gt;I decided to start with some self-contained components that I wrote myself. Just asked to convert from styled-components to Chakra UI with the code below and it spitted out pretty quickly a first version.&lt;/p&gt;
&lt;h3&gt;Input:&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;import styled from &quot;styled-components&quot;;

export const PostMetaData = styled.div`
  display: flex;
  flex-direction: column;
  justify-content: space-between;
  align-items: center;
  margin-bottom: 1rem;

  @media (min-width: 400px) {
    flex-direction: row;
  }

  div {
    margin-bottom: 0.5rem;
  }
`;
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Output:&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;import { Box, useBreakpointValue } from &quot;@chakra-ui/react&quot;;

export const PostMetaData = (props) =&amp;gt; {
  const flexDirection = useBreakpointValue({ base: &quot;column&quot;, md: &quot;row&quot; });

  return (
    &amp;lt;Box
      display=&quot;flex&quot;
      flexDirection={flexDirection}
      justifyContent=&quot;space-between&quot;
      alignItems=&quot;center&quot;
      marginBottom=&quot;1rem&quot;
      {...props}
    &amp;gt;
      {React.Children.map(props.children, (child) =&amp;gt; (
        &amp;lt;Box marginBottom=&quot;0.5rem&quot;&amp;gt;{child}&amp;lt;/Box&amp;gt;
      ))}
    &amp;lt;/Box&amp;gt;
  );
};
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;It didn&apos;t produce totally equivalent code, it actually looked better with the breakpoints it gave me. I didn&apos;t really need to deal with children, but by handling this case that, it showed me some flaws in my original design. Off to a great start! I tweaked a bit its output and moved on to more transformations. Some were pretty trivial and barely needed any changes.&lt;/p&gt;
&lt;p&gt;When a new Chakra component showed up, I looked in their documentation to learn about it and how it could be used. I also looked at the code it produced to see how it was used. Repeating this, I quickly discovered the main components I needed and after a few migrations I felt like I could write them myself, but it was still faster to ask the AI.&lt;/p&gt;
&lt;h2&gt;Limitation&lt;/h2&gt;
&lt;p&gt;One migration didn&apos;t work too well, it was the &lt;code&gt;Avatar&lt;/code&gt; component of this site. Here is the original version:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;const AvatarStyles = styled.div`
  padding: 1rem;

  img {
    padding: 5px;
    border: 1px solid ${(props) =&amp;gt; props.theme.grey};
    border-radius: 50%;
  }
`

export const Avatar: React.FC = () =&amp;gt; (
  &amp;lt;AvatarStyles&amp;gt;
    &amp;lt;StaticImage
      src=&quot;../assets/avatar.jpg&quot;
      alt=&quot;Picture of Bruno&quot;
      placeholder=&quot;blurred&quot;
      layout=&quot;fixed&quot;
      width={160}
      height={160}
    /&amp;gt;
  &amp;lt;/AvatarStyles&amp;gt;
)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The &lt;code&gt;AvatarStyles&lt;/code&gt; was using a nested rule for an &lt;code&gt;img&lt;/code&gt; tag which is inside the &lt;code&gt;StaticImage&lt;/code&gt; component. The CSS rule was targeting a non-direct descendant, which ChatGTP initially wrote using an underscore-prefixed prop &lt;code&gt;_img&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;export const Avatar: React.FC = () =&amp;gt; (
  &amp;lt;Box
    padding=&quot;1rem&quot;
    _img={{
      padding: &apos;5px&apos;,
      border: &apos;1px solid&apos;,
      borderColor: &apos;gray.500&apos;, // Adjust the color according to your theme
      borderRadius: &apos;50%&apos;,
    }}
  &amp;gt;
    &amp;lt;StaticImage
      src=&quot;../assets/avatar.jpg&quot;
      alt=&quot;Picture of Bruno&quot;
      placeholder=&quot;blurred&quot;
      layout=&quot;fixed&quot;
      width={160}
      height={160}
    /&amp;gt;
  &amp;lt;/Box&amp;gt;
);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;After telling it that it doesn&apos;t work, it tried something else, but just failed to produce an output that worked. It seems that this might be an old syntax and the AI didn&apos;t know the newer syntax from the version I used. I eventually landed on this:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;export const Avatar: React.FC = () =&amp;gt; (
  &amp;lt;Box
    padding={4}
    sx={{
      img: {
        borderRadius: &apos;50%&apos;,
        padding: &apos;var(--chakra-space-1)&apos;,
        border: &apos;1px solid&apos;,
        borderColor: &apos;gray.500&apos;,
      },
    }}
  &amp;gt;
    &amp;lt;StaticImage
      src=&quot;../assets/avatar.jpg&quot;
      alt=&quot;Picture of Bruno&quot;
      placeholder=&quot;blurred&quot;
      layout=&quot;fixed&quot;
      width={160}
      height={160}
    /&amp;gt;
  &amp;lt;/Box&amp;gt;
)
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Theme / global styles&lt;/h2&gt;
&lt;p&gt;The theme is done via a Javascript object, passed to the &lt;code&gt;ChakraProvider&lt;/code&gt; component wrapping the whole app. The global styles are included automatically from there. Pretty straightforward to use. The thing I really liked is that everything is in one place, in my theme object, while before, I had things split between my theme and global styles, and they were 2 separate files.&lt;/p&gt;
&lt;p&gt;What took me a bit of time to figure out what keys/values are available. Once I understood that keys are mapped to CSS variables, it clicked and I could find what to customise by looking at the CSS variables in the browser.&lt;/p&gt;
&lt;h1&gt;Results&lt;/h1&gt;
&lt;p&gt;The resulting &lt;a href=&quot;https://github.com/browniebroke/browniebroke.com/pull/1125&quot;&gt;pull request&lt;/a&gt; is huge, but ChatGPT helped my and it was actually pretty painless. I think the design looks very close, but is overall improved. If/when I need something new, I have plenty of battle-tested components to choose from. I also ran Lighthouse to make sure there wasn&apos;t any performance regression, and it actually improved a bit.&lt;/p&gt;
&lt;p&gt;So overall, really happy with that migration, and I&apos;m looking forward to using Chakra UI in my next projects.&lt;/p&gt;
</content:encoded></item><item><title>Enabling sudo via TouchID using Ansible</title><link>https://browniebroke.com/blog/2022-11-25-enabling-sudo-via-touchid-using-ansible/</link><guid isPermaLink="true">https://browniebroke.com/blog/2022-11-25-enabling-sudo-via-touchid-using-ansible/</guid><description>A small post explaining how to integrate sudo with Touch ID on Mac.</description><pubDate>Fri, 25 Nov 2022 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;&lt;a href=&quot;/tils/sudo-with-touchid-on-macos/&quot;&gt;I discovered&lt;/a&gt; fairly recently in &lt;a href=&quot;https://it.digitaino.com/use-touchid-to-authenticate-sudo-on-macos/&quot;&gt;a blog post&lt;/a&gt; that it was possible to enable sudo mode in the terminal via Touch ID instead of having to type my password. I&apos;m glad I wrote a TIL about it because I was able to find it again very easily and do it again on a new machine.&lt;/p&gt;
&lt;p&gt;Today, I went a step further and integrated this into &lt;a href=&quot;/tils/provisioning-my-mac-with-ansible/&quot;&gt;my Ansible playbook&lt;/a&gt; that I use to provision my machine. This was a pretty simple action which made me learn a bit more about Ansible. If you&apos;re in a rush, &lt;a href=&quot;https://github.com/browniebroke/mac-ansible/commit/0235430ab45f5519aa97d3df317d3a61a405c546&quot;&gt;here is the commit&lt;/a&gt; with the solution I ended up with:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# Do sudo using Touch ID
- name: Set content of sudo file as fact
  set_fact:
    sudo_conf: &quot;{{ lookup(&apos;file&apos;, &apos;/etc/pam.d/sudo&apos;) }}&quot;
  ignore_errors: yes
  tags:
    - touchid

- name: Set sudo via Touch ID if not setup
  become: yes
  command: sed -i &apos;&apos; &apos;s/auth       sufficient     pam_smartcard.so/auth       sufficient     pam_smartcard.so\nauth       sufficient     pam_tid.so/g&apos; /etc/pam.d/sudo
  when: &quot;&apos;auth       sufficient     pam_tid.so&apos; not in sudo_conf&quot;
  tags:
    - touchid
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Here is the breakdown of what&apos;s going on, with the key learning:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;I use the &lt;code&gt;set_fact&lt;/code&gt; module to read the content of the file and store it in a &lt;code&gt;sudo_conf&lt;/code&gt; variable.&lt;/li&gt;
&lt;li&gt;The following task uses the &lt;code&gt;when&lt;/code&gt; condition to check if the &lt;code&gt;sudo_conf&lt;/code&gt; variable (and therefore the file) already contains the required string. If it does, it will not run the task.&lt;/li&gt;
&lt;li&gt;If the string isn&apos;t present, I use &lt;code&gt;sed&lt;/code&gt; to add it in the right place.&lt;/li&gt;
&lt;li&gt;Use &lt;code&gt;sed&lt;/code&gt;&apos;s &lt;code&gt;-i&lt;/code&gt; option to edit the file in place. The version of &lt;code&gt;sed&lt;/code&gt; shipping with macOS has a quirk and needs an empty string to edit in place (means no suffix). An alternative is to &lt;code&gt;brew install gnu-sed&lt;/code&gt; and use &lt;code&gt;gsed -i&lt;/code&gt; without the following empty string.&lt;/li&gt;
&lt;li&gt;Use &lt;code&gt;become: yes&lt;/code&gt; to run the command as root.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;And that&apos;s it, short and sweet. I&apos;m not sure if this is the best way to do it, but it works and I learned a bit more about Ansible.&lt;/p&gt;
</content:encoded></item><item><title>Specify docs dependency groups with Poetry and Read the Docs</title><link>https://browniebroke.com/blog/2022-11-21-specify-docs-dependency-groups-with-poetry-and-read-the-docs/</link><guid isPermaLink="true">https://browniebroke.com/blog/2022-11-21-specify-docs-dependency-groups-with-poetry-and-read-the-docs/</guid><description>Guide on how to setup your package on Read the Docs with Poetry&apos;s dependency groups.</description><pubDate>Mon, 21 Nov 2022 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;I wrote a while back on how to &lt;a href=&quot;/blog/migrating-project-to-poetry/&quot;&gt;migrate a project to Poetry&lt;/a&gt;. One of the gotcha was how to specify the dependencies to build the project documentation, especially on Read the Docs. The solution, at the time, was to specify these dependencies as &quot;extras&quot;, which had the unintended side effects of being visible to your users, while only needed for internal purposes. By that, I mean that the package could be installed with &lt;code&gt;pip install package-name[docs]&lt;/code&gt;.&lt;/p&gt;
&lt;h2&gt;Dependency groups&lt;/h2&gt;
&lt;p&gt;Poetry 1.2 introduced a new feature which I have been waiting for a while: &lt;a href=&quot;https://python-poetry.org/docs/managing-dependencies/#dependency-groups&quot;&gt;dependency groups&lt;/a&gt;. Until recently, the only groups were the main dependencies (the one that your package needs to run) and the development dependencies (the ones that are only needed for development, like testing or linting).&lt;/p&gt;
&lt;p&gt;With dependency groups, you can now specify any arbitrary group of dependencies, and use them in any way you want. they can be optional or not.&lt;/p&gt;
&lt;p&gt;This help us solve my previous problem, now I can add an optional &lt;code&gt;docs&lt;/code&gt; dependency group and use it to build the documentation. I use &lt;a href=&quot;https://myst-parser.readthedocs.io&quot;&gt;MyST parser&lt;/a&gt; with &lt;a href=&quot;https://www.sphinx-doc.org&quot;&gt;Sphinx&lt;/a&gt;, so here is how it might look like:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;[tool.poetry.group.docs]
optional = true

[tool.poetry.group.docs.dependencies]
myst-parser = &quot;&amp;gt;=0.16&quot;
sphinx = &quot;&amp;gt;=4.0&quot;
sphinx-autobuild = &quot;&amp;gt;=2021.0&quot;
sphinx-rtd-theme = &quot;&amp;gt;=1.0&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now I can install my docs dependencies with:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;poetry install --with docs
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And no extras are added to my package. Nice.&lt;/p&gt;
&lt;h2&gt;Read the Docs&lt;/h2&gt;
&lt;p&gt;The other piece of the puzzle is to get my documentation online. I usually do this using Read the Docs. They have good support for installing the package being documented with extras, but they don&apos;t have Poetry installed, so it needs a bit of customisation. The good news is that their config file is flexible enough to allow it. The example for Poetry was just a bit out-of-date with the recent Poetry 1.2 release. I sent &lt;a href=&quot;https://github.com/readthedocs/readthedocs.org/pull/9743&quot;&gt;a pull request&lt;/a&gt; to fix it, but came up with a suboptimal solution, which one of the maintainer helped me improve. The updated version will be published soon, but here is what I ended up with:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;version: 2

build:
  os: ubuntu-20.04
  tools:
    python: &quot;3.9&quot;
  jobs:
    post_create_environment:
      # Install poetry
      - pip install poetry
      # Tell poetry to not use a virtual environment
      - poetry config virtualenvs.create false
    post_install:
      # Install dependencies
      - poetry install --with docs

sphinx:
  configuration: docs/source/conf.py
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The main trick is to configure Poetry to not create virtual environment, and then pass the &lt;code&gt;poetry install&lt;/code&gt; command form the previous section with docs group as &lt;code&gt;post_install&lt;/code&gt;. RTD doesn&apos;t let you customise the installation step, and it may install some older version of Sphinx, so by setting as &lt;code&gt;post_install&lt;/code&gt;, you&apos;re sure to get what you ask for. This last bit shouldn&apos;t be a problem unless your RTD project was created a long time ago.&lt;/p&gt;
&lt;h2&gt;Closing words&lt;/h2&gt;
&lt;p&gt;I hope this helps you if you&apos;re using Poetry and Read the Docs. If you have any questions, feel free to reach out to me on &lt;a href=&quot;https://fosstodon.org/@browniebroke&quot;&gt;Mastodon&lt;/a&gt; or &lt;a href=&quot;https://twitter.com/browniebroke&quot;&gt;Twitter&lt;/a&gt;.&lt;/p&gt;
</content:encoded></item><item><title>Convert a Poetry package to the src layout</title><link>https://browniebroke.com/blog/2022-01-19-convert-existing-poetry-to-src-layout/</link><guid isPermaLink="true">https://browniebroke.com/blog/2022-01-19-convert-existing-poetry-to-src-layout/</guid><description>Quick run down on the steps required to convert a Python package using Poetry to the src layout.</description><pubDate>Wed, 19 Jan 2022 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;The &lt;code&gt;src&lt;/code&gt; layout is commonly used in Python ecosystem nowadays (even &lt;a href=&quot;https://docs.pytest.org/en/6.2.x/goodpractices.html#tests-outside-application-code&quot;&gt;the pytest docs&lt;/a&gt; recommend it), but I&apos;m not really using it on my projects. I think I&apos;ve never hit the pain points it solves, and last time I tried, it made my development workflow more complicated.&lt;/p&gt;
&lt;p&gt;I&apos;m not going into the whys, they are detailed in &lt;a href=&quot;https://blog.ionelmc.ro/2014/05/25/python-packaging/&quot;&gt;this excellent blog&lt;/a&gt; explaining its rationale in detail, where I read about it the first time.&lt;/p&gt;
&lt;p&gt;In recent years, as it became more popular, the tooling has caught up, and I wanted to try it again to see if the situation improved.&lt;/p&gt;
&lt;h2&gt;Starting a new project&lt;/h2&gt;
&lt;p&gt;If you start a new project, Poetry supports it out of the box in &lt;a href=&quot;https://python-poetry.org/docs/cli/#new&quot;&gt;their &lt;code&gt;poetry new&lt;/code&gt; command&lt;/a&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;poetry new --src my-package
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Although you might want to use a full blown template with other tools also configured (e.g. pytest).&lt;/p&gt;
&lt;h2&gt;Converting a project&lt;/h2&gt;
&lt;p&gt;I found it a bit less well documented how to convert a project, so here you go. Assuming the project uses Poetry, pytest and a layout as follows:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;my-package
├── poetry.lock
├── pyproject.toml
├── my_package
│   ├── __init__.py
│   └── main.py
└── tests
    ├── __init__.py
    └── test_main.py
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Here are the steps I followed to convert to &lt;code&gt;src/&lt;/code&gt; layout, without changing the imports in my tests:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Create a &lt;code&gt;src/&lt;/code&gt; &lt;strong&gt;folder&lt;/strong&gt;. And I really mean folder, not package: do not create a &lt;code&gt;__init__.py&lt;/code&gt; in that folder.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Move &lt;code&gt;my_package&lt;/code&gt; into &lt;code&gt;src/&lt;/code&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Change you &lt;code&gt;packages&lt;/code&gt; section in your &lt;code&gt;pyproject.toml&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;  packages = [
-     { include = &quot;my_package&quot; },
+     { include = &quot;my_package&quot;, from = &quot;src&quot; },
  ]
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Add &lt;code&gt;pythonpath&lt;/code&gt; to your pytest option as follows:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;  [tool.pytest.ini_options]
+ pythonpath = [&quot;src&quot;]
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If using pytest version lower than 7, you&apos;ll also need to install &lt;code&gt;pytest-srcpaths&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;poetry add -D pytest-srcpaths
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;If your &lt;code&gt;pyproject.toml&lt;/code&gt; references some paths, make sure to update them, e.g python-semantic-release:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;  [tool.semantic_release]
  branch = &quot;main&quot;
- version_variable = &quot;my_package/__init__.py:__version__&quot;
+ version_variable = &quot;src/my_package/__init__.py:__version__&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;If everything went well, you should have the following layout and running &lt;code&gt;pytest&lt;/code&gt; should work:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;my-package
├── poetry.lock
├── pyproject.toml
├── src
│   └── my_package
│       ├── __init__.py
│       └── main.py
└── tests
    ├── __init__.py
    └── test_main.py
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;This was much easier than I remembered! I think I&apos;ll change all my projects to this layout, having this guide will help me reprodude it. Hopefulluy it&apos;ll help you as well!&lt;/p&gt;
</content:encoded></item><item><title>Convert Python requirements to Poetry format</title><link>https://browniebroke.com/blog/2021-01-07-convert-requirements-to-pyproject/</link><guid isPermaLink="true">https://browniebroke.com/blog/2021-01-07-convert-requirements-to-pyproject/</guid><description>Automate your migration to Poetry with dephell.</description><pubDate>Thu, 07 Jan 2021 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;I &lt;a href=&quot;../migrating-project-to-poetry/&quot;&gt;recently wrote&lt;/a&gt; about migrating a Python project to &lt;a href=&quot;https://python-poetry.org/&quot;&gt;Poetry&lt;/a&gt;, however, this guide is quite manual process. I recently came across &lt;a href=&quot;https://dephell.readthedocs.io/cmd-deps-convert.html&quot;&gt;dephell&lt;/a&gt;, which seems to be able to automate some part of it. I wanted to check out how it might help for this follow-up post.&lt;/p&gt;
&lt;h2&gt;Migrate setup.py stuff&lt;/h2&gt;
&lt;p&gt;First step is to migrate meta-data and direct dependencies specified in &lt;code&gt;setup.py&lt;/code&gt; file:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;dephell deps convert \
  --from=setup.py \
  --to-format=poetry \
  --to-path=pyproject.toml
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This converts some package metadata and add the dependencies which are listed in your &lt;code&gt;setup.py&lt;/code&gt;.&lt;/p&gt;
&lt;h2&gt;Convert development dependencies&lt;/h2&gt;
&lt;p&gt;The development dependencies are located in &lt;code&gt;requirements.in&lt;/code&gt; (as I used &lt;a href=&quot;https://github.com/jazzband/pip-tools&quot;&gt;pip-tools&lt;/a&gt;), and to not overwrite the dependencies from the previous step, let&apos;s write them to a temporary file:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;dephell deps convert \
  --from=requirements.in \
  --to-format=poetry \
  --to-path=pyproject-dev.toml
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Create a new section for development dependencies in &lt;code&gt;pyproject.toml&lt;/code&gt; and move over dependencies from &lt;code&gt;pyproject-dev.toml&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;[tool.poetry.dev-dependencies]
# copy deps from pyproject-dev.toml here
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now you can get rid of the temporary &lt;code&gt;pyproject-dev.toml&lt;/code&gt; file.&lt;/p&gt;
&lt;h2&gt;Finish&lt;/h2&gt;
&lt;p&gt;As far as I&apos;m aware, that is as far as this tool will get you. The &lt;code&gt;pyproject.toml&lt;/code&gt; file should almost be ready for Poetry, you can try running &lt;code&gt;poetry install&lt;/code&gt; and fix the remaining issues.&lt;/p&gt;
&lt;p&gt;In my case, here are the things I needed to adjust:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;the minimum Python version wasn&apos;t properly detected, it was just &lt;code&gt;&apos;*&apos;&lt;/code&gt; despite my &lt;code&gt;setup.cfg&lt;/code&gt; having &lt;code&gt;python_requires &amp;gt;= 3.6&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Not all metadata were converted, I had to finish them manually.&lt;/li&gt;
&lt;li&gt;Some extra classifiers were included although Poetry can generate some of them automatically.&lt;/li&gt;
&lt;li&gt;Other tools configured via &lt;code&gt;setup.cfg&lt;/code&gt; which I wanted to migrate to &lt;code&gt;pyproject.toml&lt;/code&gt; are not migrated by this tool, it&apos;s -understandably- out of its scope.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Nevertheless, this tool could speed up migration for projects with lots of dependencies.&lt;/p&gt;
</content:encoded></item><item><title>Migrating a project to Poetry</title><link>https://browniebroke.com/blog/2020-10-18-migrating-project-to-poetry/</link><guid isPermaLink="true">https://browniebroke.com/blog/2020-10-18-migrating-project-to-poetry/</guid><description>How I migrated a Python project to Poetry.</description><pubDate>Sun, 18 Oct 2020 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;&lt;a href=&quot;https://python-poetry.org/&quot;&gt;Poetry&lt;/a&gt; is a tool solving the problem of Python packaging. It was started back in February 2018 by Sébastien Eustace (also the author of &lt;a href=&quot;https://pendulum.eustace.io/&quot;&gt;pendulum&lt;/a&gt;). It has a beautiful website and an ambitious headline:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Python packaging and dependency management made easy&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;It has been on my radar for a while, but I never gave it a proper go. I was happily using &lt;a href=&quot;https://github.com/jazzband/pip-tools&quot;&gt;pip-tools&lt;/a&gt;, which was solving my main use case, while being a lot more lightweight and meant I could keep working with pip since its output is a good old &lt;code&gt;requirements.txt&lt;/code&gt;. I heard about it a few times online, often next to &lt;a href=&quot;https://pipenv.pypa.io&quot;&gt;Pipenv&lt;/a&gt;, but recently it looks like Poetry got a bit more traction.&lt;/p&gt;
&lt;p&gt;Having a bit of time on my hands, a few weeks ago I decided to take a proper look at it and maybe migrate one of my projects, &lt;a href=&quot;https://deezer-python.readthedocs.io&quot;&gt;Deezer Python&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;Starting point&lt;/h2&gt;
&lt;p&gt;Before the migration, here is how the package was managed:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Project metadata were in &lt;code&gt;setup.cfg&lt;/code&gt;, using &lt;a href=&quot;https://setuptools.readthedocs.io/en/latest/setuptools.html#setup-cfg-only-projects&quot;&gt;setuptools declarative config&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;Development (and documentation) dependencies managed by &lt;a href=&quot;https://github.com/jazzband/pip-tools&quot;&gt;pip-tools&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;Documentation hosted on Read the Docs (RTD).&lt;/li&gt;
&lt;li&gt;Releases automated with &lt;a href=&quot;https://python-semantic-release.readthedocs.io&quot;&gt;Python Semantic Release&lt;/a&gt; (PSR) on Github Actions.&lt;/li&gt;
&lt;li&gt;Development tools configured in &lt;code&gt;setup.cfg&lt;/code&gt; (black, isort, pyupgrade, flake8).&lt;/li&gt;
&lt;li&gt;Using &lt;code&gt;tox&lt;/code&gt; to help local testing.&lt;/li&gt;
&lt;li&gt;Using Github Actions for CI.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;These are a couple of features that were impacted by migrating to Poetry, and I think it&apos;s worth mentioning them for context. Since Poetry uses &lt;code&gt;pyproject.toml&lt;/code&gt;, I was also hoping to move all my tools config from &lt;code&gt;setup.cfg&lt;/code&gt; to that file.&lt;/p&gt;
&lt;h2&gt;Migration&lt;/h2&gt;
&lt;p&gt;&lt;em&gt;Update: I recently came across &lt;a href=&quot;https://dephell.readthedocs.io/cmd-deps-convert.html&quot;&gt;the &lt;code&gt;dephell&lt;/code&gt; tool&lt;/a&gt; which seems to automate some conversions check out &lt;a href=&quot;../convert-requirements-to-pyproject/&quot;&gt;the follow-up post&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;
&lt;h3&gt;Installing Poetry&lt;/h3&gt;
&lt;p&gt;The first step is to get the CLI. I initially installed it via Homebrew, but later realised that Poetry was setting some default values based on the Python version its installation uses. As the Homebrew Python can be updated without notice, I realised it was not the best option here, so I later reinstalled it via &lt;code&gt;pipx&lt;/code&gt; using a Python installation managed by pyenv that wouldn&apos;t be wiped without my knowledge:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;pipx install \
 --python ~/.pyenv/versions/3.8.6/bin/python \
 poetry
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Project metadata&lt;/h3&gt;
&lt;p&gt;The first step was to migrate the project metadata from &lt;code&gt;setup.cfg&lt;/code&gt; to &lt;code&gt;pyproject.toml&lt;/code&gt;. Poetry comes with a handy interactive command &lt;code&gt;poetry init&lt;/code&gt; which will create a minimal &lt;code&gt;pyproject.toml&lt;/code&gt; for you. I already noticed a few pleasant surprises:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The CLI was very nice to interact with&lt;/li&gt;
&lt;li&gt;The &lt;code&gt;author&lt;/code&gt; and &lt;code&gt;author_email&lt;/code&gt; from &lt;code&gt;setup.cfg&lt;/code&gt; were merged into an array of &lt;code&gt;authors&lt;/code&gt;, each with the format &lt;code&gt;Full Name &amp;lt;email@address.com&amp;gt;&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I then went on to convert more settings into the new format manually, and the process was quite painless. Many settings have the same name and values, and when they are different, it&apos;s mainly to simplify things. I guess it&apos;s something a library like setuptools cannot easily afford to do due to backwards compatibility, but that a new opt-in tool like Poetry can.&lt;/p&gt;
&lt;h3&gt;Dependencies&lt;/h3&gt;
&lt;p&gt;Adding dependencies and development dependencies was pretty simple, I just needed to run &lt;code&gt;poetry add [-D] ...&lt;/code&gt; with the list of packages at the end.&lt;/p&gt;
&lt;p&gt;In this process, I discovered that one of the development dependencies, &lt;a href=&quot;https://github.com/asottile/scratch/wiki/python-3-statement#360&quot;&gt;&lt;code&gt;pyupgrade&lt;/code&gt; is not compatible with Python 3.6.0&lt;/a&gt;: Poetry would not let me set my own Python to &lt;code&gt;^3.6&lt;/code&gt;. I initially changed my minimum version to &lt;code&gt;^3.6.1&lt;/code&gt; as a quick fix, but I realised later that it impacted my package&apos;s trove classifiers, the ones generated by Poetry: my package wasn&apos;t listed as Python 3.6 compatible. Since this is just for a development dependency, there is a better way! The fix is to specify the dependency conditional to the Python version:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;[tool.poetry.dependencies]
python = &quot;^3.6&quot;
...

[tool.poetry.dev-dependencies]
...
pyupgrade = { version = &quot;^2.7.3&quot;, python = &quot;^3.6.1&quot; }
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;With this, the minimum Python version of my package and its classifiers are correct.&lt;/p&gt;
&lt;h3&gt;Extra Dependencies&lt;/h3&gt;
&lt;p&gt;This package had an &quot;extra require&quot; dependency. There is a good &lt;a href=&quot;https://python-poetry.org/docs/pyproject/#extras&quot;&gt;example in the documentation&lt;/a&gt;, these dependencies need to be specified in the same section as normal dependencies, but as &lt;code&gt;optional&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;[tool.poetry.dependencies]
...
tornado = {version = &quot;^6.0.4&quot;, optional = true}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And a dedicated &lt;code&gt;pyproject.toml&lt;/code&gt; section maps the extras to an array of optional dependencies:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;[tool.poetry.extras]
tornado = [&quot;tornado&quot;]
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;I got confused initially because I thought it was done via the &lt;code&gt;poetry add ... -E ...&lt;/code&gt; command. However, it does something different, it&apos;s for the extras of the dependency, not the extras of my own library the dependency should fall under.&lt;/p&gt;
&lt;p&gt;For example Django has &lt;a href=&quot;https://github.com/django/django/blob/0eee5c1b9c2e306aa2c2807daf146ee88676bc97/setup.cfg#L52-L54&quot;&gt;2 possible extras&lt;/a&gt; at the moment, so one would run the following command to use them:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;poetry add Django -E argon2
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;It means &quot;install Django with the &lt;code&gt;argon2&lt;/code&gt; extra&quot;. I thought it meant &quot;install Django and put it under the &lt;code&gt;argon2&lt;/code&gt; extra&quot;.&lt;/p&gt;
&lt;h3&gt;Docs dependencies&lt;/h3&gt;
&lt;p&gt;The dependencies to build the docs were specified in a &lt;code&gt;requirements.txt&lt;/code&gt; in the &lt;code&gt;docs/&lt;/code&gt; folder and RTD was configured to pick this up. I initially thought that I wouldn&apos;t be able to remove that file, but it turns out &lt;a href=&quot;https://github.com/readthedocs/readthedocs.org/issues/4912&quot;&gt;it&apos;s possible to make it work&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Thanks to &lt;a href=&quot;https://www.python.org/dev/peps/pep-0517/&quot;&gt;PEP 517&lt;/a&gt;, which &lt;a href=&quot;https://python-poetry.org/docs/pyproject/#poetry-and-pep-517&quot;&gt;Poetry is compliant with&lt;/a&gt;, you can do &lt;code&gt;pip install .&lt;/code&gt; in a Poetry package. This has been in pip since 19.0, and pip running on RTD is newer than this. However, this method wouldn&apos;t install your development dependencies, so your docs dependencies cannot be specified as such. It works if you specify a &lt;code&gt;docs&lt;/code&gt; extra, though:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# pyproject.toml
[tool.poetry.dependencies]
...
myst-parser = {version = &quot;^0.12&quot;, optional = true}
sphinx = {version = &quot;^3&quot;, optional = true}
sphinx-autobuild = {version = &quot;^2020.9.1&quot;, optional = true}
sphinx-rtd-theme = {version = &quot;^0.5&quot;, optional = true}

[tool.poetry.extras]
...
docs = [
    &quot;myst-parser&quot;,
    &quot;sphinx&quot;,
    &quot;sphinx-autobuild&quot;,
    &quot;sphinx-rtd-theme&quot;,
]
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;# readthedocs.yml
version: 2
python:
  install:
    - method: pip
      path: .
      extra_requirements:
        - docs
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The downside of this, is that the extra is part of your package, so not ideal.&lt;/p&gt;
&lt;h2&gt;Releases&lt;/h2&gt;
&lt;p&gt;I recently moved the automation of releases to &lt;a href=&quot;https://python-semantic-release.readthedocs.io&quot;&gt;Python Semantic Release&lt;/a&gt; which worked well for me, and this would have been a blocker if it hadn&apos;t worked. These are the pieces I needed:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Move its config to come from &lt;code&gt;pyproject.toml&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Package version is specified in &lt;code&gt;pyproject.toml&lt;/code&gt; as well as in &lt;code&gt;__init__.py&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Update build command to use Poetry instead of setuptools.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Here is the PSR config in &lt;code&gt;pyproject.toml&lt;/code&gt; to achieve that:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;[tool.semantic_release]
version_variable = [
    &quot;deezer/__init__.py:__version__&quot;,
    &quot;pyproject.toml:version&quot;
]
build_command = &quot;pip install poetry &amp;amp;&amp;amp; poetry build&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;I was expecting to have to change more, but it all worked out of the box with just that.&lt;/p&gt;
&lt;h2&gt;Linting and code formatting&lt;/h2&gt;
&lt;p&gt;All the tools I use for linting and code formatting were configured via &lt;code&gt;setup.cfg&lt;/code&gt; and ideally I&apos;d like to replace it by &lt;code&gt;pyproject.toml&lt;/code&gt;. It was possible for almost everything, except for flake8 which has &lt;a href=&quot;https://gitlab.com/pycqa/flake8/-/issues/428&quot;&gt;an open issue&lt;/a&gt; for it.&lt;/p&gt;
&lt;p&gt;I decided to move as many things as I could to &lt;code&gt;pyproject.toml&lt;/code&gt;, and move flake8 config to &lt;code&gt;.flake8&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;With all the above, I was able to remove the &lt;code&gt;setup.cfg&lt;/code&gt; as well as all the pip-tools files for dependencies.&lt;/p&gt;
&lt;h2&gt;Code Coverage&lt;/h2&gt;
&lt;p&gt;Pytest had an &lt;a href=&quot;https://docs.pytest.org/en/stable/customize.html#pyproject-toml&quot;&gt;unexpected config section&lt;/a&gt; for &lt;code&gt;pyproject.toml&lt;/code&gt;, I didn&apos;t pay much attention to it initially and put its config under &lt;code&gt;[tool.pytest]&lt;/code&gt;, while the section should actually be &lt;code&gt;[tool.pytest.ini_options]&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;The consequence of this was that my config was ignored and I didn&apos;t run the tests with coverage enabled, which silenced another error in the coverage section &lt;code&gt;tool.coverage.run&lt;/code&gt;, where source should be an array:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;[tool.pytest.ini_options]
addopts = &quot;-v -Wdefault --cov=deezer&quot;

[tool.coverage.run]
branch = true
source = [&quot;deezer&quot;]
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;With this, pytest was running with coverage, but for some reason, codecov wasn&apos;t picking up the report properly. I noticed that it used to find an &lt;code&gt;xml&lt;/code&gt; file when it was working, so I edited the command on CI to generate it with &lt;code&gt;--cov-report=xml&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;poetry run pytest --cov-report=xml
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;With these changes I eventually got my code coverage reporting back, but it&apos;s something I missed in the original migration. Not directly related to Poetry, but interesting that Pytest decided to go with this section name.&lt;/p&gt;
&lt;h2&gt;Tox&lt;/h2&gt;
&lt;p&gt;Poetry works nicely with Tox, I followed the &lt;a href=&quot;https://python-poetry.org/docs/faq/#is-tox-supported&quot;&gt;section in their FAQ&lt;/a&gt;, and here is an overview of the changes:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;[tox]
isolated_build = true
envlist = py36,py37,py38,py39,pypy3,docs,lint,bandit

[testenv]
whitelist_externals = poetry
commands =
    poetry install
    poetry run pytest
...
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;I replaced the &lt;code&gt;deps&lt;/code&gt; section in each &lt;code&gt;testenv&lt;/code&gt; by a &lt;code&gt;poetry install&lt;/code&gt; into the list of commands to run, and prefixed all commands to be run in the isolated environment by &lt;code&gt;poetry run&lt;/code&gt;.&lt;/p&gt;
&lt;h2&gt;Github Actions&lt;/h2&gt;
&lt;p&gt;Poetry isn&apos;t installed out of the box on Github Actions, one could either install it with a simple &lt;code&gt;run&lt;/code&gt; step or use &lt;a href=&quot;https://github.com/abatilo/actions-poetry&quot;&gt;a dedicated action&lt;/a&gt; for it. I&apos;ve opted for the dedicated action, thinking that Dependabot could keep it up to date for me.&lt;/p&gt;
&lt;p&gt;The rest of the changes are pretty simple, it&apos;s a matter of replacing &lt;code&gt;pip install&lt;/code&gt; by &lt;code&gt;poetry install -E ...&lt;/code&gt; and prefixing all commands by &lt;code&gt;poetry run&lt;/code&gt;. My docs were tested and I was changing directory with &lt;code&gt;cd&lt;/code&gt;, I took this opportunity to instead use &lt;code&gt;working-directory&lt;/code&gt; key to the Github action step.&lt;/p&gt;
&lt;h2&gt;Verdict&lt;/h2&gt;
&lt;p&gt;Did Poetry deliver on its ambitious tagline? I think so, I was really impressed by the developer experience of Poetry, its CLI is really nice, and I hit few issues on the way. Overall the migration was not too difficult, you can check the &lt;a href=&quot;https://github.com/browniebroke/deezer-python/pull/196&quot;&gt;pull request&lt;/a&gt; on Github. I feel like there are quite a few features where I only scratched the surface (like multi-environments). I&apos;m going to wait a bit to see how this works in the longer run, but I think I&apos;ll migrate my other projects soon.&lt;/p&gt;
</content:encoded></item><item><title>Auto-update pre-commit hooks with GitHub Actions</title><link>https://browniebroke.com/blog/2020-07-12-gh-action-pre-commit-autoupdate/</link><guid isPermaLink="true">https://browniebroke.com/blog/2020-07-12-gh-action-pre-commit-autoupdate/</guid><description>A short post to explain how to get pre-hooks to automatically update their version in the config file.</description><pubDate>Sun, 12 Jul 2020 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Pre-commit hooks are great to reduce the feedback loop for things like linting and auto-formatting. Git supports them out of the box, but they are not easy to share across all developers working on a project, they need to be installed by each developers.&lt;/p&gt;
&lt;p&gt;Several tools exist to solve this problem, but my favorite is &lt;a href=&quot;https://pre-commit.com/&quot;&gt;pre-commit.com&lt;/a&gt;. It&apos;s written in Python, but it aims at being &lt;a href=&quot;https://pre-commit.com/#supported-languages&quot;&gt;language agnostic&lt;/a&gt;. It saves your setup in a config file and developers can install all of them with a single command.&lt;/p&gt;
&lt;p&gt;Each tool is referenced by their github repo and tag to install, which is great because each tool is pinned to a specific version. However, I usually have the tool versions already elsewhere in my repository, for example in &lt;code&gt;requirements.txt&lt;/code&gt;, causing some duplication. The main project dependencies are automatically updated with Dependabot, &lt;a href=&quot;https://pyup.io/&quot;&gt;PyUP&lt;/a&gt; or &lt;a href=&quot;https://renovate.whitesourcesoftware.com/&quot;&gt;Renovate&lt;/a&gt; but none of these tools support the pre-commit config file. After a while, it&apos;s easy to end up with versions discrepancies.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;UPDATE: since this article was written, Renovate added support for updating your pre-commit hook config.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;That is until this week-end, where I stumbled upon &lt;a href=&quot;https://pre-commit.com/#pre-commit-autoupdate&quot;&gt;the &lt;code&gt;autoupdate&lt;/code&gt; command&lt;/a&gt; from pre-commit. I&apos;m not sure how I missed this before, it looks like it&apos;s been part of pre-commit for a really long time. By combining this with the power of Github actions, I was able to get it to send me a pull request each time a new version is available:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;name: Pre-commit auto-update

on:
  schedule:
    - cron: &quot;0 0 * * *&quot;

jobs:
  auto-update:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v2

      - name: Set up Python
        uses: actions/setup-python@v2
        with:
          python-version: 3.8

      - name: Install pre-commit
        run: pip install pre-commit

      - name: Run pre-commit autoupdate
        run: pre-commit autoupdate

      - name: Create Pull Request
        uses: peter-evans/create-pull-request@v2
        with:
          token: ${{ secrets.CPR_GITHUB_TOKEN }}
          branch: update/pre-commit-autoupdate
          title: Auto-update pre-commit hooks
          commit-message: Auto-update pre-commit hooks
          body: |
            Update versions of tools in pre-commit 
            configs to latest version
          labels: dependencies
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This workflow is scheduled every day at midnight, runs &lt;code&gt;pre-commit autoupdate&lt;/code&gt; and sends a pull request if there are any changes.&lt;/p&gt;
&lt;p&gt;The piece that required a bit of fiddling is the action creating the pull request, partly to get commit message, title, content and labels right, but mostly because I initially used &lt;code&gt;secrets.GITHUB_TOKEN&lt;/code&gt; as token, but it wouldn&apos;t trigger the CI build for that pull request.&lt;/p&gt;
&lt;p&gt;It&apos;s a limitation which is well documented on the action&apos;s README, and is intentional from Github. I chose the solution to create a &lt;a href=&quot;https://docs.github.com/en/github/authenticating-to-github/creating-a-personal-access-token&quot;&gt;PAT&lt;/a&gt; scoped to &lt;code&gt;repo&lt;/code&gt; and added it to the secrets as &lt;code&gt;CPR_GITHUB_TOKEN&lt;/code&gt;. It&apos;s deployed and running on &lt;a href=&quot;https://github.com/browniebroke/django-codemod/actions?query=workflow%3A%22Pre-commit+auto-update%22&quot;&gt;the repo of &lt;code&gt;django-codemod&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;The pull request action has fixed inputs, so it will create one pull request at a time for all updates. If several tools get a new version, they would all be updated at once, and if a pull request already exists, it would receive more updates. This is not necessarily a bad thing, but if one tool breaks the build due to new linting rules, all are stuck.&lt;/p&gt;
&lt;p&gt;Maybe I&apos;ll look into making the pull request content a bit more dynamic, but for now it does the job I need to. I&apos;m also planning to add this to my &lt;a href=&quot;https://github.com/browniebroke/cookiecutter-pypackage&quot;&gt;Cookiecutter template for Python package&lt;/a&gt;, so I can get it for all my new projects.&lt;/p&gt;
&lt;p&gt;I hope this can help folks keep their pre-commit hook up to date, maybe this will become obsolete when &lt;a href=&quot;https://pre-commit.ci/&quot;&gt;pre-commit CI&lt;/a&gt; is ready, or maybe it will be a cheaper and simpler alternative&lt;/p&gt;
</content:encoded></item><item><title>Mastering flexbox with Flexbox Zombies</title><link>https://browniebroke.com/blog/2020-05-03-flexbox-zombie/</link><guid isPermaLink="true">https://browniebroke.com/blog/2020-05-03-flexbox-zombie/</guid><description>A short post to share my recent learning experience.</description><pubDate>Sun, 03 May 2020 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;&lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Flexible_Box_Layout&quot;&gt;Flexbox&lt;/a&gt; is not exactly new, and I&apos;ve played with it a few times, but as a developers who spends most of my time in the back-end building API, I don&apos;t really need to know it at work. I do a bit of CSS here and there, but not enough to internalise it. I never took the time to really understand what various properties do, when to apply them on container vs when to apply them on content and I always had to look up &lt;a href=&quot;https://yoksel.github.io/flex-cheatsheet/&quot;&gt;the cheatsheet&lt;/a&gt; to do anything with it.&lt;/p&gt;
&lt;p&gt;This was until a few weeks ago when I read about &lt;a href=&quot;https://flexboxzombies.com&quot;&gt;Flexbox Zombies&lt;/a&gt; somewhere (probably on Twitter). I&apos;m a big fan of gamification, it hooks me up and I learn very well with this. Decided to take advantage of these unusual times where we have to stay home more than normal, I took the class and set myself to do one lesson a day.&lt;/p&gt;
&lt;p&gt;The course is made of 12 stages -11 lessons and 1 final test- each taking between 15 to 30 mins. The first part of each lesson is explaining one or two properties and its values, followed by series of exercises with more of less help. The final test is where you need to defeat the boss and is without hints.&lt;/p&gt;
&lt;p&gt;The result for me is that 12 days later, I feel very much like I know what I am doing when working with flexbox. Before it was like a guessing game, very much empirical, applying properties half-randomly on the container or items. Now I know what I &lt;em&gt;should&lt;/em&gt; use for my use case. Right now, all the things I learnt are fresh but I&apos;m confident this time it will stick in my head.&lt;/p&gt;
&lt;p&gt;If this feel like something you&apos;d like to learn, I would encourage you to &lt;a href=&quot;https://flexboxzombies.com&quot;&gt;sign-up for the course&lt;/a&gt;, it&apos;s free (at least now).&lt;/p&gt;
&lt;p&gt;Thanks to &lt;a href=&quot;https://twitter.com/geddski&quot;&gt;Dave Geddes&lt;/a&gt; for putting this amazing learning content together!&lt;/p&gt;
</content:encoded></item><item><title>Self-host your Typography.js fonts with Gasby</title><link>https://browniebroke.com/blog/2020-01-05-self-host-typographyjs-fonts-with-gatsby/</link><guid isPermaLink="true">https://browniebroke.com/blog/2020-01-05-self-host-typographyjs-fonts-with-gatsby/</guid><description>How to self-host the fonts used by your Typography.js theme with GatsbyJS.</description><pubDate>Sun, 05 Jan 2020 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;Problem&lt;/h2&gt;
&lt;p&gt;I recently added a CSP to this blog, and one of the tricky parts when implementing this was that the fonts were coming from from Google fonts. Several extra directives were required, and depending on the browser I was using, it was not even working properly, I think Chrome wasn&apos;t properly loading them, while Firefox was.&lt;/p&gt;
&lt;h2&gt;Investigation&lt;/h2&gt;
&lt;p&gt;To fix this, I decided that I should self-host the fonts I need instead of linking to several Google domains. I was ready to download the file and copy them in my codebase, but then I realised there is no CSS/SASS, so nothing linking to them...&lt;/p&gt;
&lt;p&gt;I&apos;m using the &lt;a href=&quot;https://www.npmjs.com/package/typography-theme-funston&quot;&gt;Funston theme&lt;/a&gt; for &lt;a href=&quot;http://kyleamathews.github.io/typography.js/&quot;&gt;Typography.js&lt;/a&gt; with &lt;a href=&quot;https://www.gatsbyjs.org/packages/gatsby-plugin-typography/&quot;&gt;the appropriate Gatsby plugin&lt;/a&gt;. The fonts are &lt;a href=&quot;https://github.com/KyleAMathews/typography.js/blob/33d86df7e0d7f44cd1a71c8bd8791bdb71a7ecc5/packages/typography-theme-funston/src/index.js#L10-L19&quot;&gt;specified by the theme&lt;/a&gt; and the appropriate link tag are injected in the HTML when needed.&lt;/p&gt;
&lt;p&gt;The only mention of self-hosting is in &lt;a href=&quot;https://www.gatsbyjs.org/packages/gatsby-plugin-typography/#options&quot;&gt;the Gatsby plugin options&lt;/a&gt;:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;code&gt;omitGoogleFont&lt;/code&gt;: (boolean, default: &lt;code&gt;false&lt;/code&gt;) Typography includes &lt;a href=&quot;https://github.com/KyleAMathews/typography.js/blob/e7e71c82f63c7a146eb1b5ac7017695359dd9cba/packages/react-typography/src/GoogleFont.js&quot;&gt;a helper&lt;/a&gt; that makes a request to Google&apos;s font CDN for the fonts you need. You might, however, want to inject the fonts into JS or use a CDN of your choosing. Setting this value to &lt;code&gt;true&lt;/code&gt; will make &lt;code&gt;gatsby-plugin-typography&lt;/code&gt; skip the inclusion of this helper. &lt;strong&gt;You will have to include the appropriate fonts yourself.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;However, how to include them yourself is left unanswered, but luckily someone &lt;a href=&quot;https://stackoverflow.com/a/52786121/2261637&quot;&gt;answered this question on Stackoverflow&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;Solution&lt;/h2&gt;
&lt;p&gt;Ok, looks like I got all the pieces of the puzzle, let&apos;s code!&lt;/p&gt;
&lt;p&gt;First, let&apos;s disable Google font from the &lt;code&gt;gatsby-plugin-typography&lt;/code&gt; config:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// gatsby-config.js
{
  ...
  plugins: [
    {
      resolve: `gatsby-plugin-typography`,
      options: {
        pathToConfigModule: `src/utils/typography`,
        omitGoogleFont: true,
      },
    },
  ]
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The 2 fonts I need are &quot;Patua One&quot; and &quot;Cabin Condensed&quot;, and -following the previous Stackoverflow post- you can find on npm &lt;a href=&quot;https://www.npmjs.com/package/typeface-patua-one&quot;&gt;here&lt;/a&gt; and &lt;a href=&quot;https://www.npmjs.com/package/typeface-cabin-condensed&quot;&gt;here&lt;/a&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;yarn add typeface-patua-one typeface-cabin-condensed
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then add them to the &lt;a href=&quot;https://www.gatsbyjs.org/docs/browser-apis/#onInitialClientRender&quot;&gt;&lt;code&gt;onInitialClientRender&lt;/code&gt; browser API&lt;/a&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// gatsby-browser.js
exports.onInitialClientRender = () =&amp;gt; {
  require(&quot;typeface-patua-one&quot;);
  require(&quot;typeface-cabin-condensed&quot;);
};
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And that&apos;s pretty much it, now the fonts are self-hosted.&lt;/p&gt;
&lt;h2&gt;Cleaning up my CSP&lt;/h2&gt;
&lt;p&gt;As I said in the intro, the main driver for this change was my CSP, which was at best overly complicated, and at worst wrong. So now that it doesn&apos;t need to be so complicated, let&apos;s review it.&lt;/p&gt;
&lt;p&gt;The usage of Google fonts require -at least- the following to work:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;&apos;style-src&apos;: fonts.googleapis.com fonts.gstatic.com&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;&apos;font-src&apos;: fonts.gstatic.com&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;&apos;connect-src&apos;: fonts.googleapis.com&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;&apos;prefetch-src&apos;: fonts.googleapis.com&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Some directives, like &lt;code&gt;font-src&lt;/code&gt; and &lt;code&gt;prefetch-src&lt;/code&gt; are only required for Google fonts, so I can remove them from the plugin config.&lt;/p&gt;
&lt;h2&gt;Final words&lt;/h2&gt;
&lt;p&gt;If you want to look at the entire change, I made &lt;a href=&quot;https://github.com/browniebroke/browniebroke.com/pull/234&quot;&gt;a pull request&lt;/a&gt; for it, feel free to have a look to it.&lt;/p&gt;
</content:encoded></item><item><title>Setting up a Content Security Policy with Gatsby</title><link>https://browniebroke.com/blog/2019-11-14-setting-up-content-security-policy-gatsby/</link><guid isPermaLink="true">https://browniebroke.com/blog/2019-11-14-setting-up-content-security-policy-gatsby/</guid><description>My quick story on how I added a content security policy (CSP) on my blog, powered by GatsbyJS.</description><pubDate>Thu, 14 Nov 2019 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;This is a quick story telling how I set up a content security policy on my personal blog which is powered by &lt;a href=&quot;https://www.gatsbyjs.org/&quot;&gt;Gatsby&lt;/a&gt;. I&apos;ve been keen to try adding CSP for a while but I know it can be tricky to get right and cover everything. However, this blog is a very simple use case as I embed very little third party scripts but it took me some time to get it right nevertheless.&lt;/p&gt;
&lt;h2&gt;First attempt&lt;/h2&gt;
&lt;p&gt;Gatsby is relatively young but has already a rich plugin ecosystem. So the first thing I did was to research any plugin that would help with this. I quickly found &lt;a href=&quot;https://www.gatsbyjs.org/packages/gatsby-plugin-csp/&quot;&gt;&lt;code&gt;gatsby-plugin-csp&lt;/code&gt;&lt;/a&gt; doing exactly what I wanted. I went ahead and installed it without any options to give it a try:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// gatsby-config.js
module.exports = {
  plugins: [`gatsby-plugin-csp`],
};
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;I opened a pull request to check a production-like &lt;a href=&quot;https://5dc89dea31c71e000832cd5a--browniebroke.netlify.com/&quot;&gt;deploy preview on Netlify&lt;/a&gt;, but it looked pretty broken:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;_01-first-try.png&quot; alt=&quot;First try&quot; title=&quot;First Try&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Looks like a few things were missing... Let&apos;s customise this plugin!&lt;/p&gt;
&lt;h2&gt;Starting to customise&lt;/h2&gt;
&lt;p&gt;The plugin has a few options, I quickly glanced the documentation and immediately started adding directives for the errors that were reported. I&apos;m using Google analytics and Google fonts, I need to whitelist them:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// gatsby-config.js
module.exports = {
  plugins: [
    {
      resolve: `gatsby-plugin-csp`,
      options: {
        directives: {
          &quot;script-src&quot;: `&apos;self&apos; www.google-analytics.com`,
          &quot;style-src&quot;: `&apos;self&apos; &apos;unsafe-inline&apos; fonts.googleapis.com`,
          &quot;img-src&quot;: `&apos;self&apos; data: www.google-analytics.com`,
        },
      },
    },
  ],
};
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Pushed to my branch again, waited for the deployment to be updated, but &lt;a href=&quot;https://5dc8a896ace0c4000847a904--browniebroke.netlify.com/&quot;&gt;it was still not looking great&lt;/a&gt;:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;_02-second-with-options.png&quot; alt=&quot;With Options&quot; title=&quot;With Options&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Hum, weird it looks even worse! Ok the errors are different, let&apos;s keep customising. Looking at the errors, I can see another Google font domain and inlines are missing, let&apos;s add them:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// gatsby-config.js
module.exports = {
  plugins: [
    {
      resolve: `gatsby-plugin-csp`,
      options: {
        directives: {
          &quot;script-src&quot;: `&apos;self&apos; &apos;unsafe-inline&apos; data: www.google-analytics.com`,
          &quot;style-src&quot;: `&apos;self&apos; &apos;unsafe-inline&apos; fonts.googleapis.com fonts.gstatic.com`,
          &quot;img-src&quot;: `&apos;self&apos; data: www.google-analytics.com`,
        },
      },
    },
  ],
};
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Let&apos;s &lt;a href=&quot;https://5dc8acc45da30f0008c79aa6--browniebroke.netlify.com/&quot;&gt;deploy again&lt;/a&gt;... But now I&apos;m confused:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;_03-inline-disallowed.png&quot; alt=&quot;Inline disallowed&quot; title=&quot;Inline disallowed&quot; /&gt;&lt;/p&gt;
&lt;p&gt;It says some inlines are disallowed, but they are in my directive!&lt;/p&gt;
&lt;h2&gt;Time to read the documentation&lt;/h2&gt;
&lt;p&gt;After going back to the plugin documentation, I can see they have 2 options &lt;code&gt;mergeScriptHashes&lt;/code&gt; and &lt;code&gt;mergeStyleHashes&lt;/code&gt;, but I didn&apos;t really understand what they are for. At the bottom of the plugin&apos;s readme, there is a link to &lt;a href=&quot;https://bejamas.io/blog/content-security-policy-gatsby-websites/&quot;&gt;a blog post&lt;/a&gt; that I should have read first.&lt;/p&gt;
&lt;p&gt;What happens? The plugin tries its best to generate a list of the hashes for the allowed inlines script and styles that Gatsby adds. It turns out that the CSP specification states that a policy cannot use both hashes and &lt;code&gt;&apos;unsafe-inline&apos;&lt;/code&gt;, and to be fair, that&apos;s what my browser is telling me in the first warning at the top of my console:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Content Security Policy: Ignoring &quot;&apos;unsafe-inline&apos;&quot; within script-src or style-src: nonce-source or hash-source specified
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Lesson learned: read the warning/errors from top to bottom, not the other way around.&lt;/p&gt;
&lt;h2&gt;Solution&lt;/h2&gt;
&lt;p&gt;These 2 options were exactly what I needed, we can&apos;t use &lt;code&gt;unsafe-inline&lt;/code&gt; with hashes which are included by the plugin, we need &lt;a href=&quot;https://github.com/bejamas/gatsby-plugin-csp/issues/3#issuecomment-521032340&quot;&gt;to disable them&lt;/a&gt;.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// gatsby-config.js
module.exports = {
  plugins: [
    {
      resolve: `gatsby-plugin-csp`,
      options: {
        mergeScriptHashes: false,
        mergeStyleHashes: false,
        directives: {
          &quot;script-src&quot;: `&apos;self&apos; &apos;unsafe-inline&apos; data: www.google-analytics.com`,
          &quot;style-src&quot;: `&apos;self&apos; &apos;unsafe-inline&apos; fonts.googleapis.com fonts.gstatic.com`,
          &quot;img-src&quot;: `&apos;self&apos; data: www.google-analytics.com`,
          &quot;font-src&quot;: `&apos;self&apos; data: fonts.gstatic.com`,
        },
      },
    },
  ],
};
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;At this point, it all &lt;a href=&quot;https://5dc9d3e45c60a70008e187a5--browniebroke.netlify.com/&quot;&gt;look good visually&lt;/a&gt;, but when checking &lt;a href=&quot;secutiryheaders.com&quot;&gt;security headers&lt;/a&gt;, I still had a B grade:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;_04-b-grade-security-headers.png&quot; alt=&quot;B Grade&quot; title=&quot;B Grade&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;Moving policy to the HTTP header&lt;/h2&gt;
&lt;p&gt;The plugin implements CSP by using a &lt;code&gt;&amp;lt;meta&amp;gt;&lt;/code&gt; tag, not a HTTP header. They have &lt;a href=&quot;https://github.com/bejamas/gatsby-plugin-csp/issues/4&quot;&gt;an issue open&lt;/a&gt; to support HTTP headers, but it looks like it won&apos;t be supported soon as it depends on the deployment platform used. The issue links to &lt;a href=&quot;https://github.com/gatsbyjs/gatsby/issues/10890#issuecomment-468982396&quot;&gt;a comment&lt;/a&gt; with a solution to inject the CSP to the headers with the &lt;code&gt;gatsby-plugin-netlify&lt;/code&gt; package. &lt;a href=&quot;https://github.com/DeveloPassion/website/commit/c31120ccccefed43c266c8ef862ec696bd36c7a8&quot;&gt;The patch&lt;/a&gt; is pretty self-contained so I decided to &lt;a href=&quot;https://github.com/browniebroke/browniebroke.com/pull/210/commits/f27c05c84b0f4f2785aca0f2b8ef73efddb39a14&quot;&gt;replicate it on this blog&lt;/a&gt;. Seem to work fine and after deploying, I finally got this A grade I was after:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;_05-a-grade-security-headers.png&quot; alt=&quot;A Grade&quot; title=&quot;A Grade&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Success! Press merge and got it deployed.&lt;/p&gt;
&lt;h2&gt;Final words&lt;/h2&gt;
&lt;p&gt;Thanks for the very helpful resources out there, &lt;a href=&quot;https://github.com/thomkrupa&quot;&gt;Thom Krupa&lt;/a&gt; &amp;amp; the &lt;a href=&quot;https://github.com/bejamas&quot;&gt;Bejamas organisation&lt;/a&gt; as well as &lt;a href=&quot;https://github.com/dsebastien&quot;&gt;Sebastien Dubois&lt;/a&gt; for the script to make the header work.&lt;/p&gt;
&lt;p&gt;The A grade is nice, however the policy I&apos;m ending up with is not very secure and allows a lot of thing to be executed. Each time we need to resort to unsafe-inline, we&apos;re opening up a breach for attackers. I don&apos;t have much sensitive things on this site though, so it&apos;s probably good enough for this case and this seems to be the best we can do for now. I&apos;ll keep a close eye to the improvements in Gatsby and the CSP to see things evolve.&lt;/p&gt;
</content:encoded></item><item><title>Making Celery work nicely with Django transactions</title><link>https://browniebroke.com/blog/2019-06-25-making-celery-work-nicely-with-django-transactions/</link><guid isPermaLink="true">https://browniebroke.com/blog/2019-06-25-making-celery-work-nicely-with-django-transactions/</guid><description>A short post about things I&apos;ve learned when working with Celery in a Django project and a tip to avoid a common pitfall with DB transactions.</description><pubDate>Tue, 25 Jun 2019 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;I&apos;ve been using Celery in my Django projects for about 5 years now. Along the way, I made some mistakes, learned from them and picked up a few best practices. I will try to share a few of them here.&lt;/p&gt;
&lt;h2&gt;Avoid serialising complex objects&lt;/h2&gt;
&lt;p&gt;This is a pretty simple best practice, but was easy to miss as the default serialization was pickle in before v4 of Celery, the default is now JSON and enforces this. Failing that may cause hard to catch issues like when the pickle protocol changes. This happened when we migrated from Python 2 to 3 for instance. It&apos;s pretty much impossible to catch by your test suite, but will happen for sure in production.&lt;/p&gt;
&lt;h2&gt;Worker throwing &lt;code&gt;ModelInstanceDoesNotExist&lt;/code&gt;&lt;/h2&gt;
&lt;p&gt;In a Django project, a complex object is very likely to be a model instance, that&apos;s where your data lives after all. The usual way to work with the previous best practice is to pass the &lt;code&gt;id&lt;/code&gt; of a model instance when submitting the task and fetch the instance again from DB when handling the task on the worker.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# Send task as
your_task.delay(user_id=user.id)

# and run as
@app.task()
def your_task(user_id):
    user = User.objects.get(
        id=user_id
    )
    ...
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;However, when doing this in a naive way, this can lead to exceptions due to missing instances. It took me a while to get this one the first time it happened to me, especially given that the instance with the offending &lt;code&gt;id&lt;/code&gt; was always there when checking manually, and there was no issues in my unit tests.&lt;/p&gt;
&lt;p&gt;Experienced readers probably know the answer: transactions. Basically, the task is picked up by the worker before the main transaction is committed in the database. We need to delay task submission until the transaction is committed. Luckily, Django provides a hook just for that type of things: &lt;code&gt;transaction.on_commit()&lt;/code&gt;. This is well documented in &lt;a href=&quot;http://docs.celeryproject.org/en/latest/userguide/tasks.html?highlight=on_commit#database-transactions&quot;&gt;the Celery&lt;/a&gt; and the since 1.10 even Django uses Celery as an example &lt;a href=&quot;https://docs.djangoproject.com/en/2.2/topics/db/transactions/#django.db.transaction.on_commit&quot;&gt;in their documentation&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;Problems with this solution&lt;/h2&gt;
&lt;p&gt;This hook is great, but causes a bit of boilerplate. Each time you want to use that, you need to define a &lt;code&gt;lambda&lt;/code&gt; when you call the task. It can get quite repetitive when you start to have many tasks. Not only its longer to write and hurts readability, it may also cause some weird bugs depending on how it&apos;s used. Here is a very simplified example:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# tasks.py
@app.task()
def print_value(val):
    print(val)


# views.py
def my_view(request):
    for index in range(5):
        transaction.on_commit(
            lambda: print_value.delay(index)
        )
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;What would this print on the worker process? The answer is 5 times the number &apos;4&apos;. If you found it, well done! It&apos;s &lt;a href=&quot;https://docs.python.org/3/faq/programming.html#why-do-lambdas-defined-in-a-loop-with-different-values-all-return-the-same-result&quot;&gt;a documented behaviour of lambdas&lt;/a&gt;, but in a more realistic code example with longer functions and more complex parameters, it would easily be missed.&lt;/p&gt;
&lt;h2&gt;A better API&lt;/h2&gt;
&lt;p&gt;import { Callout } from &quot;../../../components/callout&quot;;&lt;/p&gt;
&lt;p&gt;&amp;lt;Callout level=&quot;note&quot;&amp;gt;
As of Celery v5.4, released 17th April 2024, this functionality has now been
released upstream. See the &lt;a href=&quot;https://docs.celeryq.dev/en/stable/django/first-steps-with-django.html#trigger-tasks-at-the-end-of-the-database-transaction&quot;&gt;Celery
docs&lt;/a&gt;
for more information.
&amp;lt;/Callout&amp;gt;&lt;/p&gt;
&lt;p&gt;One idea that may come to mind is to wrap this in a decorator. This was &lt;a href=&quot;https://code.djangoproject.com/ticket/29557&quot;&gt;suggested as an improvement to Django&lt;/a&gt; but was rejected after some discussion. I agree with the counter points: a decorator would hurt the readability of the code calling the decorated function.&lt;/p&gt;
&lt;p&gt;What if we could wrap this boilerplate into a helper function to handle this nicely? Or better, attach this helper to our tasks? Ideally, we want to be able to call our tasks like this:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;print_value.delay_on_commit(5)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;That&apos;s what I&apos;m talking about. A simple API that is easy to use, and clear to read. This also addresses the drawbacks of the previous decorator idea. The good news is that Celery provides &lt;a href=&quot;http://docs.celeryproject.org/en/latest/userguide/tasks.html#custom-task-classes&quot;&gt;a proper way to extend the default behaviour&lt;/a&gt; making it not very hard to implement.&lt;/p&gt;
&lt;p&gt;Under the hood, the &lt;code&gt;@task&lt;/code&gt; decorator run your task body in the &lt;code&gt;run()&lt;/code&gt; method of the Celery base task class. This class is where the &lt;code&gt;delay()&lt;/code&gt; callable comes from. It&apos;s possible to override the base class either when defining the task function or more globally, when defining the Celery app:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;from celery import Celery


app = Celery(
    &apos;tasks&apos;,
    task_cls=&apos;your_task:BetterTask&apos;,
)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And your base task would look like this:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;from celery import Task
from django.db import transaction


class BetterTask(Task):

    def delay_on_commit(self, *args, **kwargs):
        return transaction.on_commit(
            lambda: self.delay(*args, **kwargs)
        )
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And that&apos;s all you need! Now you have a nicer API, less error prone, you just need to remember to use it. How would you use it? If we modify our previous view, we would get the following:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;
def my_view(request):
    for index in range(5):
        print_value.delay_on_commit(index)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If we run it, we can confirm that it works now as intended, printing the numbers 1 to 5. Nice!&lt;/p&gt;
&lt;h2&gt;Closing words&lt;/h2&gt;
&lt;p&gt;As Django developers, we are generally less used to the asynchronous way of thinking than in other programming languages, as Django does not requires us to think about that (&lt;a href=&quot;https://www.aeracode.org/2018/06/04/django-async-roadmap/&quot;&gt;yet&lt;/a&gt;). On the other hand, Celery is asynchronous by its very nature, and it&apos;s easy to cause some unusual bugs in your application, if not careful. By adding a wrapper around a common pattern, to expose a better API, we can make our code easier to write, read and avoid bugs.&lt;/p&gt;
</content:encoded></item><item><title>Static vs. Media files in Django</title><link>https://browniebroke.com/blog/2019-06-18-static-vs-media-in-django/</link><guid isPermaLink="true">https://browniebroke.com/blog/2019-06-18-static-vs-media-in-django/</guid><description>The difference between static and media files in Django, which are often confused by beginners.</description><pubDate>Tue, 18 Jun 2019 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Django is a great Web framework for building Web applications in Python. It comes with a lot of batteries included that you&apos;ll most likely need at some point in your project. Two of them took me a while to differentiate when I started: static and media files.&lt;/p&gt;
&lt;p&gt;After helping some less experienced people, I feel like I&apos;m not the only one running into the confusion, so I&apos;m hoping to clarify their differences in this post.&lt;/p&gt;
&lt;h2&gt;TL;DR&lt;/h2&gt;
&lt;p&gt;Static files are part of your application code, while media is for generated content by your application or users of your application.&lt;/p&gt;
&lt;h2&gt;Static files&lt;/h2&gt;
&lt;p&gt;Static files are managed by the &lt;a href=&quot;https://docs.djangoproject.com/en/stable/ref/contrib/staticfiles/&quot;&gt;&lt;code&gt;staticfiles&lt;/code&gt; app&lt;/a&gt; which you need to install. It&apos;s made of several building blocks, the 3 most important ones being:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Storage classes&lt;/li&gt;
&lt;li&gt;Templates tags&lt;/li&gt;
&lt;li&gt;&lt;code&gt;collectstatic&lt;/code&gt; admin command&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;These components work together to serve the assets in a more or less optimised way, depending on the environment. This can be altered using the following settings:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;STATIC_ROOT&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;STATIC_URL&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;STATICFILES_DIRS&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;STATICFILES_STORAGE&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Static files are usually either part of your code, or part of your dependencies&apos; code. They can come from various places, each app may provide its own files. They are typically kept in source control. The Django admin ships with some javascript and CSS, for example, that are stored &lt;a href=&quot;https://github.com/django/django/tree/master/django/contrib/admin/static/admin&quot;&gt;in Django&apos;s Github repository&lt;/a&gt;.&lt;/p&gt;
&lt;h3&gt;Local development setup&lt;/h3&gt;
&lt;p&gt;In development, &lt;a href=&quot;https://docs.djangoproject.com/en/stable/ref/contrib/staticfiles/#static-file-development-view&quot;&gt;the setup for static files is inefficient and optimised for convenience&lt;/a&gt;. It’s based on a view that, by default, looks into all the installed apps to find static files. Works great for local development, but not ideal for production.&lt;/p&gt;
&lt;h3&gt;Production setup&lt;/h3&gt;
&lt;p&gt;In production, finding files is done ahead of time via the &lt;code&gt;collectstatic&lt;/code&gt; admin command, which you should run as part of your deployment. At a high level, the command does a very similar job as the development view, but the main difference is that it runs outside of the request-response cycle, without blocking the person visiting your website. It copies all the static files into a single location, being another folder or somewhere on another machine, which could be in a different part of the world.&lt;/p&gt;
&lt;p&gt;My go-to solution for this used to be in &lt;a href=&quot;https://pypi.org/project/django-storages/&quot;&gt;&lt;code&gt;django-storages&lt;/code&gt;&lt;/a&gt;. It ships with a storage class for saving your files into AWS S3. I would also add a CDN in front to help caching the assets closer to my users. The setup looked like this:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;_with-bucket.jpg&quot; alt=&quot;Static files in a Bucket&quot; /&gt;&lt;/p&gt;
&lt;p&gt;However, recently I&apos;ve switched to &lt;a href=&quot;https://pypi.org/project/whitenoise/&quot;&gt;Whitenoise&lt;/a&gt;, which leads to a simpler setup, and remove the need for a S3 bucket, all hosting comes from the Django app. The CDN should serve most of the traffic anyway:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;_with-whitenoise.jpg&quot; alt=&quot;Static files with Whitenoise&quot; /&gt;&lt;/p&gt;
&lt;p&gt;I still use &lt;code&gt;django-storages&lt;/code&gt; but only for media files, which brings us to the next section...&lt;/p&gt;
&lt;h2&gt;Media files&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;https://docs.djangoproject.com/en/stable/topics/files/&quot;&gt;Media files&lt;/a&gt; are usually for files which are uploaded by users or generated by your application during the life of your Django project. They are typically not stored in source control.&lt;/p&gt;
&lt;p&gt;This could be in the admin, the profile picture the user set in thei profile, some more private documents or a PDF receipt for an order that your app generate.&lt;/p&gt;
&lt;p&gt;Basically, anything which is going into a &lt;code&gt;FileField&lt;/code&gt;, &lt;code&gt;ImageField&lt;/code&gt; or the likes is classified as media storage.&lt;/p&gt;
&lt;p&gt;By default, files are stored on the local file system, and again a few settings are here to configure the behaviour:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;MEDIA_ROOT&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;MEDIA_URL&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;DEFAULT_FILE_STORAGE&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;In some production environments -if you run your app on Heroku for instance- this default setup is not suitable. Heroku has an ephemeral file system meaning that it&apos;s cleared each time the application is updated. This is where &lt;code&gt;django-storages&lt;/code&gt; is still useful and needed.&lt;/p&gt;
&lt;p&gt;If your app is hosting a mix of public and private medias, I recommend to define separate storage classes for each, you can tell in the field instantiation which storage class to use:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;class User:
    name = models.CharField()
    # public file
    profile_picture = models.ImageField(
        storage=PublicStorage()
    )
    # private file
    dbs_check_document = models.FileField(
        storage=PrivateStorage()
    )
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The &lt;code&gt;django-storages&lt;/code&gt; library has several backends for various hosting providers with a lot of options to customise them, so I won&apos;t go into the details of each, but here is an example of what it could look like for AWS:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;from storages.backends.s3boto3 import S3Boto3Storage


class PublicStorage(S3Boto3Storage):
    default_acl = &apos;public&apos;
    file_overwrite = False
    bucket_name = &apos;my-public-bucket&apos;


class PrivateStorage(S3Boto3Storage):
    default_acl = &apos;private&apos;
    file_overwrite = False
    bucket_name = &apos;my-private-bucket&apos;
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Use different class locally and in production&lt;/h3&gt;
&lt;p&gt;There is one problem with the above approach, though: by defining your storage class on the model field, that means it would potentially be shared between you local environment and production, which is probably not what you want. To solve this, we can use a factory function to either use the local file system or a remote location, depending on a setting:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;def get_storage(storage_type):
    if settings.USE_REMOTE_STORAGE:
        storage_class = {
            &apos;private&apos;: PrivateStorage,
            &apos;public&apos;: PublicStorage,
        }[storage_type]
        return storage_class()
    return FileSystemStorage()
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Which would change your model definition to:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;class User:
    name = models.CharField()
    # public file
    profile_picture = models.ImageField(
        storage=get_storage(&apos;public&apos;)
    )
    # private file
    dbs_check_document = models.FileField(
        storage=get_storage(&apos;private&apos;)
    )
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;In a real world case, this helper should probably handle &lt;code&gt;KeyError&lt;/code&gt; in the lookup and return the &lt;code&gt;DEFAULT_FILE_STORAGE&lt;/code&gt;. You could also invent custom settings as you need.&lt;/p&gt;
&lt;h2&gt;Security concerns&lt;/h2&gt;
&lt;p&gt;If you decide to use &lt;code&gt;django-storages&lt;/code&gt; for both Static and Media files, it&apos;s important that you make the separation very clear, a user of your website shouldn&apos;t be able to override one of your static assets by uploading a resource! I like to use a separate S3 Bucket for each type of storage. AWS has many options to customise the privacy of buckets and the objects their contain, and other providers have equivalent features.&lt;/p&gt;
&lt;h2&gt;Final note&lt;/h2&gt;
&lt;p&gt;While the Media storage might not be used by your application, the static files are a more essential, and any reasonable Django site will need it (at least for the admin).&lt;/p&gt;
&lt;p&gt;I hope this post will help reduce the confusion I see frequently with beginners.&lt;/p&gt;
</content:encoded></item><item><title>Add cache-control header to an entire S3 Bucket using Boto3</title><link>https://browniebroke.com/blog/2019-05-01-add-cache-control-header-entire-s3-bucket-with-boto3/</link><guid isPermaLink="true">https://browniebroke.com/blog/2019-05-01-add-cache-control-header-entire-s3-bucket-with-boto3/</guid><description>How to change cache-control header for multiple objects in a AWS S3 bucket using boto3.</description><pubDate>Wed, 01 May 2019 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;I recently came across a task which seem pretty generic, but for which I couldn&apos;t find an existing solution online: update the &lt;code&gt;Cache-Control&lt;/code&gt; header for an entire S3 bucket &lt;strong&gt;using boto3&lt;/strong&gt;.&lt;/p&gt;
&lt;h2&gt;Existing solutions&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Do it through the AWS management console, but I wanted to script it.&lt;/li&gt;
&lt;li&gt;Do it using &lt;a href=&quot;http://www.dhimanvivek.com/boto/s3-add-metadata-cache-control-header-to-key&quot;&gt;Boto 2&lt;/a&gt;, but this is no longer installed on our system, I didn&apos;t want to reintroduce an outdated dependency.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Boto3 solution&lt;/h2&gt;
&lt;p&gt;After a bit of fiddling and digging through the documentation, I came up with this pretty simple script:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;s3 = boto3.resource(&apos;s3&apos;)
bucket = s3.Bucket(&apos;my-public-bucket&apos;)
for summary in bucket.objects.filter(Prefix=&apos;static&apos;):
    obj = summary.Object()
    obj.copy_from(
        CopySource={
            &apos;Bucket&apos;: &apos;my-public-bucket&apos;,
            &apos;Key&apos;: obj.key,
        },
        CacheControl=&apos;public,max-age=604800,immutable&apos;,
        MetadataDirective=&apos;REPLACE&apos;,
    )
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The value depends a lot on your use case, you might want ot read the excellent &lt;a href=&quot;https://csswizardry.com/2019/03/cache-control-for-civilians/&quot;&gt;Cache-Control for civilians&lt;/a&gt; post which goes into detail.&lt;/p&gt;
&lt;p&gt;This solution uses the &lt;a href=&quot;https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/s3.html#S3.Object.copy_from&quot;&gt;&lt;code&gt;copy_from&lt;/code&gt; API&lt;/a&gt;, which a bit of an unnatural API for achieving this goal, but it does the trick, and seems to be the only way at the moment. Hopefully this may help others.&lt;/p&gt;
&lt;h2&gt;Failures to get there:&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;I initially tried via the &lt;a href=&quot;https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/s3.html#S3.Object.put&quot;&gt;&lt;code&gt;put&lt;/code&gt; API&lt;/a&gt;, but this actually overrides the existing file with an empty one, which is not what I wanted.&lt;/li&gt;
&lt;li&gt;The copy to itself operation failed without &lt;code&gt;MetadataDirective=&apos;REPLACE&apos;&lt;/code&gt;. I was about to give up but then discovered about it.&lt;/li&gt;
&lt;/ul&gt;
</content:encoded></item><item><title>How I made 1000&apos;s of websites more secure with one line of code</title><link>https://browniebroke.com/blog/2019-04-24-how-i-made-1000-of-website-more-secure/</link><guid isPermaLink="true">https://browniebroke.com/blog/2019-04-24-how-i-made-1000-of-website-more-secure/</guid><description>This is the story of learning about a new security header, added it to Gatsby, and made tons of sites more secure</description><pubDate>Wed, 24 Apr 2019 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Or at least &lt;a href=&quot;https://github.com/search?l=JavaScript&amp;amp;o=desc&amp;amp;q=gatsby-plugin-netlify&amp;amp;s=indexed&amp;amp;type=Code&quot;&gt;a lot of them&lt;/a&gt;...&lt;/p&gt;
&lt;h2&gt;Learning about a new security header&lt;/h2&gt;
&lt;p&gt;Last week, I attended the &lt;a href=&quot;https://www.meetup.com/djangolondon/&quot;&gt;London Django meetup&lt;/a&gt;. Among a very interesting lineup of talks, there was a &lt;a href=&quot;https://skillsmatter.com/skillscasts/13859-london-django-lightening-talk&quot;&gt;lighting presentation&lt;/a&gt; from &lt;a href=&quot;https://adamj.eu/&quot;&gt;Adam Johnson&lt;/a&gt; about security headers and how Django helps with them. If you don&apos;t know what security headers are, I urge you to watch the talk, it&apos;s a bit about Django but it applies about the web in general.&lt;/p&gt;
&lt;p&gt;While I knew the ones that comes with Django and the very important &lt;code&gt;Content-Security-Policy&lt;/code&gt;, I discovered the &lt;code&gt;Referrer-Policy&lt;/code&gt; header, including the &lt;a href=&quot;https://books.google.co.uk/books?id=3EybAgAAQBAJ&amp;amp;pg=PT541&amp;amp;lpg=PT541&amp;amp;redir_esc=y#v=onepage&amp;amp;q&amp;amp;f=false&quot;&gt;story of the &lt;code&gt;Referer&lt;/code&gt; header mispelling&lt;/a&gt; from the early days of the web.&lt;/p&gt;
&lt;p&gt;In the presentation, Adam mentioned a tool to check your site and educate about all the possible security headers: &lt;a href=&quot;https://twitter.com/Scott_Helme&quot;&gt;Scott Helme&lt;/a&gt;&apos;s &lt;a href=&quot;https://securityheaders.com/&quot;&gt;securityheaders.com&lt;/a&gt;. I was already following Scott on Twitter -I learned a great deal about CSP thanks to him- but although he probably mentioned this tool, I forgot about it.&lt;/p&gt;
&lt;h2&gt;Fixing my sites&lt;/h2&gt;
&lt;p&gt;After the presentation, I decided to check for a couple of my recent sites I built with &lt;a href=&quot;https://www.gatsbyjs.org/&quot;&gt;Gatsby&lt;/a&gt; which I expected to be pretty well covered. After checking, they were pretty well covered, but to my surprise, the &lt;code&gt;Referrer-Polocy&lt;/code&gt; wasn&apos;t set.&lt;/p&gt;
&lt;p&gt;I quickly found that this could be easily set via an option from &lt;a href=&quot;https://www.gatsbyjs.org/packages/gatsby-plugin-netlify/&quot;&gt;&lt;code&gt;gatsby-plugin-netlify&lt;/code&gt;&lt;/a&gt;. I started adding it and set it to the suggested value of &lt;code&gt;&apos;same-origin&apos;&lt;/code&gt;, which seems pretty sensible:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;module.exports = {
  ...
  plugins: [
    ...
    {
      resolve: `gatsby-plugin-netlify`,
      options: {
        headers: {
          &apos;/*&apos;: [&apos;Referrer-Policy: same-origin&apos;],
        },
      },
    },
  ],
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;It all worked fine so I was going to add it to my other website, but then it struck me that maybe I can push this to all websites built with Gatsby by contributing upstream, &amp;amp; multiply my impact! A quick check for &lt;a href=&quot;https://gatsbyjs.org&quot;&gt;gatsbyjs.org&lt;/a&gt; showed similar results as my own sites:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;_gatbsyjs-security-headers-before.png&quot; alt=&quot;Gatsbyjs.org security headers report before&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;Fixing 1000&apos;s of website at once&lt;/h2&gt;
&lt;p&gt;So I went ahead and forked the repository on Github and quickly located &lt;a href=&quot;https://github.com/gatsbyjs/gatsby/tree/master/packages/gatsby-plugin-netlify&quot;&gt;where the Netlify plug-in&lt;/a&gt; is located in their monorepo architecture. Once in there, the plug-in codebase is actually pretty small so it was not hard to find the entry point and where to make the change, &lt;a href=&quot;https://github.com/gatsbyjs/gatsby/blob/5ef65a4a8783a9a81c3680d532432a26d2f4a27d/packages/gatsby-plugin-netlify/src/constants.js#L19-L26&quot;&gt;in their SECURITY_HEADERS constants&lt;/a&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;export const SECURITY_HEADERS = {
     `X-Frame-Options: DENY`,
     `X-XSS-Protection: 1; mode=block`,
     `X-Content-Type-Options: nosniff`,
     `Referrer-Policy: same-origin`,
   ],
 }
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Ideally I wanted to write tests but couldn&apos;t find any for the existing headers. Since the change was small, I decided to open a &lt;a href=&quot;https://github.blog/2019-02-14-introducing-draft-pull-requests/&quot;&gt;draft pull request&lt;/a&gt;, with the plan to fix potential test failures later, but there was none. I marked it as ready for review and at this point I was expecting to wait for a while.&lt;/p&gt;
&lt;h2&gt;Results&lt;/h2&gt;
&lt;p&gt;After this got merged, I wanted to see my change propagated to the &lt;a href=&quot;https://gatsbyjs.org&quot;&gt;gatsbyjs.org&lt;/a&gt; site. I checked again and here it was:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;_gatbsyjs-security-headers-after.png&quot; alt=&quot;Gatsbyjs.org security headers report after&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;Next steps&lt;/h2&gt;
&lt;p&gt;It would be nice to set the CSP, but as Adam pointed in his talk, this is a big one to implement -especially on existing sites- and there is no simple default. Gatsby has &lt;a href=&quot;https://github.com/gatsbyjs/gatsby/issues/10890&quot;&gt;an open issue&lt;/a&gt; to track some of the challenges around CSP.&lt;/p&gt;
&lt;p&gt;I&apos;m planning to add it to my websites and see how this goes, I&apos;ll probably use &lt;a href=&quot;https://github.com/bejamas/gatsby-plugin-csp&quot;&gt;&lt;code&gt;gatsby-plugin-csp&lt;/code&gt;&lt;/a&gt; as it looks promising.&lt;/p&gt;
</content:encoded></item><item><title>New year, new laptop</title><link>https://browniebroke.com/blog/2018-01-14-new-year-new-laptop/</link><guid isPermaLink="true">https://browniebroke.com/blog/2018-01-14-new-year-new-laptop/</guid><description>The story of the clean install of my new laptop</description><pubDate>Sun, 14 Jan 2018 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;A couple of days ago, I accidentally dropped my old laptop on the floor. It was an old
Macbook Pro from mid 2010, which I&apos;ve upgraded a couple of times by swapping a HDD to a SDD
and then by doubling the RAM twice. I had 1TB storage and 16GB of memory, but the old
processor was starting to age. After almost 8 years of service, its life came to an end.&lt;/p&gt;
&lt;p&gt;After weighting the options, I decided to go for a Macbook Pro 15 inches, and while it was being delivered,
I spent some time updating &lt;a href=&quot;https://github.com/browniebroke/macbook-scripts&quot;&gt;my setup scripts&lt;/a&gt;. It used to
be a shell script calling &lt;a href=&quot;https://brew.sh/&quot;&gt;Homebrew&lt;/a&gt; directly, but I recently stumble upon
&lt;a href=&quot;https://github.com/Homebrew/homebrew-bundle&quot;&gt;Homebrew bundle&lt;/a&gt; and decided to use that instead. It was a
good opportunity to review what I no longer needed and what was missing from it.&lt;/p&gt;
&lt;p&gt;When I received the new machine, I downloaded the files from there and ran the entry point
bash script, and it worked great. The settings were restored using
&lt;a href=&quot;https://github.com/lra/mackup&quot;&gt;mackup&lt;/a&gt;, which I&apos;m glad I&apos;ve set it up recently.&lt;/p&gt;
&lt;p&gt;However, I had a lot of environments setup in &lt;a href=&quot;https://insomnia.rest/&quot;&gt;Insomnia&lt;/a&gt;
(an alternative to Postman which I prefer), and sadly mackup didn&apos;t carry them over.
It&apos;s probably not supporting that (yet?), so it might be an opportunity to improve this
great project. My preferences from PyCharm were not fully backed up either, it looks like
something changed recently as other people have reported a similar problem in Jetbrains
products.&lt;/p&gt;
&lt;p&gt;I also use &lt;a href=&quot;https://github.com/KELiON/cerebro/&quot;&gt;Cerebro&lt;/a&gt; which is bit like Spotlight
on steroids. The current Homebrew version (0.3.2) is crashing, so I had to grab the previous
version and install it manually. I couldn&apos;t find how to install a specific cask version.
Hopefully &lt;a href=&quot;https://github.com/KELiON/cerebro/issues/434&quot;&gt;this issue&lt;/a&gt; will be resolved
soon. Also, it looks like Docker for Mac is not installable via Homebrew, I had to go and get
it from their website.&lt;/p&gt;
&lt;p&gt;Lastly, there is still a chicken and egg situation where I need to login to Dropbox first,
which requires the password from 1password, which settings are backed up in mackup.&lt;/p&gt;
&lt;p&gt;I&apos;m very satisfied overall, the setup has been almost entirely automated.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;UPDATE: I&apos;ve submitted &lt;a href=&quot;https://github.com/lra/mackup/pull/1116&quot;&gt;a pull request&lt;/a&gt;
to Mackup to add support for Insomnia, as it was very simple to do, and it just got
merged!&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;UPDATE II: Since the Cerebro issue seems to be stuck, I&apos;ve
&lt;a href=&quot;https://github.com/caskroom/homebrew-cask/pull/43305&quot;&gt;sent a PR&lt;/a&gt; to homebrew-cask
to revert the 0.3.2 upgrade and install 0.3.1 instead.&lt;/em&gt;&lt;/p&gt;
</content:encoded></item><item><title>Hello World!</title><link>https://browniebroke.com/blog/2017-11-29-hello-world/</link><guid isPermaLink="true">https://browniebroke.com/blog/2017-11-29-hello-world/</guid><description>A first post with a welcome message</description><pubDate>Wed, 29 Nov 2017 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Setting up my blog using the &lt;a href=&quot;https://jekyllrb.com&quot;&gt;Jekyll&lt;/a&gt; static site generator
and Github pages. This is using the &lt;a href=&quot;https://github.com/blog/2464-use-any-theme-with-github-pages&quot;&gt;remote theme&lt;/a&gt;
feature and is I&apos;m trying to get up and running with &lt;a href=&quot;https://mmistakes.github.io/minimal-mistakes/&quot;&gt;Minimal Mistakes&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;So far it&apos;s mostly looking good, but there are few issues with listing the posts on the home page.
I might write another post to explain in details.&lt;/p&gt;
</content:encoded></item></channel></rss>