<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Lincoln Loop Blog</title><link>https://lincolnloop.com/blog/</link><description>Lincoln Loop Blog</description><atom:link href="https://lincolnloop.com/blog/feeds/latest/" rel="self"/><language>en</language><lastBuildDate>Fri, 22 Jul 2022 11:55:11 -0500</lastBuildDate><item><title>Python Package Manager Shootout</title><link>https://lincolnloop.com/blog/python-package-manager-shootout/</link><description>
&lt;p&gt;&lt;strong&gt;Please update to our new feed url: &lt;a href="https://lincolnloop.com/blog/feed/"&gt;lincolnloop.com/blog/feed/&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
&lt;hr/&gt;
&lt;p&gt;&lt;strong&gt;tldr;&lt;/strong&gt; I built a benchmark for Python Package managers, you can view it at &lt;a href="https://lincolnloop.github.io/python-package-manager-shootout/"&gt;https://lincolnloop.github.io/python-package-manager-shootout/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;When starting a new Python project, you have a few different options for how you want to manage your dependencies. Like Node.js has &lt;a href="https://docs.npmjs.com/cli/v8/"&gt;&lt;code&gt;npm&lt;/code&gt;&lt;/a&gt;, &lt;a href="https://yarnpkg.com/"&gt;&lt;code&gt;yarn&lt;/code&gt;&lt;/a&gt;, &lt;a href="https://pnpm.io/"&gt;&lt;code&gt;pnpm&lt;/code&gt;&lt;/a&gt;, and &lt;a href="https://bun.sh/"&gt;&lt;code&gt;bun&lt;/code&gt;&lt;/a&gt;, Python has &lt;a href="https://pdm.fming.dev/latest/"&gt;&lt;code&gt;pdm&lt;/code&gt;&lt;/a&gt;, &lt;a href="https://pip-tools.readthedocs.io/"&gt;&lt;code&gt;pip-tools&lt;/code&gt;&lt;/a&gt;, &lt;a href="https://pipenv.pypa.io/"&gt;&lt;code&gt;pipenv&lt;/code&gt;&lt;/a&gt;, and &lt;a href="https://python-poetry.org/"&gt;&lt;code&gt;poetry&lt;/code&gt;&lt;/a&gt; (and others).&lt;/p&gt;

&lt;p&gt;They largely do the same things and fans of each one will usually tout why their choice is better. It&amp;#39;s easy to compare tools based on features, but there isn&amp;#39;t any good way to see how they compare on performance. Slow tooling is a regular source of frustration for developers. How much time are you going to be waiting on packages to install either locally or in CI over the life of the project?&lt;/p&gt;

&lt;p&gt;&lt;a href="https://xkcd.com/303/"&gt;&lt;img src="https://imgs.xkcd.com/comics/compiling.png" alt="compiling"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;With this in mind, I set out to build an impartial benchmark for these projects.&lt;/p&gt;

&lt;h2&gt;A Real World Example&lt;/h2&gt;

&lt;p&gt;The first issue to tackle was finding a sufficiently complex real world set of packages to work against. The pain of slow tooling doesn&amp;#39;t really crop up until your dependency list is sufficiently large and resolving the dependency chain is non-trivial. I found a great sample from the folks at &lt;a href="https://sentry.io"&gt;Sentry&lt;/a&gt;. Their product is largely open source and sufficiently complex that their &lt;a href="https://github.com/getsentry/sentry/blob/3ca31eee26246450d20501764993fc89eb9547ff/requirements-base.txt"&gt;&lt;code&gt;requirements.txt&lt;/code&gt;&lt;/a&gt; made a great corpus to run the benchmark on. It is also interesting in that it includes at least one dependency that requires compilation instead of downloading a bunch of binary/pure-Python packages.&lt;/p&gt;

&lt;h2&gt;Operations to Benchmark&lt;/h2&gt;

&lt;p&gt;All these tools work slightly differently, but the general concepts are more-or-less identical. I decided to measure the following operations:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Tooling installation: How long does it take to install the tool itself?&lt;/li&gt;
&lt;li&gt;Importing requirements: How long does it take to add all the depencencies to the project?&lt;/li&gt;
&lt;li&gt;Locking: How long does it take to generate a lock file (resolve dependency chain, pin all the dependencies, and fetch published hashes for the packages)?&lt;/li&gt;
&lt;li&gt;Install: How long does it take (with both a cold and warm cache) to install all the dependencies?&lt;/li&gt;
&lt;li&gt;Update: How long does it take to update all the dependencies?&lt;/li&gt;
&lt;li&gt;Add Package: How long does it take to add a new package to the list of dependencies (including install &amp;amp; re-lock)?&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;Lies, Damned Lies, and Benchmarks&lt;/h2&gt;

&lt;p&gt;Whenever you publish a benchmark, people will come out of the woodwork to tell you all the ways it is incorrect. I knew I needed to build something that was repeatable and auditable. I hoped package manager maintainers would contribute to the repo to ensure their projects are represented fairly (which they already have!). I also want people to be able to easily add new tools to the benchmark. With that in mind, here&amp;#39;s where I ended up...&lt;/p&gt;

&lt;h3&gt;GitHub Actions&lt;/h3&gt;

&lt;p&gt;Publishing the code used to perform the benchmark was a given, but I wanted to take it a step further. I&amp;#39;m lazy and don&amp;#39;t want to have to re-run the benchmark every time somebody makes a change or releases a new version of a package manager. I also don&amp;#39;t want to update the site with the results manually. So I automated the entire thing.&lt;/p&gt;

&lt;p&gt;GitHub Actions provides everything I needed for the automation. It can re-run the benchmarks on a schedule, then parse the results and update the site (published on GitHub Pages). Since these run on shared hardware and depend on external network resources, there is variability in the results. To combat that, we publish the average results of a few runs.&lt;/p&gt;

&lt;h3&gt;Structuring the Benchmarks&lt;/h3&gt;

&lt;p&gt;I wanted the GitHub workflow file to be machine generated to make it easy to add new tools without an error-prone copy/paste/replace process. I needed some way to abstract the unique commands of each tool into a shared set of commands that could easily be templated. The tool everyone loves to hate, &lt;em&gt;make&lt;/em&gt;, fit the bill perfectly. Our &lt;a href="https://github.com/lincolnloop/python-package-manager-shootout/blob/3118e974dce6c0735f11574babbe2ab2ae9cb10e/Makefile"&gt;&lt;em&gt;Makefile&lt;/em&gt;&lt;/a&gt; provides a lightweight abstraction layer over all the differences. With that in place, I could use a generic template and assemble the workflow file using &lt;a href="https://github.com/lincolnloop/python-package-manager-shootout/blob/3118e974dce6c0735f11574babbe2ab2ae9cb10e/bin/build_workflow.sh"&gt;a simple bash script&lt;/a&gt; with &lt;a href="https://manpages.org/envsubst"&gt;&lt;code&gt;envsubst&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;Timing&lt;/h3&gt;

&lt;p&gt;The &lt;a href="https://manpages.org/time"&gt;&lt;code&gt;time&lt;/code&gt; command&lt;/a&gt; (not the built-in shell function) has everything needed to measure the performance of the individual commands. Wrapping them in &lt;em&gt;make&lt;/em&gt; may create the tiniest bit of overhead, but I suspect it is so small it gets rounded off. &lt;code&gt;time&lt;/code&gt; lets you output to a file in whatever format you want. I made it output as a CSV and collect all the operations into a single file for each package manager.&lt;/p&gt;

&lt;h3&gt;Aggregation&lt;/h3&gt;

&lt;p&gt;Each tools benchmark runs as a separate job in parallel on GitHub Action. When each job completes, it uploads its result CSV as an &lt;a href="https://docs.github.com/en/actions/using-workflows/storing-workflow-data-as-artifacts"&gt;artifact&lt;/a&gt;. When all the jobs complete, we run a final job &lt;a href="https://github.com/lincolnloop/python-package-manager-shootout/blob/15679a2364efc24ea40e86a654d837d34f4baa2f/.github/workflows/benchmark.yml#L311-L317"&gt;which gathers and combines the CSVs into a single file&lt;/a&gt; (thanks &lt;code&gt;sqlite-utils&lt;/code&gt;!) and stores it as a new artifact.&lt;/p&gt;

&lt;p&gt;I leveraged the newer &lt;a href="https://github.blog/2022-05-09-supercharging-github-actions-with-job-summaries/"&gt;job summary&lt;/a&gt; functionality along with &lt;a href="https://pypi.org/project/mdtable/"&gt;&lt;code&gt;mdtable&lt;/code&gt;&lt;/a&gt; to show a pretty representation of the CSVs in the GitHub Actions web interface which has been invaluable for debugging.&lt;/p&gt;

&lt;p&gt;&lt;img src="https://cldup.com/1kjikue8LX.png" alt="github actions screenshot"&gt;&lt;/p&gt;

&lt;h3&gt;Building the Site&lt;/h3&gt;

&lt;p&gt;Once the benchmarks finish, a new workflow is kicked off to rebuild the site with the new data. This involves:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Downloading the aggregated data&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/lincolnloop/python-package-manager-shootout/blob/0d42f7134430e03f8d5b0827d672cea0b7187332/site/csv_to_json.py"&gt;Transforming the data in Python into the format expected by Chart.js&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Building the static site with &lt;a href="https://parceljs.org/"&gt;&lt;code&gt;parcel&lt;/code&gt;&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Generating an open graph image with &lt;a href="https://shot-scraper.datasette.io/en/stable/"&gt;&lt;code&gt;shot-scraper&lt;/code&gt;&lt;/a&gt; so we get a pretty (and accurate) graph preview when the link is shared.&lt;/li&gt;
&lt;li&gt;Pushing the files to &lt;code&gt;gh-pages&lt;/code&gt; and letting it get deployed via GitHub Pages&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;Wrapping Up&lt;/h2&gt;

&lt;p&gt;So far this all seems to plug together quite well. Is it overengineered? Probably a bit 😄 That being said, I&amp;#39;m happy with the results. It&amp;#39;s fully auditable and I don&amp;#39;t have to answer questions about my laptop and internet connection and the position of the moon when the tests were run. It doesn&amp;#39;t require maintaining any infrastructure and uses a lot of basic Linux constructs which will still work years from now. The external dependencies are minimal, so I don&amp;#39;t anticipate it requiring much maintenance.&lt;/p&gt;

&lt;h3&gt;Interpreting the Results&lt;/h3&gt;

&lt;p&gt;All these package managers are the result of thousands of hours volunteered by folks in the Python community. Please don&amp;#39;t use these results to shame or disparage the projects or their maintainers... there&amp;#39;s too much toxicity out there already. My intent with publishing these is that people who are evaluating package managers have access to unbiased performance info. I also hope maintainers find it useful to identify places where these tools have room for improvement (it has already caught one major regression!). A faster Python ecosystem is better for everyone.&lt;/p&gt;

&lt;p&gt;If you have suggestions on how to improve this benchmark, please don&amp;#39;t hesitate to create an Issue or submit a pull request.&lt;/p&gt;
</description><pubDate>Fri, 22 Jul 2022 11:55:11 -0500</pubDate><guid>https://lincolnloop.com/blog/python-package-manager-shootout/</guid></item><item><title>Choosing a Django Version</title><link>https://lincolnloop.com/blog/choosing-django-version/</link><description>
&lt;p&gt;&lt;strong&gt;Please update to our new feed url: &lt;a href="https://lincolnloop.com/blog/feed/"&gt;lincolnloop.com/blog/feed/&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
&lt;hr/&gt;
&lt;p&gt;One of the first things you need to do when starting a new Django project is to choose which version of Django you are going to use. At any given time, there could be as many as three supported Django versions available to choose from:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;previous long-term supported version (LTS)&lt;/li&gt;
&lt;li&gt;current LTS&lt;/li&gt;
&lt;li&gt;latest official version&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;em&gt;I&amp;#39;m excluding pre-releases from this list which should only be used for testing.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;What are you optimizing for?&lt;/h2&gt;

&lt;p&gt;The first question to ask yourself is what are you optimizing for. When we are building applications for our clients we are almost always optimizing to reduce costs, both during the initial development process and in support post-launch. For this reason, we are choosing versions which meet the goals of &lt;em&gt;maximimizing interoperability&lt;/em&gt; with the Django ecosystem and &lt;em&gt;minimizing maintenance&lt;/em&gt; (aka toil) down-the-road. Unless you are working on a hobby project and want to play with new features, I&amp;#39;d argue these goals are universal.&lt;/p&gt;

&lt;h2&gt;Notes on Django Versioning&lt;/h2&gt;

&lt;p&gt;Django does not follow the &lt;a href="https://semver.org/"&gt;semantic versioning standard&lt;/a&gt; strictly. Versions will always be made up of three numbers (&lt;code&gt;major.minor.patch&lt;/code&gt;). Minor version &lt;code&gt;2&lt;/code&gt; is always an LTS version. After the LTS, the major version is incremented and two minor releases are made on it (&lt;code&gt;x.0&lt;/code&gt; and &lt;code&gt;x.1&lt;/code&gt;). Patch versions are released for bugs found on any supported versions. For more info, see &lt;a href="https://github.com/django/deps/blob/main/final/0004-release-schedule.rst"&gt;DEP-0004&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Check &lt;a href="https://www.djangoproject.com/download/#supported-versions"&gt;Supported Versions&lt;/a&gt; to see the current LTS cycle. As of June, 2022 it looks like this,&lt;/p&gt;

&lt;p&gt;&lt;img src="https://media.lincolnloop.com/uploads/20220602-django-supported-versions.png" alt="gantt chart of Django supported versions"&gt;&lt;/p&gt;

&lt;h2&gt;Minimizing Maintenance&lt;/h2&gt;

&lt;p&gt;The best way to minimize future maintenance is to choose an LTS release. The promise of not having to do a major version upgrade for up to three years means fewer upgrade cycles and less maintenance work.&lt;/p&gt;

&lt;h2&gt;Maximizing Interoperability&lt;/h2&gt;

&lt;p&gt;It&amp;#39;s rare to build a Django application that doesn&amp;#39;t depend on other open source Django libraries. Coming across a library that&amp;#39;s the perfect fit for your project only to find it&amp;#39;s not compatible with the version of Django will slow your progress. Being a good open source community member and submitting a patch is the right thing to do, but it takes time and effort that could otherwise be spent on your application.&lt;/p&gt;

&lt;p&gt;Open source projects are usually maintained by volunteers who are working in their free time without compensation. As a result, support can lag behind official Django releases.  If you are starting development shortly after a new LTS release, it may make more sense to use the previous LTS which should have wide adoption and still have close to 9 months of support remaining. If you take this route, read the new LTS release notes carefully to future-proof your project for the jump to the next LTS. In all cases, make sure your code runs without any deprecation warnings.&lt;/p&gt;

&lt;p&gt;Another reason to avoid jumping on a brand new LTS is to avoid any bugs that weren’t caught during testing. I generally prefer to wait for the first patch release to adopt a new LTS version (&lt;code&gt;4.2.1&lt;/code&gt; instead of &lt;code&gt;4.2.0&lt;/code&gt;). These usually show up about a month after the initial release and contain bug fixes found in the wild.&lt;/p&gt;

&lt;h2&gt;Exceptions&lt;/h2&gt;

&lt;p&gt;As with all coding rules-of-thumb, there are exceptions, but avoid getting sucked into the “it’s new-and-shiny” trap. If a newer version of Django has a killer feature that is core to your application, that might be a good reason to jump off an LTS temporarily. An example of this might be the introduction of native JSON support for Postgres in Django 1.9.&lt;/p&gt;

&lt;p&gt;If you’re using minimal dependencies or know all your dependencies support a new LTS version, that might be a good reason to start using an LTS that was recently released. It’s easy to paint yourself into a corner with this approach however. As your project comes to life, you may find you actually &lt;em&gt;do&lt;/em&gt; need a library you didn’t expect when you started.&lt;/p&gt;

&lt;p&gt;Finally, if you&amp;#39;re developing as a hobby or to learn something new, use whatever version you want! Not every development project needs to be dictated by cost or what&amp;#39;s best for the business.&lt;/p&gt;

&lt;hr&gt;

&lt;p&gt;&lt;strong&gt;Tldr;&lt;/strong&gt; Use the latest LTS which has received at least one patch release. The version number will be in the format &lt;code&gt;x.2.z&lt;/code&gt; where &lt;code&gt;z &amp;gt; 0&lt;/code&gt;. This usually isn&amp;#39;t the latest version you can download from PyPI.&lt;/p&gt;
</description><pubDate>Mon, 13 Jun 2022 13:57:15 -0500</pubDate><guid>https://lincolnloop.com/blog/choosing-django-version/</guid></item><item><title>High Performance Django Is Free Online</title><link>https://lincolnloop.com/blog/high-performance-django-free-online/</link><description>
&lt;p&gt;&lt;strong&gt;Please update to our new feed url: &lt;a href="https://lincolnloop.com/blog/feed/"&gt;lincolnloop.com/blog/feed/&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
&lt;hr/&gt;
&lt;p&gt;In 2014 Yann Malet and I (with the help of the rest of the team here) wrote a book about building and scaling Django websites. It was the culmination of things we&amp;#39;d learned from, at the time, close to a decade of experience building, deploying, and supporting Django sites.&lt;/p&gt;

&lt;p&gt;It&amp;#39;s been seven years since its release and it&amp;#39;s fair to say that the book has run its course. While I didn&amp;#39;t feel good about continuing to sell the book given its age, I also believe it still contains a lot of valuable information. As you&amp;#39;d expect, after seven years, many of the technical examples are outdated. The book, however, is largely conceptual. Code snippets are rarely the &amp;quot;secret sauce&amp;quot; when scaling a site. Topics such as caching and reducing/optimizing database queries aren&amp;#39;t usually coding challenges. In fact, thanks to the stability of Django, much of that code is still the same today.&lt;/p&gt;

&lt;p&gt;So instead of relegating the book to the dustbin, we&amp;#39;re releasing it for free online. It&amp;#39;s something we&amp;#39;ve wanted to do for a long time and I&amp;#39;m excited to finally get the book in more people&amp;#39;s hands.&lt;/p&gt;

&lt;p&gt;You can find it on this site at &lt;a href="https://lincolnloop.com/high-performance-django/"&gt;https://lincolnloop.com/high-performance-django/&lt;/a&gt;.&lt;/p&gt;
</description><pubDate>Thu, 30 Sep 2021 16:55:31 -0500</pubDate><guid>https://lincolnloop.com/blog/high-performance-django-free-online/</guid></item><item><title>Documenting Django Design Systems with Storybook</title><link>https://lincolnloop.com/blog/documenting-django-design-systems-storybook/</link><description>
&lt;p&gt;&lt;strong&gt;Please update to our new feed url: &lt;a href="https://lincolnloop.com/blog/feed/"&gt;lincolnloop.com/blog/feed/&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
&lt;hr/&gt;
&lt;p&gt;Our clients come to us with a variety of existing internal teams. Sometimes we work with UI designers or brand-focused graphic designers who deliver pixel-perfect mockups. Other times the stakeholders will describe what their goals are, and we’ll be responsible for more of the design process. In either case, it’s our responsibility to document our work well. This is important both during the course of the project and after our engagement with the client ends. &lt;/p&gt;

&lt;p&gt;Component Libraries or Design Systems provide a lean way to deliver documentation alongside project deliverables at every stage of a project. Often, the frontend tooling that we set up for these systems allows us to get client approval on design or UI deliverables in a much more accurate and reusable way than a high-fidelity Figma or Sketch file would. Once approved, the HTML/CSS is already done and ready for integration within the application code.&lt;/p&gt;

&lt;p&gt;In the past, we have kept this tooling simple, using a static site generator compiled from SASS comments. It provided some minor features to build well-designed documentation, and allowed us to build complicated HTML mockups quickly in a component-driven way. &lt;/p&gt;

&lt;h2&gt;Why switch to Storybook?&lt;/h2&gt;

&lt;p&gt;Depending on the project or the day, I switch between UI/UX tasks and frontend development. That means when I’m doing one task, I’m thinking about the other. For example, when I’m working on frontend, I’ll often form opinions on how best to structure the UI of the CMS for a given component. This is where our static-file-handoff process could use some improvement. &lt;/p&gt;

&lt;p&gt;Let&amp;#39;s take a standard “hero component” as an example. &lt;/p&gt;

&lt;p&gt;&lt;img src="https://media.lincolnloop.com/uploads/hero-component.png" alt="Hero Component Example"&gt;&lt;/p&gt;

&lt;p&gt;If I’m handing off static files to the backend team, I’ll also include a list of modifier classes for various color choices (light or dark text, and an overlay color option for the photo). Then, I’d provide a different list of modifiers to change the position of the text on the photo. If I have opinions on the CMS UI, I’d provide those notes as well. (Toggle the &lt;code&gt;knockout-text&lt;/code&gt; class, add this helper text, and use a dropdown for text alignment.)&lt;/p&gt;

&lt;p&gt;The Django developers will implement the work. This works well enough for us internally. For client approval, the backend team will push the component to a QA server, stand up an example page, and then we’ll start the feedback loop on the component frontend and the CMS UI at the same time. By necessity, that means the frontend team and backend team both need to be on standby for changes. &lt;/p&gt;

&lt;p&gt;This is where Storybook allows us to improve both our internal workflow and our client feedback process. I can work CMS-like UI controls directly into our documentation. The storybook file contains some minor configurations: &lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="k"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Masthead&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kr"&gt;from&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;../app/styleguide-examples/masthead.njk&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;default&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nx"&gt;title&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;Components/Hero&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nx"&gt;argTypes&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;knockout&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;control&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;boolean&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;modifier_class&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nx"&gt;control&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nx"&gt;type&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;select&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nx"&gt;options&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;          &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;          &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;top-right&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;          &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;Top-left&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;          &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;bottom-right&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;          &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;bottom-left&amp;#39;&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Which compiles to a nice UI in Storybook that live-updates the component example:  &lt;/p&gt;

&lt;p&gt;&lt;img src="https://media.lincolnloop.com/uploads/storybook-example.png" alt="Storybook Django example"&gt;&lt;/p&gt;

&lt;p&gt;Once the frontend and documentation are built, I can push a copy to a static server. The client can give feedback on the component and tinker with some form of the CMS UI before involving the backend team. This improves the fidelity of the initial feedback and allows me to provide instructions to the backend team with more confidence.&lt;/p&gt;

&lt;p&gt;Then at the end of the project, the client will own this interactive documentation. It acts as a catalog of the components of their site, complete with inline instructions on best-practices and use-cases for each component. When we work in collaboration with the client on these systems, they help us shape them into a tool that can be used to onboard new content creators, designers, or developers. Sections can be added for editorial style guides, social media assets, or branding guidelines for print, depending on the client needs.&lt;/p&gt;

&lt;h2&gt;Technical considerations&lt;/h2&gt;

&lt;p&gt;Storybook is a React app at its core, but it is compatible with any frontend. If a project will live in React or Vue, then the app components can be used with no duplication. The “story” files will add a bit of context and mock data to your existing components.  For the sake of this “basic Django” experiment, I used the Storybook HTML starter and piped in a nunjucks loader. Nunjucks templates feel very similar to Django or Jinja templates, which makes handoff to the backend team easy. However there are enough minor differences that I can’t recommend trying to use them together. &lt;a href="https://mozilla.github.io/nunjucks/faq.html"&gt;Here are some more details&lt;/a&gt; on the problems that you might encounter. &lt;/p&gt;

&lt;h2&gt;The Verdict&lt;/h2&gt;

&lt;p&gt;Every tool has its strengths and tradeoffs. Storybook is certainly more complicated to set up and maintain than a static site, but its benefits and baked-in interactivity add more than enough value to make it an essential part of new projects.&lt;/p&gt;
</description><pubDate>Fri, 05 Feb 2021 12:19:23 -0600</pubDate><guid>https://lincolnloop.com/blog/documenting-django-design-systems-storybook/</guid></item><item><title>Distributed Locking in Django</title><link>https://lincolnloop.com/blog/distributed-locking-django/</link><description>
&lt;p&gt;&lt;strong&gt;Please update to our new feed url: &lt;a href="https://lincolnloop.com/blog/feed/"&gt;lincolnloop.com/blog/feed/&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
&lt;hr/&gt;
&lt;p&gt;As you start scaling an application out horizontally (adding more servers/instances), you may run into a problem that requires &lt;em&gt;distributed locking&lt;/em&gt;. That&amp;#39;s a fancy term, but the concept is simple. Sometimes you have to be sure that when a block of code is running (usually modifying data somewhere), no other instances runs that same block of code. Ideally, you can design your code not to require locks, but sometimes it is inevitable.&lt;/p&gt;

&lt;p&gt;In general, locks are used when you need to modify state (e.g. the database) in an atomic manner. Some examples of when you might need a distributed lock:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Cron jobs/scheduled tasks whose runtime may exceed the interval with which they are triggered&lt;/li&gt;
&lt;li&gt;Flushing a &lt;a href="https://en.wikipedia.org/wiki/Cache_(computing)#Writing_policies"&gt;write-back cache&lt;/a&gt; to the database.&lt;/li&gt;
&lt;li&gt;Bulk processing files&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If running the code on multiple servers simultaneously would result in corrupted or duplicate data, you probably need to use a distributed lock. The code in question will acquire the lock before execution. Once it has the lock, any other attempt to acquire it will fail.&lt;/p&gt;

&lt;h2&gt;Implementations&lt;/h2&gt;

&lt;p&gt;Lock data must be stored in a location accessible to all application instances. If this is a standard Django site, you probably already have two such systems available to you, the cache and the database. The tricky part about locking is that it must be &lt;em&gt;atomic&lt;/em&gt; to avoid a race condition when two processes try to acquire the lock simultaneously. Thankfully, this is already a solved problem in the standard cache and database backends used by Django. For these backends, you can find third-party libraries which expose the functionality in Python.&lt;/p&gt;

&lt;h3&gt;Redis&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://redis.io/commands/setnx"&gt;&lt;code&gt;SETNX&lt;/code&gt;&lt;/a&gt; is the Redis primitive that is used for locking. The &lt;a href="https://github.com/jazzband/django-redis#locks"&gt;&lt;code&gt;django-redis&lt;/code&gt;&lt;/a&gt; package provides a context manager for this functionality:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;django.core.cache&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;cache&lt;/span&gt;

&lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;cache&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;lock&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;somekey&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;do_some_thing&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;

&lt;h3&gt;Memcached&lt;/h3&gt;

&lt;p&gt;Memcached&amp;#39;s &lt;a href="https://github.com/memcached/memcached/wiki/Commands#add"&gt;&lt;code&gt;ADD&lt;/code&gt;&lt;/a&gt; works similar to Redis&amp;#39; &lt;code&gt;SETNX&lt;/code&gt;. I don&amp;#39;t have experience with any libraries that implement a context manager around this, but both &lt;a href="https://pypi.org/project/django-cache-lock/"&gt;django-cache-lock&lt;/a&gt; and &lt;a href="https://pypi.org/project/sherlock/"&gt;sherlock&lt;/a&gt; appear to provide it.&lt;/p&gt;

&lt;h3&gt;Postgres&lt;/h3&gt;

&lt;p&gt;Postgres has a &lt;a href="https://www.postgresql.org/docs/9.1/functions-admin.html#FUNCTIONS-ADVISORY-LOCKS"&gt;&lt;code&gt;pg_advisory_lock&lt;/code&gt;&lt;/a&gt; function which is utilized by &lt;a href="https://pypi.org/project/django-pglocks/"&gt;django-pglocks&lt;/a&gt;.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;django_pglocks&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;advisory_lock&lt;/span&gt;

&lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;advisory_lock&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;somekey&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;do_some_thing&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;

&lt;h3&gt;MySQL&lt;/h3&gt;

&lt;p&gt;MySQL provides a &lt;a href="https://dev.mysql.com/doc/refman/8.0/en/locking-functions.html"&gt;&lt;code&gt;GET_LOCK&lt;/code&gt;&lt;/a&gt; function for distributed locks. It is exposed via a context manager in the &lt;a href="https://pypi.org/project/django-mysql/"&gt;django-mysql&lt;/a&gt; library.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;django_mysql.locks&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Lock&lt;/span&gt;

&lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;Lock&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;somekey&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;do_some_thing&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;

&lt;h2&gt;Additional Considerations&lt;/h2&gt;

&lt;h3&gt;Timeouts&lt;/h3&gt;

&lt;p&gt;While context managers &lt;em&gt;should&lt;/em&gt; release the lock when they exit, there&amp;#39;s always the possibility that your application crashes before that can happen. Without a timeout on the lock, it will be held forever and prevent any similar code from running. Refer to the docs of the library you choose to see how to specify a timeout. You should set this value to something longer than it should ever take the code to execute, but short enough, it doesn&amp;#39;t prevent successive runs from executing.&lt;/p&gt;

&lt;h3&gt;Encountering a Lock&lt;/h3&gt;

&lt;p&gt;What your application does when it encounters a lock that has already been acquired is going to be specific to its requirements. Most implementations will throw an exception if the lock can&amp;#39;t be acquired, so you&amp;#39;ll usually wrap this code in a &lt;code&gt;try&lt;/code&gt;/&lt;code&gt;except&lt;/code&gt;. Possibilities of what to do in the exception case include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Retry later&lt;/li&gt;
&lt;li&gt;Wait for the lock to be released&lt;/li&gt;
&lt;li&gt;Log an error&lt;/li&gt;
&lt;li&gt;Do nothing&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;Database Row Locking&lt;/h3&gt;

&lt;p&gt;If your code needs exclusive access to modify specific rows in the database and you want to prevent any other modification of those rows while the code executes, &lt;a href="https://en.wikipedia.org/wiki/Record_locking"&gt;database row locking&lt;/a&gt; may be a better option. Django provides functionality for this with the &lt;a href="https://docs.djangoproject.com/en/3.0/ref/models/querysets/#select-for-update"&gt;&lt;code&gt;select_for_update&lt;/code&gt;&lt;/a&gt; queryset method.&lt;/p&gt;

&lt;hr&gt;

&lt;p&gt;Distributed locking sounds like a difficult technical issue, but in general, it is a solved problem. Any Django app should be able to grab a mature off-the-shelf solution to the problem. The only problems to solve are identifying the code that requires a lock and what should happen if a lock is encountered.&lt;/p&gt;
</description><pubDate>Tue, 11 Aug 2020 14:44:19 -0500</pubDate><guid>https://lincolnloop.com/blog/distributed-locking-django/</guid></item><item><title>Goodconf: A Python Configuration Library</title><link>https://lincolnloop.com/blog/goodconf-python-configuration-library/</link><description>
&lt;p&gt;&lt;strong&gt;Please update to our new feed url: &lt;a href="https://lincolnloop.com/blog/feed/"&gt;lincolnloop.com/blog/feed/&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
&lt;hr/&gt;
&lt;p&gt;I&amp;#39;ve been working quite a bit lately on streamlining Lincoln Loop&amp;#39;s standard deployment systems. One thorn we&amp;#39;ve always had is how to handle application configuration.&lt;/p&gt;

&lt;p&gt;In the past, we would have our configuration management system write the configuration out to a JSON file at a known location on the filesystem. The application would read the JSON and set the necessary variables accordingly. This accomplished a few goals:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Deployments didn&amp;#39;t require any sort of modification to the code from the upstream repository.&lt;/li&gt;
&lt;li&gt;Production secrets could be encrypted and stored safely away from the code.&lt;/li&gt;
&lt;li&gt;Unlike environment variables, the data could have proper types (booleans, lists, etc.)&lt;/li&gt;
&lt;li&gt;It avoided using environment variables altogether, which can be problematic from a security perspective &lt;a href="https://diogomonica.com/2017/03/27/why-you-shouldnt-use-env-variables-for-secret-data/"&gt;in many scenarios&lt;/a&gt;.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;That being said, the setup had some downfalls as well.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The configuration variables needed were not well documented. You had to read through the code to understand how and where they were used.&lt;/li&gt;
&lt;li&gt;We were maintaining a separate Django settings module for local and deployed environments since the file wasn&amp;#39;t used locally.&lt;/li&gt;
&lt;li&gt;The configuration file was not friendly for other services like Heroku or Docker where environment variables are typically used.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Basically, I want to use our Python projects like I would expect to use any other software. Install the software, create a configuration file, and run.&lt;/p&gt;

&lt;h2&gt;Introducing Goodconf&lt;/h2&gt;

&lt;p&gt;I wrote &lt;a href="https://github.com/lincolnloop/goodconf"&gt;Goodconf&lt;/a&gt; (with lots of help from &lt;a href="https://github.com/smileychris"&gt;Chris Beaven&lt;/a&gt;) to solve some of our issues in a reusable library.&lt;/p&gt;

&lt;p&gt;Goodconf is inspired by &lt;a href="https://github.com/globocom/derpconf"&gt;derpconf&lt;/a&gt; and &lt;a href="https://github.com/dcramer/logan"&gt;logan&lt;/a&gt; which are spun out of &lt;a href="http://thumbor.org/"&gt;Thumbor&lt;/a&gt; and &lt;a href="https://github.com/getsentry/sentry"&gt;Sentry&lt;/a&gt; respectively.&lt;/p&gt;

&lt;p&gt;With Goodconf, you create a class which defines all the configuration values your application expects to receive. They can have default values and help text which can be used to generate documentation.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;goodconf&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;GoodConf&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Value&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;MyConf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;GoodConf&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="s2"&gt;&amp;quot;Configuration for My App&amp;quot;&lt;/span&gt;
    &lt;span class="n"&gt;DEBUG&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Value&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;default&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;False&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;help&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Toggle debugging.&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;DATABASE_URL&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Value&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;default&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;postgres://localhost:5432/mydb&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;help&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Database connection.&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;SECRET_KEY&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Value&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;initial&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="k"&gt;lambda&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;base64&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;b64encode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;urandom&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;60&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;decode&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
        &lt;span class="n"&gt;help&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Used for cryptographic signing. &amp;quot;&lt;/span&gt;
        &lt;span class="s2"&gt;&amp;quot;https://docs.djangoproject.com/en/2.0/ref/settings/#secret-key&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;You can then instantiate the class like so:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="n"&gt;config&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;MyConf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;file_env_var&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;MYAPP_CONF&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;default_files&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;/etc/myapp/myapp.yml&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;myapp.yml&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;This lets you define the default location for configuration files (&lt;code&gt;/etc/myapp/myapp.yml&lt;/code&gt; or &lt;code&gt;$(pwd)/myapp.yml&lt;/code&gt; if that doesn&amp;#39;t exist) and also an environment variable that can be used to load a file from a different location.&lt;/p&gt;

&lt;p&gt;There is also a Django helper which lets you do &lt;code&gt;manage.py --config=/path/to/config.yml ...&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;With the configuration defined, you simply need to load it and start using it. Loading the config will try to read from the configuration files and fallback on environment variables (cast to the proper Python type) if none are found.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;load&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="n"&gt;SECRET_KEY&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;SECRET_KEY&lt;/span&gt;
&lt;span class="o"&gt;...&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;My favorite feature of Goodconf is the output you can generate from a config class:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;config.generate_yaml()&lt;/code&gt; boilerplate YAML config with help text as comments&lt;/li&gt;
&lt;li&gt;&lt;code&gt;config.generate_json()&lt;/code&gt; boilerplate JSON config&lt;/li&gt;
&lt;li&gt;&lt;code&gt;config.generate_markdown()&lt;/code&gt; Documentation in Markdown format&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This lets you quickly generate a config file for local development and documentation to drop into a &lt;code&gt;README.md&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;Using Goodconf&lt;/h2&gt;

&lt;p&gt;Goodconf&amp;#39;s &lt;a href="https://github.com/lincolnloop/goodconf#readme"&gt;README&lt;/a&gt; has some examples of how to use the library, but if you&amp;#39;re like me, it&amp;#39;s easier to see an example. Check out our &lt;a href="https://github.com/lincolnloop/saltdash"&gt;saltdash repo&lt;/a&gt; for an example of using Goodconf in a Django project.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/lincolnloop/saltdash/blob/master/saltdash/__init__.py"&gt;&lt;code&gt;saltdash/__init__.py&lt;/code&gt;&lt;/a&gt; defines the configuration&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/lincolnloop/saltdash/blob/master/saltdash/settings/__init__.py"&gt;&lt;code&gt;saltdash/settings/__init__.py&lt;/code&gt;&lt;/a&gt; shows how to use it to define Django settings&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/lincolnloop/saltdash/blob/master/setup.cfg"&gt;&lt;code&gt;setup.cfg&lt;/code&gt;&lt;/a&gt; (under &lt;code&gt;[options.entry_points]&lt;/code&gt;) shows how to install scripts for the management command and configuration generator&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;Try it Out&lt;/h2&gt;

&lt;p&gt;We&amp;#39;re using Goodconf in production now and happy with the results. We hope you&amp;#39;ll try it out on your own projects. If you do, please give us feedback here or in GitHub.&lt;/p&gt;
</description><pubDate>Wed, 29 Jul 2020 11:02:49 -0500</pubDate><guid>https://lincolnloop.com/blog/goodconf-python-configuration-library/</guid></item><item><title>Python Dependency Locking with pip-tools</title><link>https://lincolnloop.com/blog/python-dependency-locking-pip-tools/</link><description>
&lt;p&gt;&lt;strong&gt;Please update to our new feed url: &lt;a href="https://lincolnloop.com/blog/feed/"&gt;lincolnloop.com/blog/feed/&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
&lt;hr/&gt;
&lt;p&gt;Two of the biggest benefits &lt;code&gt;pipenv&lt;/code&gt; and &lt;code&gt;poetry&lt;/code&gt; are dependency locking and hash checking. Dependency locking means you can specify the direct dependencies your code requires, for example, &lt;code&gt;celery==4.4.*&lt;/code&gt; and the tooling will lock, not only &lt;code&gt;celery&lt;/code&gt; to a specific version, but also every dependency pulled in by &lt;code&gt;celery&lt;/code&gt;. Hash checking ensures the package you download matches a hash of the package when it was first downloaded. This ensures that when &lt;code&gt;celery&lt;/code&gt; (or any of its dependencies) are installed on a different system, they match the same file hash as when they were set up by the developer. If the package is in some way tampered with between the two installs, hash checking will fail, preventing the new (and potentially malicious) package from being installed.&lt;/p&gt;

&lt;p&gt;The combination of these two features makes your project installation &lt;em&gt;deterministic&lt;/em&gt;: given the same requirements/lock file, you&amp;#39;ll always get the same set of packages installed. This prevents all manner of issues when dependencies can shift underneath the code between developers&amp;#39; machines, CI, and deployment. It&amp;#39;s a huge improvement over how we previously managed dependencies.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;Pipenv&lt;/code&gt; and &lt;code&gt;poetry&lt;/code&gt; have many other features, but I find myself rarely using them. Managing Python virtual environments is nice, but for experienced Python developers, skipping &lt;code&gt;python -m venv .venv&lt;/code&gt; before installing a project is not game-changing. I have limited experience with &lt;code&gt;poetry&lt;/code&gt;, but the performance of &lt;code&gt;pipenv&lt;/code&gt; and edge-case bugs have been a regular sore point in the projects we use it on.&lt;/p&gt;

&lt;h2&gt;Locking Dependencies with pip-compile&lt;/h2&gt;

&lt;p&gt;If you&amp;#39;re willing to give up the bells-and-whistles, &lt;code&gt;pip-compile&lt;/code&gt; (provided by &lt;a href="https://pypi.org/project/pip-tools/"&gt;&lt;code&gt;pip-tools&lt;/code&gt;&lt;/a&gt;) is a viable alternative. It provides both locking and hash-pinning for your dependencies. Once installed, you&amp;#39;ll typically create a &lt;code&gt;requirements.in&lt;/code&gt; file. This is where you define your project&amp;#39;s top-level dependencies (similar to pipenv&amp;#39;s &lt;code&gt;Pipfile&lt;/code&gt; or &lt;code&gt;pyproject.toml&lt;/code&gt; in poetry). A basic example might look something like this:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;Django==2.2.*
psycopg2
celery&gt;4.4&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;To &amp;quot;lock&amp;quot; these dependencies, you can run:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;pip-compile --generate-hashes --output-file&lt;span class="o"&gt;=&lt;/span&gt;requirements.txt requirements.in
&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;This generates the standard &lt;code&gt;requirements.txt&lt;/code&gt; file with all dependencies and includes package hashes provided by PyPI. Here&amp;#39;s a line from that file:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;pytz==2019.3 \
    --hash=sha256:1c557d7d0e871de1f5ccd5833f60fb2550652da6be2693c1e02300743d21500d \
    --hash=sha256:b02c06db6cf09c12dd25137e563b31700d3b80fcc4ad23abb7a315f2789819be \
    # via django&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Note we didn&amp;#39;t have &lt;code&gt;pytz&lt;/code&gt; in our &lt;code&gt;requirements.in&lt;/code&gt;, but it&amp;#39;s included in &lt;code&gt;requirements.txt&lt;/code&gt; because it is required by &lt;code data-syntax="text-only"&gt;django&lt;/code&gt; (which the &lt;code&gt;pip-compile&lt;/code&gt; is kind enough to output in the file).&lt;/p&gt;

&lt;p&gt;If your project uses a &lt;code data-syntax="text-only"&gt;Makefile&lt;/code&gt;, this workflow is well suited for it. The following will allow you to run &lt;code&gt;make requirements.txt&lt;/code&gt; and it will be updated if and only if the &lt;code&gt;requirements.in&lt;/code&gt; file has changed since &lt;code&gt;requirements.txt&lt;/code&gt; was last generated:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="nf"&gt;requirements.txt&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;requirements&lt;/span&gt;.&lt;span class="n"&gt;in&lt;/span&gt;
    pip-compile --upgrade --generate-hashes --output-file&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nv"&gt;$@&lt;/span&gt; requirements.in
&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;&lt;code&gt;pip-compile&lt;/code&gt; also provides the option for selectively upgrading individual packages with the &lt;code&gt;--upgrade-package&lt;/code&gt; argument &lt;a href="https://github.com/jazzband/pip-tools/#updating-requirements"&gt;as noted in their &lt;code&gt;README.md&lt;/code&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;Installing Dependencies&lt;/h2&gt;

&lt;p&gt;Installing the dependencies is as simple as:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;pip install -r requirements.txt
&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;You can also use &lt;code&gt;pip-sync&lt;/code&gt; (included in &lt;code&gt;pip-tools&lt;/code&gt;) to reconcile an existing virtual environment with whatever exists in &lt;code&gt;requirements.txt&lt;/code&gt;. Not only will this install/upgrade packages as needed, but also remove packages that are no longer in &lt;code&gt;requirements.txt&lt;/code&gt;.&lt;/p&gt;

&lt;hr&gt;

&lt;p&gt;&lt;code&gt;pipenv&lt;/code&gt; and &lt;code&gt;poetry&lt;/code&gt; get a lot of attention in the Python community, and rightfully so. They are great projects that certainly scratch an itch for developers. That being said, if you&amp;#39;re just looking for something to handle dependency management, &lt;code&gt;pip-tools&lt;/code&gt; may be all you need.&lt;/p&gt;
</description><pubDate>Wed, 22 Jul 2020 13:06:57 -0500</pubDate><guid>https://lincolnloop.com/blog/python-dependency-locking-pip-tools/</guid></item><item><title>User-Generated Themes with Django and CSS Variables</title><link>https://lincolnloop.com/blog/user-generated-themes-django-css/</link><description>
&lt;p&gt;&lt;strong&gt;Please update to our new feed url: &lt;a href="https://lincolnloop.com/blog/feed/"&gt;lincolnloop.com/blog/feed/&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
&lt;hr/&gt;
&lt;p&gt;Consider a “white labeled” app where a CMS admin can customize the design of their public-facing dashboard. The developer is tasked with saving a handful of user-populated values to the database. Colors, fonts, and perhaps a background image or two now need to override dozens of CSS properties throughout the system. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/django-compressor/django-compressor"&gt;Django Compressor&lt;/a&gt; has historically been great for this type of task. The workflow would look something like this: &lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Build a SASS file/Django template hybrid that looks like this:&lt;/li&gt;
&lt;/ol&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="x"&gt;$brand-color: &lt;/span&gt;&lt;span class="cp"&gt;{{&lt;/span&gt; &lt;span class="nv"&gt;user.brand_color&lt;/span&gt; &lt;span class="cp"&gt;}}&lt;/span&gt;&lt;span class="x"&gt;;&lt;/span&gt;
&lt;span class="x"&gt;$link-color: &lt;/span&gt;&lt;span class="cp"&gt;{{&lt;/span&gt; &lt;span class="nv"&gt;user.link_color&lt;/span&gt; &lt;span class="cp"&gt;}}&lt;/span&gt;&lt;span class="x"&gt;;&lt;/span&gt;
&lt;span class="x"&gt;$font-family: &lt;/span&gt;&lt;span class="cp"&gt;{{&lt;/span&gt; &lt;span class="nv"&gt;user.font_family&lt;/span&gt; &lt;span class="cp"&gt;}}&lt;/span&gt;&lt;span class="x"&gt;;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Pipe that file into Django Compressor and build a fresh full copy of the app CSS using these variables instead of the defaults. &lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Inject a link to the new CSS file, if it exists, underneath the base app file to let the CSS cascade override everything properly. &lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This is sub-optimal in 2020 for several reasons. It creates unnecessary duplicative code. It adds a potential point-of-failure on the server. Using modern CSS, there are ways to make the browser do all of the heavy lifting. &lt;/p&gt;

&lt;h2&gt;Enter CSS variables&lt;/h2&gt;

&lt;p&gt;CSS variables are a perfect fit for this task if user themes don’t need to support Internet Explorer. (If IE is mandatory, &lt;a href="https://github.com/jhildenbiddle/css-vars-ponyfill"&gt;css-vars-ponyfill&lt;/a&gt; may help you get the job done.) With CSS variables, it’s possible to override variables using the cascade, so we can avoid the preprocessor entirely. It’s also possible to make this change in a fairly surgical way, without completely refactoring your stylesheets away from SASS or LESS. Note that this is not how I would start a new project. Instead, I would begin with CSS variables as a first-class citizen. This is simply a safe way to make this change to an older codebase.&lt;/p&gt;

&lt;h2&gt;Update the base SCSS file&lt;/h2&gt;

&lt;p&gt;First, we’ll update the base SCSS file to use CSS variables, with a fallback of the original values. The original &lt;code&gt;_variables.scss&lt;/code&gt; file looks like this:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="na"&gt;$heading-color&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mh"&gt;#222&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;!default&lt;/span&gt;&lt;span class="err"&gt;;&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="na"&gt;$link-color&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mh"&gt;#33C&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;!default&lt;/span&gt;&lt;span class="err"&gt;;&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="na"&gt;$font-family&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Lato&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;sans-serif&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;!default&lt;/span&gt;&lt;span class="err"&gt;;&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;To keep those default values and have them overridden later with CSS variables, we need to inject the CSS variable syntax:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="na"&gt;$heading-color&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;--&lt;/span&gt;&lt;span class="n"&gt;heading-color&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mh"&gt;#222&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="err"&gt;;&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="na"&gt;$link-color&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;--&lt;/span&gt;&lt;span class="n"&gt;link-color&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mh"&gt;#33C&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="err"&gt;;&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="na"&gt;$font-family&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;--&lt;/span&gt;&lt;span class="no"&gt;font-family&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Lato, sans-serif&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="err"&gt;;&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;If no CSS variables are defined, the original values will be used. With these changes made, it’s time to cross your fingers and hope the CSS compiles.&lt;/p&gt;

&lt;p&gt;It might not.&lt;/p&gt;

&lt;p&gt;If the SCSS source uses color functions throughout the project, there will be errors. For example, SASS won’t know what to do with &lt;code&gt;mix($black, $heading-color, 20%)&lt;/code&gt; since &lt;code&gt;$heading-color&lt;/code&gt; no longer resolves to computable color values. These will require manual refactoring. Solutions vary greatly depending on how colors are structured in the SCSS. This could be solved with &lt;a href="https://css-tricks.com/css-custom-properties-theming/"&gt;a clever use of RGBA gradient overlays&lt;/a&gt;, going all-in with a &lt;a href="https://blog.jim-nielsen.com/2019/generating-shades-of-color-using-css-variables/"&gt;CSS-variable approach to colors&lt;/a&gt;, or making more variables in &lt;code&gt;_variables.scss&lt;/code&gt; to consolidate this work and put all of the color math in one place. Unfortunately, SASS color functions are still much more elegant than trying to do this work in CSS, so every solution has tradeoffs.&lt;/p&gt;

&lt;h2&gt;Update the Django template&lt;/h2&gt;

&lt;p&gt;Now that the SASS is ready, the base CSS file can be compiled with modern frontend-only tools, and CSS variables would only be used for the theme overrides. The Django template could be a template file that compiles to a CSS file (which would be best for page-caching reasons), or an inlined &lt;code&gt;&amp;lt;style&amp;gt;&lt;/code&gt; tag in the dashboard HTML template:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="cp"&gt;{%&lt;/span&gt; &lt;span class="k"&gt;block&lt;/span&gt; &lt;span class="nv"&gt;append_head&lt;/span&gt; &lt;span class="cp"&gt;%}&lt;/span&gt;&lt;span class="x"&gt;&lt;/span&gt;
&lt;span class="x"&gt;&amp;lt;style&amp;gt;&lt;/span&gt;
&lt;span class="x"&gt;:root {&lt;/span&gt;
&lt;span class="x"&gt;    &lt;/span&gt;&lt;span class="cp"&gt;{%&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nv"&gt;user.heading_color&lt;/span&gt; &lt;span class="cp"&gt;%}&lt;/span&gt;&lt;span class="x"&gt; &lt;/span&gt;
&lt;span class="x"&gt;    --heading-color: &lt;/span&gt;&lt;span class="cp"&gt;{{&lt;/span&gt; &lt;span class="nv"&gt;user.heading_color&lt;/span&gt; &lt;span class="cp"&gt;}}&lt;/span&gt;&lt;span class="x"&gt;; &lt;/span&gt;
&lt;span class="x"&gt;    &lt;/span&gt;&lt;span class="cp"&gt;{%&lt;/span&gt; &lt;span class="k"&gt;endif&lt;/span&gt; &lt;span class="cp"&gt;%}&lt;/span&gt;&lt;span class="x"&gt;&lt;/span&gt;
&lt;span class="x"&gt;    &lt;/span&gt;&lt;span class="cp"&gt;{%&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nv"&gt;user.link_color&lt;/span&gt; &lt;span class="cp"&gt;%}&lt;/span&gt;&lt;span class="x"&gt; &lt;/span&gt;
&lt;span class="x"&gt;    --link-color: &lt;/span&gt;&lt;span class="cp"&gt;{{&lt;/span&gt; &lt;span class="nv"&gt;user.link_color&lt;/span&gt; &lt;span class="cp"&gt;}}&lt;/span&gt;&lt;span class="x"&gt;; &lt;/span&gt;
&lt;span class="x"&gt;    &lt;/span&gt;&lt;span class="cp"&gt;{%&lt;/span&gt; &lt;span class="k"&gt;endif&lt;/span&gt; &lt;span class="cp"&gt;%}&lt;/span&gt;&lt;span class="x"&gt;&lt;/span&gt;
&lt;span class="x"&gt;    &lt;/span&gt;&lt;span class="cp"&gt;{%&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nv"&gt;user.font_family&lt;/span&gt; &lt;span class="cp"&gt;%}&lt;/span&gt;&lt;span class="x"&gt; &lt;/span&gt;
&lt;span class="x"&gt;    --font-family: &lt;/span&gt;&lt;span class="cp"&gt;{{&lt;/span&gt; &lt;span class="nv"&gt;user.font_family&lt;/span&gt; &lt;span class="cp"&gt;}}&lt;/span&gt;&lt;span class="x"&gt;;&lt;/span&gt;
&lt;span class="x"&gt;    &lt;/span&gt;&lt;span class="cp"&gt;{%&lt;/span&gt; &lt;span class="k"&gt;endif&lt;/span&gt; &lt;span class="cp"&gt;%}&lt;/span&gt;&lt;span class="x"&gt;&lt;/span&gt;
&lt;span class="x"&gt;}&lt;/span&gt;
&lt;span class="x"&gt;&amp;lt;/style&amp;gt;&lt;/span&gt;
&lt;span class="cp"&gt;{%&lt;/span&gt; &lt;span class="k"&gt;endblock&lt;/span&gt; &lt;span class="nv"&gt;append_head&lt;/span&gt; &lt;span class="cp"&gt;%}&lt;/span&gt;&lt;span class="x"&gt;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;

&lt;h2&gt;Wrap it up, or keep going&lt;/h2&gt;

&lt;p&gt;This is a small example to begin working with CSS custom properties. It’s a powerful technology with &lt;a href="https://caniuse.com/#feat=css-variables"&gt;great browser support&lt;/a&gt;, and it’s been a game-changer here at Lincoln Loop. So if it’s not a part of your regular tool belt yet, it’s time to &lt;a href="https://css-tricks.com/advantages-native-css-variables/"&gt;give it a spin&lt;/a&gt;.&lt;/p&gt;
</description><pubDate>Wed, 24 Jun 2020 10:37:16 -0500</pubDate><guid>https://lincolnloop.com/blog/user-generated-themes-django-css/</guid></item><item><title>Dissecting a Python Zipapp Built with Shiv</title><link>https://lincolnloop.com/blog/dissecting-python-zipapp-built-shiv/</link><description>
&lt;p&gt;&lt;strong&gt;Please update to our new feed url: &lt;a href="https://lincolnloop.com/blog/feed/"&gt;lincolnloop.com/blog/feed/&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
&lt;hr/&gt;
&lt;p&gt;In &lt;a href="https://lincolnloop.com/blog/single-file-python-django-deployments/"&gt;a previous post&lt;/a&gt;, we showed how to use &lt;a href="https://shiv.readthedocs.io/"&gt;shiv&lt;/a&gt; to bundle a Django project into a single file for distribution and deployment. Running a large Python project as a single file feels like magic -- which is great until you need to debug a problem. At that point, you need to understand how things work and what is happening under the hood. With that in mind, let&amp;#39;s demystify shiv&amp;#39;s magic.&lt;/p&gt;

&lt;h2&gt;Zipapps&lt;/h2&gt;

&lt;p&gt;Shiv uses a little known feature of Python called a &lt;a href="https://www.python.org/dev/peps/pep-0441/"&gt;ZIP application&lt;/a&gt; or &amp;quot;zipapp&amp;quot;. Zipapps provide a way to bundle multiple Python files into a single ZIP archive which the Python interpreter can then execute. Here&amp;#39;s a trivial example:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;$ mkdir myapp
$ &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;print(&amp;quot;hello world&amp;quot;)&amp;#39;&lt;/span&gt; &amp;gt; myapp/__main__.py
$ python -m zipapp myapp
$ python myapp.pyz
hello world
&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Pretty cool, huh? Copy &lt;code&gt;myapp.pyz&lt;/code&gt; anywhere you have a compatible version of Python and it just works.&lt;/p&gt;

&lt;p&gt;But real-world projects aren&amp;#39;t so simple. They have dependencies, non-Python files that need to be included, and complex build steps.&lt;/p&gt;

&lt;h2&gt;Enter Shiv&lt;/h2&gt;

&lt;p&gt;The folks at LinkedIn built shiv to work around the shortcomings of traditional zipapps. It includes dependencies in the zipapp and ensures they are on the &lt;code&gt;PYTHONPATH&lt;/code&gt; at runtime. Shiv is only needed for creation. The resulting zipapp is executable without any additional tooling. You can think shiv as a wrapper around &lt;code&gt;pip&lt;/code&gt; which it uses behind the scenes to download and install dependencies. Here is an example of creating a zipapp for &lt;code&gt;awscli&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;$ shiv --output-file&lt;span class="o"&gt;=&lt;/span&gt;aws.pyz --entry-point&lt;span class="o"&gt;=&lt;/span&gt;awscli.clidriver.main awscli
&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;This downloads &lt;code&gt;awscli&lt;/code&gt; and all its dependencies from PyPI and creates a zipapp (&lt;code&gt;aws.pyz&lt;/code&gt;) which will run a Python function specified by &lt;code&gt;--entry-point&lt;/code&gt; on execution. That file can be copied to any system with a compatible Python version and executed as if it were a binary executable (&lt;code&gt;./aws.pyz&lt;/code&gt; or &lt;code&gt;python aws.pyz&lt;/code&gt;).&lt;/p&gt;

&lt;h3&gt;Under the Hood&lt;/h3&gt;

&lt;p&gt;The good news is that &lt;code&gt;shiv&lt;/code&gt; is surprisingly simple. When we build a new archive, &lt;code&gt;shiv&lt;/code&gt; does the following:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Use &lt;code&gt;pip&lt;/code&gt; to download all the dependencies to a temporary directory&lt;/li&gt;
&lt;li&gt;Write some metadata used during the bootstrap process to &lt;code&gt;environment.json&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Create a bootstrap script that is used when the zipapp is executed.&lt;/li&gt;
&lt;li&gt;Bundle those files up into zip archive&lt;/li&gt;
&lt;li&gt;Insert a &lt;a href="https://en.wikipedia.org/wiki/Shebang_(Unix)"&gt;shebang&lt;/a&gt; at the top of the zip archive so it can be used as an executable.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The bootstrap script&amp;#39;s responsibility is to:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Unpack the dependencies to a unique path. This is skipped if it already exists from a previous run.&lt;/li&gt;
&lt;li&gt;Insert that path into the &lt;code&gt;PYTHONPATH&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Execute the &lt;code&gt;--entry-point&lt;/code&gt; we defined during creation.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The unique path is determined by combining the filename of the zipapp and a UUID generated at build time (stored in &lt;code&gt;environment.json&lt;/code&gt;). It is stored in &lt;code&gt;~/.shiv&lt;/code&gt; by default but can be changed by setting the &lt;code&gt;SHIV_ROOT&lt;/code&gt; environment variable. After execution, we can see the following:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="nb"&gt;cd&lt;/span&gt; ~/.shiv &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; find . -maxdepth &lt;span class="m"&gt;2&lt;/span&gt;
.
./aws_3e32a16c-6652-44cc-a561-3784814d736e
./aws_3e32a16c-6652-44cc-a561-3784814d736e/site-packages
&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;The &lt;code&gt;site-packages&lt;/code&gt; directory looks just like the &lt;code&gt;site-packages&lt;/code&gt; directory you&amp;#39;d find for a standard Python installation or a virtualenv. As we can see, this was assigned a UUID of &lt;code&gt;3e32a16c-6652-44cc-a561-3784814d736e&lt;/code&gt; at build time which can be confirmed by inspecting the included metadata:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;$ unzip -p aws.pyz environment.json &lt;span class="p"&gt;|&lt;/span&gt; jq .build_id
&lt;span class="s2"&gt;&amp;quot;3e32a16c-6652-44cc-a561-3784814d736e&amp;quot;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;This directory is added to the &lt;code&gt;PYTHONPATH&lt;/code&gt; like so:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;$ &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;import sys, pprint; pprint.pprint(sys.path)&amp;quot;&lt;/span&gt; &lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nv"&gt;SHIV_INTERPRETER&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt; ./aws.pyz
Python &lt;span class="m"&gt;3&lt;/span&gt;.7.3 &lt;span class="o"&gt;(&lt;/span&gt;default, Jun &lt;span class="m"&gt;11&lt;/span&gt; &lt;span class="m"&gt;2019&lt;/span&gt;, &lt;span class="m"&gt;01&lt;/span&gt;:05:09&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="o"&gt;[&lt;/span&gt;GCC &lt;span class="m"&gt;6&lt;/span&gt;.3.0 &lt;span class="m"&gt;20170516&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt; on linux
Type &lt;span class="s2"&gt;&amp;quot;help&amp;quot;&lt;/span&gt;, &lt;span class="s2"&gt;&amp;quot;copyright&amp;quot;&lt;/span&gt;, &lt;span class="s2"&gt;&amp;quot;credits&amp;quot;&lt;/span&gt; or &lt;span class="s2"&gt;&amp;quot;license&amp;quot;&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; more information.
&lt;span class="o"&gt;(&lt;/span&gt;InteractiveConsole&lt;span class="o"&gt;)&lt;/span&gt;
&amp;gt;&amp;gt;&amp;gt; &lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;./aws.pyz&amp;#39;&lt;/span&gt;,
 &lt;span class="s1"&gt;&amp;#39;/usr/local/lib/python37.zip&amp;#39;&lt;/span&gt;,
 &lt;span class="s1"&gt;&amp;#39;/usr/local/lib/python3.7&amp;#39;&lt;/span&gt;,
 &lt;span class="s1"&gt;&amp;#39;/usr/local/lib/python3.7/lib-dynload&amp;#39;&lt;/span&gt;,
 &lt;span class="s1"&gt;&amp;#39;/home/user/.shiv/aws_3e32a16c-6652-44cc-a561-3784814d736e/site-packages&amp;#39;&lt;/span&gt;,
 &lt;span class="s1"&gt;&amp;#39;/usr/local/lib/python3.7/site-packages&amp;#39;&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;&lt;em&gt;Note: The environment variable &lt;code&gt;SHIV_INTERPRETER&lt;/code&gt; allows us to drop down into a Python shell using the zipapp&amp;#39;s environment.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;The fifth item, &lt;code&gt;/home/user/.shiv/...&lt;/code&gt; is the one that was injected in via the bootstrap script. The others are Python defaults.&lt;/p&gt;

&lt;p&gt;If you prefer to run in an isolated environment without the global site packages, Python&amp;#39;s &lt;code&gt;-S&lt;/code&gt; flag can be used:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;$ &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;import sys, pprint; pprint.pprint(sys.path)&amp;quot;&lt;/span&gt; &lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nv"&gt;SHIV_INTERPRETER&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt; python -S aws.pyz
Python &lt;span class="m"&gt;3&lt;/span&gt;.7.3 &lt;span class="o"&gt;(&lt;/span&gt;default, Jun &lt;span class="m"&gt;11&lt;/span&gt; &lt;span class="m"&gt;2019&lt;/span&gt;, &lt;span class="m"&gt;01&lt;/span&gt;:05:09&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="o"&gt;[&lt;/span&gt;GCC &lt;span class="m"&gt;6&lt;/span&gt;.3.0 &lt;span class="m"&gt;20170516&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt; on linux
Type &lt;span class="s2"&gt;&amp;quot;help&amp;quot;&lt;/span&gt;, &lt;span class="s2"&gt;&amp;quot;copyright&amp;quot;&lt;/span&gt;, &lt;span class="s2"&gt;&amp;quot;credits&amp;quot;&lt;/span&gt; or &lt;span class="s2"&gt;&amp;quot;license&amp;quot;&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; more information.
&lt;span class="o"&gt;(&lt;/span&gt;InteractiveConsole&lt;span class="o"&gt;)&lt;/span&gt;
&amp;gt;&amp;gt;&amp;gt; &lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;aws.pyz&amp;#39;&lt;/span&gt;,
 &lt;span class="s1"&gt;&amp;#39;/usr/local/lib/python37.zip&amp;#39;&lt;/span&gt;,
 &lt;span class="s1"&gt;&amp;#39;/usr/local/lib/python3.7&amp;#39;&lt;/span&gt;,
 &lt;span class="s1"&gt;&amp;#39;/usr/local/lib/python3.7/lib-dynload&amp;#39;&lt;/span&gt;,
 &lt;span class="s1"&gt;&amp;#39;/home/user/.shiv/aws_3e32a16c-6652-44cc-a561-3784814d736e/site-packages&amp;#39;&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Once unpacked, you can inspect the files on disk and even edit them if you&amp;#39;re trying to do some tricky debugging. Shiv will not overwrite the files of a previously unpacked zipapp unless the environment variable &lt;code&gt;SHIV_FORCE_EXTRACT&lt;/code&gt; is set.&lt;/p&gt;

&lt;h2&gt;That&amp;#39;s It&lt;/h2&gt;

&lt;p&gt;Turns out shiv is pretty straightforward. One of the things I like most about it is its simplicity. When there&amp;#39;s a problem with &lt;em&gt;my&lt;/em&gt; code, it&amp;#39;s easy to poke around and rule out shiv as a cause. The code is well commented and easy to follow (here&amp;#39;s &lt;a href="https://github.com/linkedin/shiv/blob/20347df4617daaaaa5b81024439d194217ee5be9/src/shiv/bootstrap/__init__.py"&gt;the bootstrap script&lt;/a&gt;). It is also stable and appears to be feature complete which means not having to work against a moving target.&lt;/p&gt;
</description><pubDate>Mon, 06 Jul 2020 14:34:04 -0500</pubDate><guid>https://lincolnloop.com/blog/dissecting-python-zipapp-built-shiv/</guid></item><item><title>Single-file Python/Django Deployments</title><link>https://lincolnloop.com/blog/single-file-python-django-deployments/</link><description>
&lt;p&gt;&lt;strong&gt;Please update to our new feed url: &lt;a href="https://lincolnloop.com/blog/feed/"&gt;lincolnloop.com/blog/feed/&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
&lt;hr/&gt;
&lt;p&gt;&lt;em&gt;This post covers portions of my talk, &lt;a href="https://www.youtube.com/watch?v=Jzf8gTLN1To"&gt;Containerless Django&lt;/a&gt;, from DjangoCon US 2018.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Deploying Python has improved significantly since I started working with it over a decade ago. We have virtualenv, pip, wheels, package hash verification, and &lt;a href="https://pipenv.readthedocs.io/en/latest/basics/#pipfile-lock-security-features"&gt;lock&lt;/a&gt; &lt;a href="https://poetry.eustace.io/docs/basic-usage/#installing-with-poetrylock"&gt;files&lt;/a&gt;. Despite all the improvements, it still feels harder than it needs to be. Installing a typical large project has many steps, each one easy to trip up on:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Install Python&lt;/li&gt;
&lt;li&gt;Install build tools (pip/virtualenv, pipenv, poetry, etc.)&lt;/li&gt;
&lt;li&gt;Install build dependencies (C compiler, development libraries, etc.)&lt;/li&gt;
&lt;li&gt;Download the code&lt;/li&gt;
&lt;li&gt;Run the build tools&lt;/li&gt;
&lt;li&gt;If you&amp;#39;re using Node to build client-side files for a website, repeat steps 1-5 for that&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;It&amp;#39;s normal for ops teams to repeat this process as part of the automated testing and then again on every server the project is deployed to. It&amp;#39;s no wonder Docker has become so popular because of the ease in which you can build-once and deploy-everywhere.&lt;/p&gt;

&lt;p&gt;But Docker is a heavy-handed solution and doesn&amp;#39;t fit for every project. I envy the simplicity of languages like Go where you can compile your project down to a single binary that runs without any external dependencies. Even Java&amp;#39;s &lt;a href="https://en.wikipedia.org/wiki/JAR_(file_format)"&gt;JAR file format&lt;/a&gt; which requires Java to be preinstalled, but otherwise only requires downloading a single file would be a huge improvement.&lt;/p&gt;

&lt;h2&gt;A JAR File for Python&lt;/h2&gt;

&lt;p&gt;Turns out there are already a few projects solving this problem. &lt;a href="https://pypi.org/project/pex/"&gt;PEX&lt;/a&gt; from Twitter, &lt;a href="https://pypi.org/project/xar/"&gt;XAR&lt;/a&gt; from Facebook, and more ambitious projects like &lt;a href="https://pyoxidizer.readthedocs.io/"&gt;PyOxidizer&lt;/a&gt;, but &lt;a href="https://shiv.readthedocs.io/"&gt;shiv&lt;/a&gt; from LinkedIn hits the sweet spot for us. It is simple, stable, does not require special tooling, and incurs no runtime performance hit. It creates a ZIP file of all your code and dependencies that is executable with only a Python interpreter. In a future post, we&amp;#39;ll do a deep-dive into how shiv works under-the-hood, but for brevity&amp;#39;s sake, we&amp;#39;ll treat it as a black box here.&lt;/p&gt;

&lt;h2&gt;Using Shiv with Django&lt;/h2&gt;

&lt;p&gt;Shiv works with any proper Python package. Since most Django developers don&amp;#39;t think about packaging their projects, we&amp;#39;ll give you a crash course here.&lt;/p&gt;

&lt;h3&gt;Packaging Your Project&lt;/h3&gt;

&lt;p&gt;Previously, the only viable packaging tool was setuptools, but since &lt;a href="https://www.python.org/dev/peps/pep-0517/"&gt;PEP-517&lt;/a&gt; we now have a number of other options including &lt;a href="https://flit.readthedocs.io/"&gt;flit&lt;/a&gt; and &lt;a href="https://poetry.eustace.io/"&gt;poetry&lt;/a&gt;. At the moment, setuptools is still the de-facto standard, so, despite it being a little cruftier than the other options we&amp;#39;ll use that in our example.&lt;/p&gt;

&lt;p&gt;You can use our post, &lt;a href="https://lincolnloop.com/blog/using-setuppy-your-django-project/"&gt;Using setup.py in Your (Django) Project&lt;/a&gt;, as a starting point, but we need to take a couple more steps to ensure non-Python files (static files, templates, etc.) are included.&lt;/p&gt;

&lt;p&gt;The easiest way to do this is with a &lt;a href="https://docs.python.org/3/distutils/sourcedist.html#specifying-the-files-to-distribute"&gt;&lt;code&gt;MANIFEST.in&lt;/code&gt;&lt;/a&gt; file. It might look something like this:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;graft your_project/collected_static
graft your_project/templates&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Note that these directories need to live &lt;em&gt;inside&lt;/em&gt; the top-level Python module to be included. Also note that the static files directory should be your &lt;code&gt;STATIC_ROOT&lt;/code&gt; &lt;em&gt;not&lt;/em&gt; your &lt;code&gt;STATICFILES_DIRS&lt;/code&gt;. If you define templates inside your individual apps, you&amp;#39;ll need to include those directories as well.&lt;/p&gt;

&lt;h3&gt;Dealing with Dependencies&lt;/h3&gt;

&lt;p&gt;These days, every project should include some sort of a lock file which is machine generated and defines the exact version of every dependency and the hash verifications for them. You can do this via &lt;a href="https://poetry.eustace.io/"&gt;poetry&lt;/a&gt;, &lt;a href="https://github.com/jazzband/pip-tools"&gt;pip-compile from pip-tools&lt;/a&gt;, or &lt;a href="https://pipenv.readthedocs.io/"&gt;pipenv&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;I typically let one of these tools handle the install and then pass its &lt;code&gt;site-packages&lt;/code&gt; directory to shiv via the &lt;code&gt;--site-packages&lt;/code&gt; option. In that case, you&amp;#39;ll also pass the pip flags &lt;code&gt;--no-deps .&lt;/code&gt; to install your local project, but not include any defined dependencies for it.&lt;/p&gt;

&lt;h3&gt;Including an Entry Point&lt;/h3&gt;

&lt;p&gt;We need to provide shiv a Python function that it will run when the zipapp is executed. The most logical one is the equivalent, &lt;code&gt;manage.py&lt;/code&gt;. You can use &lt;code&gt;django.core.management.execute_from_command_line&lt;/code&gt; directly, but I recommend writing a small wrapper which also sets the default &lt;code&gt;DJANGO_SETTINGS_MODULE&lt;/code&gt; environment variable. You could create a &lt;code&gt;__main__.py&lt;/code&gt; in your project and include the following:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;os&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;django.core.management&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;execute_from_command_line&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;environ&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;setdefault&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;DJANGO_SETTINGS_MODULE&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;your_project.settings&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;execute_from_command_line&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="vm"&gt;__name__&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;__main__&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Putting it in &lt;code&gt;__main__.py&lt;/code&gt; is a Python convention that also allows you to execute management commands via &lt;code&gt;python -m your_project ...&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;The Shebang&lt;/h3&gt;

&lt;p&gt;While not necessary, you can customize the &lt;a href="https://en.wikipedia.org/wiki/Shebang_(Unix)"&gt;shebang&lt;/a&gt; of your zipapp to have it executed with a specific version of Python or a Python from a specific location. I typically use &lt;code&gt;/usr/bin/env python3.7&lt;/code&gt; (or whatever version the project expects). &lt;/p&gt;

&lt;p&gt;Putting this altogether, you might have something like this in your CI script:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;pipenv install --deploy
pipenv run manage.py collectstatic --noinput
shiv --output-file&lt;span class="o"&gt;=&lt;/span&gt;your_project.pyz &lt;span class="se"&gt;\&lt;/span&gt;
     --site-packages&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="k"&gt;$(&lt;/span&gt;pipenv --venv&lt;span class="k"&gt;)&lt;/span&gt;/lib/python3.7/site-packages &lt;span class="se"&gt;\&lt;/span&gt;
     --python&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;/usr/bin/env python3.7&amp;quot;&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
     --entry-point&lt;span class="o"&gt;=&lt;/span&gt;your_project.__main__.main &lt;span class="se"&gt;\&lt;/span&gt;
     --no-deps .
&lt;/pre&gt;&lt;/div&gt;

&lt;h3&gt;Production Webserver&lt;/h3&gt;

&lt;p&gt;Astute readers may have noticed there&amp;#39;s not an easy way to run &lt;code&gt;uwsgi&lt;/code&gt; or &lt;code&gt;gunicorn&lt;/code&gt; when we want to deploy. Typically you execute your webserver, then point it at your project instead of the other way around. We created &lt;a href="https://pypi.org/project/django-webserver/"&gt;&lt;code&gt;django-webserver&lt;/code&gt;&lt;/a&gt; so you&amp;#39;ll have access to your favorite WSGI server as a management command. We also sponsored work on uWSGI &lt;a href="https://pypi.org/project/pyuwsgi/"&gt;to package it as a wheel&lt;/a&gt; making it quick and easy to use in this setup. 🎺&lt;/p&gt;

&lt;p&gt;You&amp;#39;ll also want to be sure that your zipapp can serve its own static files, either via &lt;a href="https://pypi.org/project/whitenoise/"&gt;&lt;code&gt;whitenoise&lt;/code&gt;&lt;/a&gt; or by letting &lt;code&gt;uwsgi&lt;/code&gt; handle your staticfiles (included by default in &lt;code&gt;django-webserver&lt;/code&gt;).&lt;/p&gt;

&lt;h3&gt;Settings&lt;/h3&gt;

&lt;p&gt;People do all sorts of strange things with Django settings. You can still use the &lt;code&gt;DJANGO_SETTINGS_MODULE&lt;/code&gt; environment variable to pick the settings to use at runtime. You can also use environment variables to set different values in your settings file, but that can be tedious and, potentially, a security problem.&lt;/p&gt;

&lt;p&gt;Instead, I prefer to use a file that is easily machine readable (JSON or YAML) and also easily generated from configuration management or a secret manager like &lt;a href="https://github.com/segmentio/chamber"&gt;chamber&lt;/a&gt; or &lt;a href="https://www.vaultproject.io"&gt;Hashicorp Vault&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;We built another package, &lt;a href="https://github.com/lincolnloop/goodconf/"&gt;&lt;code&gt;goodconf&lt;/code&gt;&lt;/a&gt; which lets you use a static configuration file (or environment variables) to adjust settings across environments. This lets us treat our zipapp more like a standard application and less like a special-case Django app. The people who handle your deployments should appreciate this.&lt;/p&gt;

&lt;h3&gt;Deployment&lt;/h3&gt;

&lt;p&gt;Once you have your zipapp, deployment is almost trivial:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Install Python&lt;/li&gt;
&lt;li&gt;Create the configuration file&lt;/li&gt;
&lt;li&gt;Download the zipapp (we store ours in S3)&lt;/li&gt;
&lt;li&gt;Start server - &lt;code&gt;./myproject.pyz gunicorn&lt;/code&gt; or &lt;code&gt;./myproject.pyz pyuwsgi&lt;/code&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;Caveats&lt;/h2&gt;

&lt;p&gt;Zipapps created with shiv aren&amp;#39;t a perfect solution. You should be aware of a few things before you start using them.&lt;/p&gt;

&lt;h3&gt;Extraction&lt;/h3&gt;

&lt;p&gt;On first execution, shiv will cache the zip file contents into a unique directory in &lt;code&gt;~/.shiv&lt;/code&gt; (path is configurable at runtime). This creates a small delay on first run. It also means you may need to periodically clean out the directory if you&amp;#39;re doing lots of deploys.&lt;/p&gt;

&lt;h3&gt;System Dependencies&lt;/h3&gt;

&lt;p&gt;If you are using libraries which depend on system libraries, they will also need to be installed on the deployment target. For example, &lt;code&gt;mysqlclient&lt;/code&gt; will require the MySQL library. Fortunately, the proliferation of the &lt;a href="https://www.python.org/dev/peps/pep-0427/"&gt;wheel format&lt;/a&gt; allows authors to bundle these libraries with their packages as is the case with &lt;code&gt;Pillow&lt;/code&gt;, &lt;code&gt;psycopg2-binary&lt;/code&gt;, &lt;code&gt;lxml&lt;/code&gt;, and many others.&lt;/p&gt;

&lt;h3&gt;Flexibility&lt;/h3&gt;

&lt;p&gt;Your zipapp will define a single entry point. While it is possible to override it at runtime or even drop into a Python interpreter, I would save those options for debugging only. If you are used to running arbitrary scripts from your project or loading arbitrary files from your git repository, you&amp;#39;ll need to use some more discipline to make management commands for the things you want to run once deployed.&lt;/p&gt;

&lt;h3&gt;Portability&lt;/h3&gt;

&lt;p&gt;Pure Python projects should be very portable across different operating systems and potentially different Python versions. As soon as you start compiling dependencies however, your best bet is to build on the same OS and Python as you intend to run the project.&lt;/p&gt;

&lt;h3&gt;Isolation&lt;/h3&gt;

&lt;p&gt;Your project will run with the system&amp;#39;s site packages in &lt;code&gt;sys.path&lt;/code&gt;, effectively the same as creating a virtualenv with &lt;code&gt;--system-site-packages&lt;/code&gt;. For better isolation, use the &lt;code&gt;-S&lt;/code&gt; flag for Python, e.g. &lt;code&gt;python3.7 -S ./your_project.pyz&lt;/code&gt;. See &lt;a href="https://github.com/linkedin/shiv/issues/65#issuecomment-489282083"&gt;this GitHub Issue&lt;/a&gt; for more details.&lt;/p&gt;

&lt;h2&gt;&lt;/h2&gt;

&lt;p&gt;We&amp;#39;ve been successfully running this site and many of our client sites using shiv-generated zipapps for a few months now. We&amp;#39;re very happy with the simplicity and speed it lends to rolling out new software. If you&amp;#39;re using shiv or a similar technique to bundle your application, let us know in the comments below.&lt;/p&gt;
</description><pubDate>Wed, 10 Jul 2019 17:29:12 -0500</pubDate><guid>https://lincolnloop.com/blog/single-file-python-django-deployments/</guid></item></channel></rss>