<?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: Alex Becker</title>
    <description>The latest articles on Forem by Alex Becker (@alexbecker).</description>
    <link>https://forem.com/alexbecker</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%2F142076%2Fbb4f98d5-fff7-4b86-8120-9a959b9fc2c8.png</url>
      <title>Forem: Alex Becker</title>
      <link>https://forem.com/alexbecker</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/alexbecker"/>
    <language>en</language>
    <item>
      <title>Distributions vs Releases: Why Python Packaging is Hard</title>
      <dc:creator>Alex Becker</dc:creator>
      <pubDate>Tue, 30 Apr 2019 14:43:23 +0000</pubDate>
      <link>https://forem.com/alexbecker/distributions-vs-releases-why-python-packaging-is-hard-4ohm</link>
      <guid>https://forem.com/alexbecker/distributions-vs-releases-why-python-packaging-is-hard-4ohm</guid>
      <description>&lt;p&gt;Most programming language's package ecosystems have two levels: each &lt;em&gt;package&lt;/em&gt; has one or more &lt;em&gt;release&lt;/em&gt;, which are distinguished by a version. Python has a third: each release has one or more &lt;em&gt;distributions&lt;/em&gt;, which are the actual files you download to install a package. In most languages said file is synonymous with the release, but the "&lt;em&gt;or more&lt;/em&gt;" is crucial in Python, because for most releases of most widely-used packages, there is in fact more than one distribution. &lt;/p&gt;

&lt;p&gt;Why? Well, Python is special in that it treats C extensions as a first-class feature of the language, &lt;em&gt;and&lt;/em&gt; tries to insulate package users from having to compile C extensions. This means that distributions need to contain binary code compiled from the C extensions—such distributions (in their modern iteration) are called &lt;em&gt;binary wheels&lt;/em&gt;. But C extensions usually need to be compiled for a specific target Python version and operating system, so to have any sort of wide support you need multiple wheels. Furthermore, since the package author can't anticipate &lt;em&gt;all&lt;/em&gt; Python versions and operating systems (some of which don't exist yet!), it's also important to include a &lt;em&gt;source distribution&lt;/em&gt;, which the package user is responsible for compiling.&lt;/p&gt;

&lt;p&gt;Despite this, users—and most tools—still think in terms of release versions rather than specific distributions. This can lead to surprising inconsistencies. For example, installing a package might take seconds on one machine (because there is a matching binary distribution) and minutes or even hours on another. Even if both machines find appropriate binary distributions to install, their hashes won't match, making it more difficult to detect MitM attacks. This is because tools like &lt;code&gt;pip&lt;/code&gt; automatically determine the "most suitable" distribution for a release, preferring binary wheels when one is compatible with the given system—and the most specific binary wheel if multiple are—and otherwise falling back to the source distribution. Most other tools follow suit, if only by virtue of using &lt;code&gt;pip&lt;/code&gt; under the hood.&lt;/p&gt;

&lt;p&gt;The biggest problems crop up when a new distribution is published after you've already installed another distribution for that release. This is all but inevitable—PyPI only lets you upload distributions one at a time, creating a new release with the first upload that has a new version, so eventually someone is bound to download that first distribution before you upload the last one. It's made much more common by the practice of having buildbots build different distributions in parallel, with binary distributions generally taking significantly longer than source distributions. But even worse is when a package author goes back to add support for a new platform—or a new version of python—for a release months or years after the fact. When this happens:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Build systems which expect a certain hash for a given package suddenly break.&lt;/li&gt;
&lt;li&gt;PyPI mirrors like &lt;a href="https://pydist.com"&gt;PyDist&lt;/a&gt; don't know to look for the new distribution and get out of sync.&lt;/li&gt;
&lt;li&gt;Systems which have previously installed a distribution for the release (say, your dev machine) won't get the new distribution, and might behave differently from systems that install it (say, your production servers).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;There is no obvious way to fix these issues without significantly disrupting the ecosystem—although the PyPI maintainers are &lt;a href="https://github.com/pypa/packaging.python.org/issues/564"&gt;aware of the pain&lt;/a&gt; and &lt;a href="https://github.com/pypa/pip/issues/5874"&gt;discussing tool improvements&lt;/a&gt;. In the meantime, it is incumbent on heavy Python users and system administrators to understand how Python packages are distributed and how &lt;code&gt;pip&lt;/code&gt; chooses distributions.&lt;/p&gt;

</description>
      <category>python</category>
      <category>packaging</category>
    </item>
    <item>
      <title>Announcing the PyDist CLI</title>
      <dc:creator>Alex Becker</dc:creator>
      <pubDate>Wed, 10 Apr 2019 14:30:33 +0000</pubDate>
      <link>https://forem.com/alexbecker/announcing-the-pydist-cli-oa7</link>
      <guid>https://forem.com/alexbecker/announcing-the-pydist-cli-oa7</guid>
      <description>&lt;p&gt;One of the selling points of &lt;a href="https://pydist.com"&gt;PyDist&lt;/a&gt; is that it works with standard Python tools—you don't have to install anything to use it. This is great for keeping containerized deployments and CI infrastructure lean and fast. But sometimes you just want to do as little configuration as possible and not have to keep track of the different Python tools and their quirks. That's why I've created the &lt;a href="https://pypi.org/project/pydist-cli"&gt;PyDist CLI&lt;/a&gt;, which combines the functionality of &lt;code&gt;pip&lt;/code&gt; and &lt;code&gt;twine&lt;/code&gt; while minimizing the amount of configuration you need to do and smoothing out their rough edges.&lt;/p&gt;

&lt;p&gt;All you need to do to publish to and install from PyDist is install the CLI with &lt;code&gt;pip install pydist-cli&lt;/code&gt; and provide your API key when you first run it. You can then publish packages to PyDist with &lt;code&gt;pydist publish&lt;/code&gt; and install packages from PyDist with &lt;code&gt;pydist install&lt;/code&gt;. You can also use it with PyPI using the &lt;code&gt;--public&lt;/code&gt; flag.&lt;/p&gt;

&lt;p&gt;The PyDist CLI is much more opinionated than the standard Python tooling. The &lt;code&gt;install&lt;/code&gt; command uses PyDist by default but has an automatic fallback to PyPI, making it more reliable than &lt;code&gt;pip install&lt;/code&gt;. The &lt;code&gt;publish&lt;/code&gt; command handles building distributions (both binary and source), checking the README and metadata formatting, uploading, and cleaning up. This is appropriate for most packages—and avoids several pitfalls associated with &lt;code&gt;twine&lt;/code&gt;, such picking up artifacts in the &lt;code&gt;build/&lt;/code&gt; directory from prior releases.&lt;/p&gt;

</description>
      <category>python</category>
      <category>packaging</category>
      <category>cli</category>
    </item>
    <item>
      <title>Python's except quirk</title>
      <dc:creator>Alex Becker</dc:creator>
      <pubDate>Mon, 11 Mar 2019 16:01:41 +0000</pubDate>
      <link>https://forem.com/alexbecker/pythons-except-quirk-5ble</link>
      <guid>https://forem.com/alexbecker/pythons-except-quirk-5ble</guid>
      <description>&lt;p&gt;Let me show you my favorite Python quirk. What would you expect this python code to do?&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;e&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;2.718&lt;/span&gt;
&lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;
&lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="nb"&gt;ZeroDivisionError&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;pass&lt;/span&gt;
&lt;span class="k"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;If you come to Python from another programming language, you might expect that the &lt;code&gt;except&lt;/code&gt; clause introduces a nested scope, so assigning to &lt;code&gt;e&lt;/code&gt; in the clause does not effect the pre-existing &lt;code&gt;e&lt;/code&gt; variable in the outer scope. However, in python control structures do not generally introduce nested scoped (comprehensions being the exception), so with more python experience you would probably expect this to print a &lt;code&gt;ZeroDivisionError&lt;/code&gt; instance.&lt;/p&gt;

&lt;p&gt;Actually, in the standard CPython implementation it prints nothing; instead, the last line raises a &lt;code&gt;NameError&lt;/code&gt;. Is this a bug? Actually, it was &lt;a href="https://bugs.python.org/issue1631942"&gt;quite intentional&lt;/a&gt;. If you look at the bytecode generated by the except clause, you see:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;LOAD_CONST 0 (None)
STORE_NAME 1 (e)
DELETE_NAME 1
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;When control flow exits the &lt;code&gt;except&lt;/code&gt; block, python deletes the name from scope. Why? Because the exception holds a reference to the current stack frame, which contains &lt;em&gt;everything&lt;/em&gt; in scope. Since python is manages memory primary via reference count, this means that nothing in the current scope will be freed until the next round of garbage collection runs, if at all. The current behavior is a compromise between memory usage, ease of implementation, and cleanliness of the language. It is a bit of a wart, but I think it embodies a one of the things I love about Python: not letting purity get in the way of practicality.&lt;/p&gt;

&lt;p&gt;But that only explains the &lt;code&gt;DELETE_NAME&lt;/code&gt; instruction. Why does CPython set &lt;code&gt;e&lt;/code&gt; to &lt;code&gt;None&lt;/code&gt; if it's going to delete it immediately afterwards? Well, imagine you had the same thought as the CPython team, and decided to clean up the exception reference at the end of your &lt;code&gt;except&lt;/code&gt; block:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;
&lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="nb"&gt;ZeroDivisionError&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="p"&gt;...&lt;/span&gt;
    &lt;span class="k"&gt;del&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;At the end of your &lt;code&gt;except&lt;/code&gt; block, CPython will try to delete the name &lt;code&gt;e&lt;/code&gt;—which you already deleted! To get around this, CPython assigns &lt;code&gt;e = None&lt;/code&gt; before deleting &lt;code&gt;e&lt;/code&gt; to guarantee that &lt;code&gt;e&lt;/code&gt; exists.&lt;/p&gt;

</description>
      <category>python</category>
    </item>
    <item>
      <title>Growth of the Python Ecosystem</title>
      <dc:creator>Alex Becker</dc:creator>
      <pubDate>Mon, 11 Mar 2019 03:07:26 +0000</pubDate>
      <link>https://forem.com/alexbecker/growth-of-the-python-ecosystem-43hf</link>
      <guid>https://forem.com/alexbecker/growth-of-the-python-ecosystem-43hf</guid>
      <description>&lt;p&gt;One of the cool things about building &lt;a href="https://pydist.com"&gt;a PyPI mirror&lt;/a&gt; is having so much data about the Python ecosystem at my fingertips. I decided to explore how the ecosystem has been evolving since PyPI was created in 2003. Most of my analysis starts at 2005, since that's when PyPI added the &lt;code&gt;upload_time&lt;/code&gt; field.&lt;/p&gt;

&lt;p&gt;The Python ecosystem has been steadily growing throughout this period. After the first few years of hyper growth as PyPI gained near-full adoption in the Python community, the number of packages actively developed each year—meaning they had at least one release or new distribution uploaded—has increased 28% to 48% every year.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--BXDFwROv--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://pydist.com/static/imgs/upload_growth.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--BXDFwROv--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://pydist.com/static/imgs/upload_growth.png" alt="Active Python Packages 2005-2018"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;As this graph shows, the majority (~66%) of actively maintained packages each year are new , and the majority of those do not continue to be maintained. However, there is still steady and robust growth in the number of packages maintained for more than 1 year. Growth in releases has been even stronger, at 31% to 59% every year, although it has slowed down somewhat. This means that packages are getting more releases on average, which is a decent proxy for becoming more mature and better-maintained.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--_WQfD2TG--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://pydist.com/static/imgs/release_growth.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--_WQfD2TG--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://pydist.com/static/imgs/release_growth.png" alt="Releases per Year 2005-2018"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The most surprising result I stumbled upon came from looking at the number of releases per package. Some of these I was expecting based on my personal experience upgrading dependencies frequently, such as AWS's &lt;a href="https://pypi.org/project/botocore/"&gt;botocore&lt;/a&gt; at #15. But the cryptocurrency trading library &lt;a href="https://pypi.org/project/ccxt"&gt;cctx&lt;/a&gt; immediately stood out like a sore thumb. With 4659 releases at time of writing, it has more than &lt;em&gt;3 times&lt;/em&gt; as many releases as any other package—despite being less than 2 years old! Its &lt;a href="https://libraries.io"&gt;libraries.io&lt;/a&gt; page usually times out after 30 seconds when I try to load it. I am not sure whether this is excellent or terrible maintainership, but it is certainly impressive.&lt;/p&gt;

&lt;p&gt;Another interesting thing to look at is how practices around distributing Python packages are changing. The biggest change was of course the release of Python 3. &lt;em&gt;Binary wheels&lt;/em&gt;, introduced in 2012 and codified in &lt;a href="https://www.python.org/dev/peps/pep-0427/"&gt;PEP 427&lt;/a&gt;, are generally accepted as the best way to distribute Python packages. But adoption among package authors has taken time. &lt;a href="https://pythonwheels.com/"&gt;Python Wheels&lt;/a&gt; tracks the adoption of wheels among PyPI's 360 most downloaded packages; at time of writing 82.5% of them include wheel distributions in their releases. The long tail of the other &amp;gt;60k packages is lagging a bit behind, but the percentage of all releases that include at least 1 wheel distribution is growing steadily and just crossed 50% in 2018.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--lIA-f6_d--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://pydist.com/static/imgs/wheel_fraction.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--lIA-f6_d--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://pydist.com/static/imgs/wheel_fraction.png" alt="Fraction of Releases with Wheels"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Not every package will be distributed as a wheel; in particular&lt;br&gt;
&lt;a href="http://initd.org/psycopg/docs/install.html#disabling-wheel-packages-for-psycopg-2-7"&gt;psycopg2 will soon stop publishing wheels&lt;/a&gt; due to conflicts between the bundled LibSSL and the system's pre-existing LibSSL. But very few packages have such a reason not to be distributed as wheels, so I expect the rate of adoption to stay strong until 90% or more—which this graph suggests will happen by the end of 2022.&lt;/p&gt;

</description>
      <category>pythonpypi</category>
    </item>
    <item>
      <title>Using non-PyPI Package Indices</title>
      <dc:creator>Alex Becker</dc:creator>
      <pubDate>Fri, 08 Mar 2019 02:49:28 +0000</pubDate>
      <link>https://forem.com/alexbecker/using-non-pypi-package-indices-af</link>
      <guid>https://forem.com/alexbecker/using-non-pypi-package-indices-af</guid>
      <description>&lt;p&gt;By default, Python tools like &lt;code&gt;pip&lt;/code&gt; install packages from PyPI, the Python Package Index. However, most tools allow you to use any server that implements the same API as PyPI (usually but not always the &lt;a href="https://www.python.org/dev/peps/pep-0503/"&gt;Simple Repository API&lt;/a&gt;). There are two main reasons you might want to use an alternative package index:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;An index that mirrors PyPI let you keep installing packages even if they are deleted from PyPI or PyPI goes offline.&lt;/li&gt;
&lt;li&gt;You can upload packages to a private index instead of PyPI if you do not want to make them public.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;How to configure your tools depends on whether you are using an alternative index &lt;em&gt;in place of&lt;/em&gt; or &lt;em&gt;in addition to&lt;/em&gt; PyPI. Mirrors can be used either way, while a private index is usually used in addition to PyPI. Note that &lt;a href="https://pydist.com"&gt;PyDist&lt;/a&gt; or a self-hosted &lt;a href="https://github.com/devpi/devpi"&gt;devpi&lt;/a&gt; instance can act as both a mirror and a private index.&lt;/p&gt;

&lt;p&gt;Unfortunately, you need to configure each of your tools individually. &lt;/p&gt;

&lt;h2&gt;
  
  
  Configuring &lt;code&gt;pip&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;There are two configuration options for &lt;code&gt;pip&lt;/code&gt; that allow it to use alternative indices:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The &lt;em&gt;index URL&lt;/em&gt;, which is where &lt;code&gt;pip&lt;/code&gt; will initially check for packages. It defaults to &lt;code&gt;https://pypi.org/simple&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;The &lt;em&gt;extra index URLs&lt;/em&gt; which are where &lt;code&gt;pip&lt;/code&gt; will check for packages if they are not found in the index url. If multiple are provided, they are checked in order.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you are using a mirror, you can either set it as the index to install all packages through the mirror, or you can set it as an extra index to only use it as a backup. If you are using a private mirror to compliment PyPI, it is tempting to use it as an extra index. However, if a package by the same name as one of your private packages is published on PyPI, this will cause &lt;code&gt;pip&lt;/code&gt; to install that package instead. Thus it is safest to set the private index's URL as the index and use PyPI as an extra index.&lt;/p&gt;

&lt;p&gt;You can configure &lt;code&gt;pip&lt;/code&gt; in a number of different ways; which you should choose depends on what is easiest to set in your infrastructure and how broadly you want the configuration to apply.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Via the command line arguments &lt;code&gt;--index-url&lt;/code&gt; and &lt;code&gt;--extra-index-url&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Via the environment variables &lt;code&gt;PIP_INDEX_URL&lt;/code&gt; and &lt;code&gt;PIP_EXTRA_INDEX_URL&lt;/code&gt; (note that this can only be set once, so you can only add one extra index this way)&lt;/li&gt;
&lt;li&gt;Via the &lt;code&gt;index-url&lt;/code&gt; and &lt;code&gt;extra-index-url&lt;/code&gt; settings in a &lt;a href="https://pip.pypa.io/en/stable/user_guide/#config-file"&gt;&lt;code&gt;pip.conf&lt;/code&gt; file&lt;/a&gt; which can live:

&lt;ul&gt;
&lt;li&gt;In the root of your virtual environment (e.g. &lt;code&gt;./venv&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;In your user config's &lt;code&gt;pip&lt;/code&gt; subdirectory (usually &lt;code&gt;~/.config/pip&lt;/code&gt; on Linux)&lt;/li&gt;
&lt;li&gt;In your system-wide config directory (&lt;code&gt;/etc&lt;/code&gt; on Linux)&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Configuring &lt;code&gt;twine&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;The standard tool to upload Python packages is &lt;code&gt;twine&lt;/code&gt;. However, only some&lt;br&gt;
 rivate python indices support uploading with &lt;code&gt;twine&lt;/code&gt;—to my knowledge only &lt;a href="https://pydist.com"&gt;PyDist&lt;/a&gt; and &lt;a href="https://gemfury.com"&gt;Gemfury&lt;/a&gt; do among hosted solutions. Other indices will generally offer their own python packages for deployment.&lt;/p&gt;

&lt;p&gt;There are two ways to configure &lt;code&gt;twine&lt;/code&gt; to upload to a repository other than PyPI:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Via the command line &lt;code&gt;--repository-url&lt;/code&gt; argument&lt;/li&gt;
&lt;li&gt;Via a &lt;a href="https://docs.python.org/3/distutils/packageindex.html#the-pypirc-file"&gt;&lt;code&gt;.pypirc&lt;/code&gt; file&lt;/a&gt; in your home directory&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The &lt;code&gt;.pypirc&lt;/code&gt; file should look like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[distutils]
index-servers =
    pydist

[pydist]
repository: &amp;lt;index-url&amp;gt;
username: &amp;lt;username&amp;gt;
password: &amp;lt;password&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;If you include multiple &lt;code&gt;index-servers&lt;/code&gt; in &lt;code&gt;.pypirc&lt;/code&gt;, you can pass the name you gave the index server (&lt;code&gt;pydist&lt;/code&gt; in the example above) to the &lt;code&gt;--repository&lt;/code&gt; flag when uploading. The &lt;code&gt;.pypirc&lt;/code&gt; file is convenient because you will not be prompted for a username/password.&lt;/p&gt;

&lt;h2&gt;
  
  
  Configuring &lt;code&gt;pipenv&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;Pipenv is probably the most popular dependency-locking tool for Python. Some guides suggest using the &lt;code&gt;PIP_INDEX_URL&lt;/code&gt; and &lt;code&gt;PIP_EXTRA_INDEX_URL&lt;/code&gt; environment variables to configure the &lt;code&gt;pipenv&lt;/code&gt; like you would &lt;code&gt;pip&lt;/code&gt;, but this is not handled correctly and the maintainer &lt;a href="https://github.com/pypa/pipenv/issues/1285"&gt;told me not to use it&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Instead, you can use the &lt;code&gt;[[source]]&lt;/code&gt; section at the top of your &lt;code&gt;Pipfile&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[[source]]
url = '&amp;lt;index-url&amp;gt;'
verify_ssl = true
name = 'pydist'
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;If you want to use multiple package indices, you can include multiple &lt;code&gt;[[source]]&lt;/code&gt; sections—when &lt;code&gt;pipenv&lt;/code&gt; finds packages it tries them in the order they are specified, or if you declare a package like &lt;code&gt;mypackage = { version="*", index="pydist" }&lt;/code&gt; it will try the specified index first. However, &lt;code&gt;pipenv&lt;/code&gt;'s handling of multiple indices is &lt;a href="https://github.com/pypa/pipenv/issues/2159"&gt;currently buggy&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>python</category>
      <category>packaging</category>
    </item>
    <item>
      <title>How "pip install" Works</title>
      <dc:creator>Alex Becker</dc:creator>
      <pubDate>Tue, 05 Mar 2019 07:45:14 +0000</pubDate>
      <link>https://forem.com/alexbecker/how-pip-install-works-323j</link>
      <guid>https://forem.com/alexbecker/how-pip-install-works-323j</guid>
      <description>&lt;p&gt;What happens when you run &lt;code&gt;pip install &amp;lt;somepackage&amp;gt;&lt;/code&gt;? A lot more than you might think. Python's package ecosystem is quite complex.&lt;/p&gt;

&lt;p&gt;First &lt;code&gt;pip&lt;/code&gt; needs to decide which &lt;code&gt;distribution&lt;/code&gt; of the package to install.&lt;br&gt;
This is more complex for Python than many other languages, since each version (or &lt;em&gt;release&lt;/em&gt;) of a Python package usually has multiple &lt;code&gt;distributions&lt;/code&gt;. There are 7 different kinds of distributions, but the most common these days are &lt;em&gt;source distributions&lt;/em&gt; and &lt;em&gt;binary wheels&lt;/em&gt;. A source distribution is exactly what it says on the tin—the raw Python and potentially C extension code that the package developers wrote. A binary wheel is a more complex archive format, which can contain compiled C extension code. This is convenient for users, because compiling, say, &lt;em&gt;numpy&lt;/em&gt; from source takes a long time (~4 minutes on my desktop), and it is hard for package authors to ensure that their source code will compile on other people's machines. But it comes at a price--the compiled code is specific to the architecture and often the OS it was compiled on, so most packages with C extensions will build multiple wheel distributions, and &lt;code&gt;pip&lt;/code&gt; needs to decide which if any are suitable for your computer.&lt;/p&gt;

&lt;p&gt;To find the distributions available, &lt;code&gt;pip&lt;/code&gt; requests &lt;code&gt;https://pypi.org/simple/&amp;lt;somepackage&amp;gt;&lt;/code&gt;, which is a simple HTML page full of links, where the text of the link is the filename of the distribution. The filenames encode the version, kind of distribution, and for binary wheels, the architecture and OS they are compatible with. This format is complex enough to be covered by two different PEPs:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The version scheme is covered by &lt;a href="https://www.python.org/dev/peps/pep-0440/#version-scheme"&gt;PEP 440&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;Binary wheel filename compatibility tags are the subject of &lt;a href="https://www.python.org/dev/peps/pep-0425/"&gt;PEP 425&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;To select a distribution, &lt;code&gt;pip&lt;/code&gt; first determines which distributions are compatible with your system and implementation of python. For binary wheels, it parses the filenames according to PEP 425, extracting the &lt;em&gt;python implementation&lt;/em&gt;, &lt;em&gt;application binary interface&lt;/em&gt;, and &lt;em&gt;platform&lt;/em&gt;. The python implementation can be something as broad as &lt;code&gt;py2.py3&lt;/code&gt; (meaning "any implementation of python 2.X or 3.X") or it can specify a python interpreter and major version, such as &lt;code&gt;pp35&lt;/code&gt; (meaning PyPy version 3.5). The application binary interface is essentially what version of CPython's C-API the C extension code is compatible with, if there is any. Interpreting the platform portion of the compatibility tag is more difficult. It can be relatively obvious, like &lt;code&gt;win32&lt;/code&gt; for 32-bit Windows, but I am usually installing &lt;code&gt;manylinux1&lt;/code&gt; wheels. Which Linux distributions are compatible with &lt;code&gt;manylinux1&lt;/code&gt; is a subject of heavy debate on the &lt;code&gt;distutils&lt;/code&gt; mailing list. Luckily the process for source distributions is simpler—all source distributions are assumed to be compatible, at least at this step in the process.&lt;/p&gt;

&lt;p&gt;Once &lt;code&gt;pip&lt;/code&gt; has a list of compatible distributions, it sorts them by version, chooses the most recent version, and then chooses the "best" distribution for that version. It prefers binary wheels if there are any, and if they are multiple it chooses the one most specific to the install environment. These are just &lt;code&gt;pip&lt;/code&gt;'s default preferences though—they can be configured with options like &lt;code&gt;--no-binary&lt;/code&gt; or &lt;code&gt;--prefer-binary&lt;/code&gt;. The "best" distribution is either downloaded or installed from the local cache, which on Linux is usually located in &lt;code&gt;~/.cache/pip&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Determining the dependencies for this distribution is not simple either. In theory, one could just use the &lt;code&gt;requires_dist&lt;/code&gt; value from &lt;code&gt;https://pypi.org/pypi/&amp;lt;somepackage&amp;gt;/&amp;lt;version&amp;gt;/json&lt;/code&gt;. However, this relies on the package author uploading the correct metadata, and older packaging clients do not do so. So in practice &lt;code&gt;pip&lt;/code&gt; (and anyone else who wants to know the dependencies of a package) have to download and inspect it.&lt;/p&gt;

&lt;p&gt;For binary wheels, the dependencies are listed in a file called &lt;code&gt;METADATA&lt;/code&gt;. But for source distributions the dependencies are effectively whatever gets installed when you execute their &lt;code&gt;setup.py&lt;/code&gt; script with the &lt;code&gt;install&lt;/code&gt; command. There's no way to know unless you try it, which is what &lt;code&gt;pip&lt;/code&gt; does! Specifically, it leverages &lt;code&gt;setuptools&lt;/code&gt; to run &lt;code&gt;install&lt;/code&gt; up to the point where it knows what dependencies to install. However, this can be further complicated by the fact that running &lt;code&gt;install&lt;/code&gt; might &lt;em&gt;itself&lt;/em&gt; require dependencies. The standard way to specify this in a Python package is to pass a the &lt;code&gt;setup_requires&lt;/code&gt; argument to &lt;code&gt;setuptools.setup&lt;/code&gt;. By way of &lt;code&gt;setuptools&lt;/code&gt;, &lt;code&gt;pip&lt;/code&gt; will run &lt;code&gt;setup.py&lt;/code&gt; just enough to discover &lt;code&gt;setup_requires&lt;/code&gt;, install those dependencies, then go back and execute &lt;code&gt;setup.py&lt;/code&gt; again. Naturally, this is madness and &lt;code&gt;setup_requires&lt;/code&gt; should never be used. &lt;/p&gt;

&lt;p&gt;Once &lt;code&gt;pip&lt;/code&gt; has a list of requirements, it starts this whole process over again for each required package, taking into account any constraints on its version. It builds a whole tree of packages this way, until every dependency of every distribution it has found is already in the tree. This process breaks of course if there is a dependency cycle, but it will always terminate—after all, there are only finitely many python packages!&lt;/p&gt;

&lt;p&gt;What happens though if one of the distributions &lt;code&gt;pip&lt;/code&gt; finds violates the requirements of another, for example if it &lt;code&gt;pip&lt;/code&gt; first finds &lt;code&gt;idna&lt;/code&gt; version &lt;code&gt;2.5&lt;/code&gt; but then finds a distribution requiring &lt;code&gt;idna&amp;lt;=2.4&lt;/code&gt;? Well, it ignores the requirement and installs &lt;code&gt;idna&lt;/code&gt; anyway! There is a &lt;a href="https://github.com/pypa/pip/issues/988"&gt;longstanding issue&lt;/a&gt; open to add a true &lt;em&gt;dependency resolver&lt;/em&gt; to &lt;code&gt;pip&lt;/code&gt;, with lots of false starts and partial implementations, but none have ever quite made it in. This is of course in large part due to the complexity of determining the dependencies for a python package—it is very difficult to build an efficient dependency resolver when determining the dependencies of a &lt;em&gt;single&lt;/em&gt; candidate requires downloading and executing potentially megabytes of code!&lt;/p&gt;

&lt;p&gt;Next &lt;code&gt;pip&lt;/code&gt; has to actually build and install the package. If it downloaded a source distribution, and the &lt;code&gt;wheel&lt;/code&gt; package is installed, it will first build a binary wheel specifically for your machine out of the source. Then it needs to determine which library directory to install the package in—the system's, the user's, or a virtualenv's? This is controlled by &lt;code&gt;sys.prefix&lt;/code&gt;, which in turn is controlled by &lt;code&gt;pip&lt;/code&gt;'s executable path and the &lt;code&gt;PYTHONPATH&lt;/code&gt; and &lt;code&gt;PYTHONHOME&lt;/code&gt; environment variables. Finally, it moves the wheel files into the appropriate library directory, and compiles the python source files into bytecode for faster execution.&lt;/p&gt;

&lt;p&gt;Now your package is installed! I've really only scratched the surface—there are dozens of options that change &lt;code&gt;pip&lt;/code&gt;'s behavior, many corner cases of other distribution types and platform limitations, and I didn't even touch on installing multiple packages (which is handled differently than a package with multiple dependencies). But I hope it this was informative, if not useful.&lt;/p&gt;

</description>
      <category>python</category>
      <category>pip</category>
    </item>
  </channel>
</rss>
