<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>Forem: Bruno Alla</title>
    <description>The latest articles on Forem by Bruno Alla (@browniebroke).</description>
    <link>https://forem.com/browniebroke</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F388168%2F0309f0a1-54a6-4bdc-a0f3-83286b2d2890.jpeg</url>
      <title>Forem: Bruno Alla</title>
      <link>https://forem.com/browniebroke</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/browniebroke"/>
    <language>en</language>
    <item>
      <title>Migrating a project to Poetry</title>
      <dc:creator>Bruno Alla</dc:creator>
      <pubDate>Mon, 19 Oct 2020 13:30:00 +0000</pubDate>
      <link>https://forem.com/browniebroke/migrating-a-project-to-poetry-3nm</link>
      <guid>https://forem.com/browniebroke/migrating-a-project-to-poetry-3nm</guid>
      <description>&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Ff264nwf29ysewscd6j8g.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Ff264nwf29ysewscd6j8g.png" alt="Poetry Banner" width="800" height="252"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://python-poetry.org/"&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="https://pendulum.eustace.io/"&gt;pendulum&lt;/a&gt;). It has a beautiful website and a 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="https://github.com/jazzband/pip-tools"&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="https://pipenv.pypa.io"&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="https://deezer-python.readthedocs.io"&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="https://setuptools.readthedocs.io/en/latest/setuptools.html#setup-cfg-only-projects"&gt;setuptools declarative config&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;Development (and documentation) dependencies managed by &lt;a href="https://github.com/jazzband/pip-tools"&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="https://python-semantic-release.readthedocs.io"&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’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;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’t be wiped without my knowledge:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pipx &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
 &lt;span class="nt"&gt;--python&lt;/span&gt; ~/.pyenv/versions/3.8.6/bin/python &lt;span class="se"&gt;\&lt;/span&gt;
 poetry
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&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 merge 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’s mainly to simplify things. I guess it’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 dependency, &lt;a href="https://github.com/asottile/scratch/wiki/python-3-statement#360"&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's trove classifiers, the ones generated by Poetry: my package wasn'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;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight toml"&gt;&lt;code&gt;&lt;span class="nn"&gt;[tool.poetry.dependencies]&lt;/span&gt;
&lt;span class="py"&gt;python&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"^3.6"&lt;/span&gt;
&lt;span class="err"&gt;...&lt;/span&gt;

&lt;span class="nn"&gt;[tool.poetry.dev-dependencies]&lt;/span&gt;
&lt;span class="err"&gt;...&lt;/span&gt;
&lt;span class="py"&gt;pyupgrade&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="py"&gt;version&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"^2.7.3"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="py"&gt;python&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"^3.6.1"&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&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 “extra require” dependency. There is a good &lt;a href="https://python-poetry.org/docs/pyproject/#extras"&gt;example in the documentation&lt;/a&gt;, these dependencies needs to be specified in the same section as normal dependencies, but as &lt;code&gt;optional&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight toml"&gt;&lt;code&gt;&lt;span class="nn"&gt;[tool.poetry.dependencies]&lt;/span&gt;
&lt;span class="err"&gt;...&lt;/span&gt;
&lt;span class="py"&gt;tornado&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="py"&gt;version&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"^6.0.4"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="py"&gt;optional&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&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;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight toml"&gt;&lt;code&gt;&lt;span class="nn"&gt;[tool.poetry.extras]&lt;/span&gt;
&lt;span class="py"&gt;tornado&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"tornado"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&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’s for the extra of the dependency, not the extra of my own library the dependency should fall under.&lt;/p&gt;

&lt;p&gt;For example Django has &lt;a href="https://github.com/django/django/blob/0eee5c1b9c2e306aa2c2807daf146ee88676bc97/setup.cfg#L52-L54"&gt;2 possible extras&lt;/a&gt; at the moment, so one would run the following commands to use them:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;poetry add Django &lt;span class="nt"&gt;-E&lt;/span&gt; argon2
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It means “install Django with the &lt;code&gt;argon2&lt;/code&gt; extra”. I thought it meant “install Django and put it under the &lt;code&gt;argon2&lt;/code&gt; extra”.&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’t be able to remove that file, but it turns out &lt;a href="https://github.com/readthedocs/readthedocs.org/issues/4912"&gt;it’s possible to make it work&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Thanks to &lt;a href="https://www.python.org/dev/peps/pep-0517/"&gt;PEP 517&lt;/a&gt;, which &lt;a href="https://python-poetry.org/docs/pyproject/#poetry-and-pep-517"&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’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;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight toml"&gt;&lt;code&gt;&lt;span class="c"&gt;# pyproject.toml&lt;/span&gt;
&lt;span class="nn"&gt;[tool.poetry.dependencies]&lt;/span&gt;
&lt;span class="err"&gt;...&lt;/span&gt;
&lt;span class="py"&gt;myst-parser&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="py"&gt;version&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"^0.12"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="py"&gt;optional&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="py"&gt;sphinx&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="py"&gt;version&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"^3"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="py"&gt;optional&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="py"&gt;sphinx-autobuild&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="py"&gt;version&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"^2020.9.1"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="py"&gt;optional&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="py"&gt;sphinx-rtd-theme&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="py"&gt;version&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"^0.5"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="py"&gt;optional&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nn"&gt;[tool.poetry.extras]&lt;/span&gt;
&lt;span class="err"&gt;...&lt;/span&gt;
&lt;span class="py"&gt;docs&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="s"&gt;"myst-parser"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s"&gt;"sphinx"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s"&gt;"sphinx-autobuild"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s"&gt;"sphinx-rtd-theme"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;# readthedocs.yml&lt;/span&gt;
&lt;span class="na"&gt;version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;2&lt;/span&gt;
&lt;span class="na"&gt;python&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;install&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;method&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;pip&lt;/span&gt;
      &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;.&lt;/span&gt;
      &lt;span class="na"&gt;extra_requirements&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;docs&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&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="https://python-semantic-release.readthedocs.io"&gt;Python Semantic Release&lt;/a&gt; which worked well for me, and this would have been a blocker if it wouldn’t work. 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;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight toml"&gt;&lt;code&gt;&lt;span class="nn"&gt;[tool.semantic_release]&lt;/span&gt;
&lt;span class="py"&gt;version_variable&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="s"&gt;"deezer/ __init__.py: __version__"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s"&gt;"pyproject.toml:version"&lt;/span&gt;
&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="py"&gt;build_command&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"pip install poetry &amp;amp;&amp;amp; poetry build"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&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’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="https://gitlab.com/pycqa/flake8/-/issues/428"&gt;an open issue&lt;/a&gt; for it.&lt;/p&gt;

&lt;p&gt;I decided to move as much 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 and &lt;a href="https://docs.pytest.org/en/stable/customize.html#pyproject-toml"&gt;unexpected config section&lt;/a&gt; for &lt;code&gt;pyproject.toml&lt;/code&gt;, I didn'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'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;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[tool.pytest.ini_options]
addopts = "-v -Wdefault --cov=deezer"

[tool.coverage.run]
branch = true
source = ["deezer"]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With this, pytest was running with coverage, but for some reason, codecov wasn'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;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;poetry run pytest &lt;span class="nt"&gt;--cov-report&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;xml
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With this changes I eventually got my code coverage reporting back, but it'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="https://python-poetry.org/docs/faq/#is-tox-supported"&gt;section in their FAQ&lt;/a&gt;, and here is a overview of the changes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ini"&gt;&lt;code&gt;&lt;span class="nn"&gt;[tox]&lt;/span&gt;
&lt;span class="py"&gt;isolated_build&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;true&lt;/span&gt;
&lt;span class="py"&gt;envlist&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;py36,py37,py38,py39,pypy3,docs,lint,bandit&lt;/span&gt;

&lt;span class="nn"&gt;[testenv]&lt;/span&gt;
&lt;span class="py"&gt;whitelist_externals&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;poetry&lt;/span&gt;
&lt;span class="py"&gt;commands&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt;
    &lt;span class="err"&gt;poetry&lt;/span&gt; &lt;span class="err"&gt;install&lt;/span&gt;
    &lt;span class="err"&gt;poetry&lt;/span&gt; &lt;span class="err"&gt;run&lt;/span&gt; &lt;span class="err"&gt;pytest&lt;/span&gt;
&lt;span class="err"&gt;...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&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’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="https://github.com/abatilo/actions-poetry"&gt;a dedicated action&lt;/a&gt; for it. I’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’s a matter or 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 little issues on the way. Overall the migration was not too difficult, you can check the &lt;a href="https://github.com/browniebroke/deezer-python/pull/196"&gt;pull request&lt;/a&gt; on Github. I feel like there are quite a few features I just scratched the surface (like multi-environments). &lt;/p&gt;

&lt;p&gt;I’m going to wait a bit to see how this works in the longer run, but I think I’ll migrate my other projects soon.&lt;/p&gt;

</description>
      <category>python</category>
      <category>packaging</category>
      <category>poetry</category>
    </item>
    <item>
      <title>Auto-update pre-commit hooks with GitHub Actions</title>
      <dc:creator>Bruno Alla</dc:creator>
      <pubDate>Sun, 12 Jul 2020 00:00:00 +0000</pubDate>
      <link>https://forem.com/browniebroke/auto-update-pre-commit-hooks-with-github-actions-3f84</link>
      <guid>https://forem.com/browniebroke/auto-update-pre-commit-hooks-with-github-actions-3f84</guid>
      <description>&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fsevjg3n5kqq54dl5bxpj.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fsevjg3n5kqq54dl5bxpj.png" alt="Alt Text" width="800" height="625"&gt;&lt;/a&gt;&lt;/p&gt;

&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="https://pre-commit.com/"&gt;pre-commit&lt;/a&gt;. It’s written in Python, but it aims at being &lt;a href="https://pre-commit.com/#supported-languages"&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="https://pyup.io/"&gt;PyUP&lt;/a&gt; or &lt;a href="https://renovate.whitesourcesoftware.com/"&gt;Renovate&lt;/a&gt; but none of these tools supports the pre-commit config file. After a while, it's easy to end up with versions discrepancies.&lt;/p&gt;

&lt;p&gt;That is until this week-end, where I stumbled upon &lt;a href="https://pre-commit.com/#pre-commit-autoupdate"&gt;the &lt;code&gt;autoupdate&lt;/code&gt; command&lt;/a&gt; from pre-commit. I’m not sure how I missed this before, it looks like it’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;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Pre-commit auto-update&lt;/span&gt;

&lt;span class="na"&gt;on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;schedule&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;cron&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;0&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;0&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;*&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;*&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;*'&lt;/span&gt;

&lt;span class="na"&gt;jobs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;auto-update&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ubuntu-latest&lt;/span&gt;
    &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/checkout@v2&lt;/span&gt;

      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Set up Python&lt;/span&gt;
        &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/setup-python@v2&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;python-version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;3.8&lt;/span&gt;

      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Install pre-commit&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;pip install pre-commit&lt;/span&gt;

      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Run pre-commit autoupdate&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;pre-commit autoupdate&lt;/span&gt;

      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Create Pull Request&lt;/span&gt;
        &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;peter-evans/create-pull-request@v2&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;token&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ secrets.CPR_GITHUB_TOKEN }}&lt;/span&gt;
          &lt;span class="na"&gt;branch&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;update/pre-commit-autoupdate&lt;/span&gt;
          &lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Auto-update pre-commit hooks&lt;/span&gt;
          &lt;span class="na"&gt;commit-message&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Auto-update pre-commit hooks&lt;/span&gt;
          &lt;span class="na"&gt;body&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
            &lt;span class="s"&gt;Update versions of tools in pre-commit &lt;/span&gt;
            &lt;span class="s"&gt;configs to latest version&lt;/span&gt;
          &lt;span class="na"&gt;labels&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;dependencies&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&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't trigger the CI build for that pull request.&lt;/p&gt;

&lt;p&gt;It's a limitation which is well documented on the action's README, and is intentional from Github. I chose the solution to create a PAT 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's deployed and running on &lt;a href="https://github.com/browniebroke/django-codemod/actions?query=workflow%3A%22Pre-commit+auto-update%22"&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'll look into making the pull request content a bit more dynamic, but for now it does the job I need to. I'm also planning to add this to my &lt;a href="https://github.com/browniebroke/cookiecutter-pypackage"&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="https://pre-commit.ci/"&gt;pre-commit CI&lt;/a&gt; is ready, or maybe it will be a cheaper and simpler alternative&lt;/p&gt;

</description>
      <category>python</category>
      <category>precommit</category>
      <category>github</category>
      <category>actions</category>
    </item>
    <item>
      <title>Static vs. Media files in Django</title>
      <dc:creator>Bruno Alla</dc:creator>
      <pubDate>Tue, 18 Jun 2019 00:00:00 +0000</pubDate>
      <link>https://forem.com/browniebroke/static-vs-media-files-in-django-2k1l</link>
      <guid>https://forem.com/browniebroke/static-vs-media-files-in-django-2k1l</guid>
      <description>&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’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’m not the only one running into the confusion, so I’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="https://docs.djangoproject.com/en/stable/ref/contrib/staticfiles/"&gt;&lt;code&gt;staticfiles&lt;/code&gt; app&lt;/a&gt; which you need to install. It'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’ 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="https://github.com/django/django/tree/master/django/contrib/admin/static/admin"&gt;in Django'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="https://docs.djangoproject.com/en/stable/ref/contrib/staticfiles/#static-file-development-view"&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="https://pypi.org/project/django-storages/"&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;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2F2igd881xs4y3nmxdv61h.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2F2igd881xs4y3nmxdv61h.jpg" alt="Static files in a Bucket" width="529" height="370"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;However, a couple of years ago I’ve switched to &lt;a href="https://pypi.org/project/whitenoise/"&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;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Frfcxrc3l40l6j9hczmh3.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Frfcxrc3l40l6j9hczmh3.jpg" alt="Static files with Whitenoise" width="410" height="340"&gt;&lt;/a&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="https://docs.djangoproject.com/en/stable/topics/files/"&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'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;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;User&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;CharField&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="c1"&gt;# public file
&lt;/span&gt;    &lt;span class="n"&gt;profile_picture&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;ImageField&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;storage&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nc"&gt;PublicStorage&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="c1"&gt;# private file
&lt;/span&gt;    &lt;span class="n"&gt;dbs_check_document&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;FileField&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;storage&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nc"&gt;PrivateStorage&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&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’t go into the details of each, but here is an example of what it could look like for AWS:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;storages.backends.s3boto3&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;S3Boto3Storage&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;PublicStorage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;S3Boto3Storage&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;default_acl&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;public&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;
    &lt;span class="n"&gt;file_overwrite&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;False&lt;/span&gt;
    &lt;span class="n"&gt;bucket_name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;my-public-bucket&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;PrivateStorage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;S3Boto3Storage&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;default_acl&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;private&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;
    &lt;span class="n"&gt;file_overwrite&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;False&lt;/span&gt;
    &lt;span class="n"&gt;bucket_name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;my-private-bucket&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&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;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;get_storage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;storage_type&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;settings&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;USE_REMOTE_STORAGE&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;storage_class&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;private&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;PrivateStorage&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;public&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;PublicStorage&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;}[&lt;/span&gt;&lt;span class="n"&gt;storage_type&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;storage_class&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nc"&gt;FileSystemStorage&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Which would change your model definition to:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;User&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;CharField&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="c1"&gt;# public file
&lt;/span&gt;    &lt;span class="n"&gt;profile_picture&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;ImageField&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;storage&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nf"&gt;get_storage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;public&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="c1"&gt;# private file
&lt;/span&gt;    &lt;span class="n"&gt;dbs_check_document&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;FileField&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;storage&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nf"&gt;get_storage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;private&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&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’s important that you make the separation very clear, a user of your website shouldn’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;

</description>
      <category>python</category>
      <category>django</category>
      <category>staticfiles</category>
      <category>mediafiles</category>
    </item>
  </channel>
</rss>
