<?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>DEV Community: Frost Ming</title>
    <description>The latest articles on DEV Community by Frost Ming (@frostming).</description>
    <link>https://dev.to/frostming</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%2F486061%2Fc67db6e0-aea8-463e-8d95-792bb5178f19.png</url>
      <title>DEV Community: Frost Ming</title>
      <link>https://dev.to/frostming</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/frostming"/>
    <language>en</language>
    <item>
      <title>A Review: Pipenv vs. Poetry vs. PDM</title>
      <dc:creator>Frost Ming</dc:creator>
      <pubDate>Fri, 26 Mar 2021 13:05:54 +0000</pubDate>
      <link>https://dev.to/frostming/a-review-pipenv-vs-poetry-vs-pdm-39b4</link>
      <guid>https://dev.to/frostming/a-review-pipenv-vs-poetry-vs-pdm-39b4</guid>
      <description>&lt;h1&gt;
  
  
  Abstract
&lt;/h1&gt;

&lt;p&gt;It is 2021 and we are all using or heard of package managers in Python, among which are Pipenv and Poetry. I also built a new package manager &lt;a href="https://frostming.com/2021/01-22/introducing-pdm/" rel="noopener noreferrer"&gt;PDM&lt;/a&gt; to solve similar problems. There exist some comparisons between them around the community, but this article is not going to talk about the user interface or their versatility, it is going to focus on two important aspects: performance and correctness.&lt;/p&gt;

&lt;h1&gt;
  
  
  Setup
&lt;/h1&gt;

&lt;p&gt;&lt;strong&gt;Pipenv&lt;/strong&gt;: &lt;code&gt;HEAD@275f7e151eb0aa17702215165a371df7da9ad476&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Poetry&lt;/strong&gt;: &lt;code&gt;1.1.5&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;PDM&lt;/strong&gt;: &lt;code&gt;HEAD@d72ba5d2ee0b0305f917d8739667ba78465c5cc8&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Python version&lt;/strong&gt;: &lt;code&gt;3.9.1&lt;/code&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  Performance
&lt;/h1&gt;

&lt;p&gt;Dependency set(in poetry format):&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.9"&lt;/span&gt;
&lt;span class="py"&gt;requests&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="py"&gt;git&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"https://github.com/psf/requests.git"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="py"&gt;tag&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"v2.25.0"&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="py"&gt;numpy&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"^1.19"&lt;/span&gt;

&lt;span class="nn"&gt;[tool.poetry.dev-dependencies]&lt;/span&gt;
&lt;span class="py"&gt;pytest&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"^5.2"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This includes a Git dependency and a heavy binary dependency. In the following result, the cost times are measured in seconds.&lt;/p&gt;

&lt;p&gt;Unless explicitly given, the measurements are done against the &lt;code&gt;install&lt;/code&gt; command.&lt;/p&gt;

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

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;Pipenv&lt;/th&gt;
&lt;th&gt;Poetry&lt;/th&gt;
&lt;th&gt;PDM&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Clean cache, no lockfile&lt;/td&gt;
&lt;td&gt;98&lt;/td&gt;
&lt;td&gt;150&lt;/td&gt;
&lt;td&gt;58&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;With cache, no lockfile&lt;/td&gt;
&lt;td&gt;117&lt;/td&gt;
&lt;td&gt;66&lt;/td&gt;
&lt;td&gt;28&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Clean cache, reuse lockfile*&lt;/td&gt;
&lt;td&gt;128&lt;/td&gt;
&lt;td&gt;145&lt;/td&gt;
&lt;td&gt;35&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;With cache, reuse lockfile**&lt;/td&gt;
&lt;td&gt;145&lt;/td&gt;
&lt;td&gt;50&lt;/td&gt;
&lt;td&gt;16&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;*&lt;/strong&gt;: Test with command &lt;code&gt;poetry add click&lt;/code&gt; &lt;code&gt;pipenv install --keep-outdated click&lt;/code&gt; &lt;code&gt;pdm add click&lt;/code&gt; respectively.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;**&lt;/strong&gt;: Commands are the same as above.&lt;/p&gt;

&lt;h2&gt;
  
  
  Performance Review
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Pipenv has a problematic cache system, which slows down the performance with the existence of caches&lt;/li&gt;
&lt;li&gt;Poetry and PDM both benefit a lot from the caches, PDM takes even less time.&lt;/li&gt;
&lt;li&gt;Pipenv uses a very different mechanism to reuse the lock file — it runs full locking first then modifies the content of the old lock file, while PDM can reuse the pinned versions in the lock file. Poetry improves a little with the lock file existing.&lt;/li&gt;
&lt;/ul&gt;

&lt;h1&gt;
  
  
  Correctness
&lt;/h1&gt;

&lt;p&gt;The goal of these 3 package managers is to produce a reproducible environment and they all try their best to make it work on cross-platforms and python versions. Let's see how it turns out.&lt;/p&gt;

&lt;h2&gt;
  
  
  Python Compatibility
&lt;/h2&gt;

&lt;p&gt;The project file is designed as following(Poetry):&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||^2.7"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And PDM:&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;[project]&lt;/span&gt;
&lt;span class="py"&gt;requires-python&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="py"&gt;"&amp;gt;=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;3.5&lt;/span&gt;&lt;span class="err"&gt;.*&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="err"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;They both reference to the same range of Python versions to support. Although Pipenv doesn't support a Python version range constraint, I will also post its results here as a reference.&lt;/p&gt;

&lt;p&gt;Now let's add one dependency &lt;code&gt;pytest&lt;/code&gt; from a clean project. This dependency is chosen purposely because it has a different version set to support Python 2 and Python 3.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Result of Poetry:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;poetry add pytest
&lt;span class="c"&gt;...
&lt;/span&gt;&lt;span class="go"&gt;Using version ^6.2.2 for pytest

Updating dependencies
Resolving dependencies...

  SolverProblemError

&lt;/span&gt;&lt;span class="gp"&gt;  The current project's Python requirement (&amp;gt;&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;2.7,&amp;lt;3.0 &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt;3.6,&amp;lt;4.0&lt;span class="o"&gt;)&lt;/span&gt; is not compatible with some of the required packages Python requirement:
&lt;span class="gp"&gt;    - pytest requires Python &amp;gt;&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;3.6, so it will not be satisfied &lt;span class="k"&gt;for &lt;/span&gt;Python &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt;2.7,&amp;lt;3.0
&lt;span class="go"&gt;
&lt;/span&gt;&lt;span class="gp"&gt;  Because no versions of pytest match &amp;gt;&lt;/span&gt;6.2.2,&amp;lt;7.0.0
&lt;span class="gp"&gt;   and pytest (6.2.2) requires Python &amp;gt;&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;3.6, pytest is forbidden.
&lt;span class="go"&gt;  So, because foo depends on pytest (^6.2.2), version solving failed.
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Lock failed as &lt;code&gt;6.2.2&lt;/code&gt; was picked but it was not compatible with python 2.7. Anyhow Poetry shows a well human-readable error message telling people what happened and how to solve it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Result of Pipenv&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Depending on Python 3 or Python 2 is used to create the virtualenv(with &lt;code&gt;--three/--two&lt;/code&gt; option), &lt;code&gt;pytest&lt;/code&gt; is locked to &lt;code&gt;6.2.2&lt;/code&gt; and &lt;code&gt;4.6.11&lt;/code&gt; respectively. The lock file surely can't work on both Python 2 and Python 3 environment at the same time.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Result of PDM&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;pdm add pytest
&lt;span class="go"&gt;Adding packages to default dependencies: pytest
✔ 🔒 Lock successful
&lt;/span&gt;&lt;span class="c"&gt;...
&lt;/span&gt;&lt;span class="go"&gt;
  ✔ Install pytest 4.6.11 successful

&lt;/span&gt;&lt;span class="c"&gt;...
&lt;/span&gt;&lt;span class="go"&gt;🎉 All complete!
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;pytest&lt;/code&gt; is correctly resolved to &lt;code&gt;4.6.11&lt;/code&gt; that supports all versions within the &lt;code&gt;requires-python&lt;/code&gt; contraint.&lt;/p&gt;

&lt;h2&gt;
  
  
  Version Tree Searching
&lt;/h2&gt;

&lt;p&gt;A dependency resolver should be able to search for other candidates when the current one causes version conflicts.&lt;/p&gt;

&lt;p&gt;Let's take the example from &lt;a href="https://github.com/python-poetry/poetry#dependency-resolution" rel="noopener noreferrer"&gt;Poetry's README&lt;/a&gt;:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Result of Pipenv&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;pipenv &lt;span class="nb"&gt;install &lt;/span&gt;oslo.utils&lt;span class="o"&gt;==&lt;/span&gt;1.4.0
&lt;span class="c"&gt;...
&lt;/span&gt;&lt;span class="go"&gt;There are incompatible versions in the resolved dependencies:
&lt;/span&gt;&lt;span class="gp"&gt;  pbr!=0.7,&amp;lt;1.0,&amp;gt;&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;0.6 &lt;span class="o"&gt;(&lt;/span&gt;from oslo.utils&lt;span class="o"&gt;==&lt;/span&gt;1.4.0-&amp;gt;-r C:&lt;span class="se"&gt;\U&lt;/span&gt;sers&lt;span class="se"&gt;\F&lt;/span&gt;ROSTM~1&lt;span class="se"&gt;\A&lt;/span&gt;ppData&lt;span class="se"&gt;\L&lt;/span&gt;ocal&lt;span class="se"&gt;\T&lt;/span&gt;emp&lt;span class="se"&gt;\p&lt;/span&gt;ipenvkbeeio2trequirements&lt;span class="se"&gt;\p&lt;/span&gt;ipenv-0zsj0laj-constraints.txt &lt;span class="o"&gt;(&lt;/span&gt;line 3&lt;span class="o"&gt;))&lt;/span&gt;
&lt;span class="gp"&gt;  pbr!=2.1.0,&amp;gt;&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;2.0.0 &lt;span class="o"&gt;(&lt;/span&gt;from oslo.i18n&lt;span class="o"&gt;==&lt;/span&gt;5.0.1-&amp;gt;oslo.utils&lt;span class="o"&gt;==&lt;/span&gt;1.4.0-&amp;gt;-r C:&lt;span class="se"&gt;\U&lt;/span&gt;sers&lt;span class="se"&gt;\F&lt;/span&gt;ROSTM~1&lt;span class="se"&gt;\A&lt;/span&gt;ppData&lt;span class="se"&gt;\L&lt;/span&gt;ocal&lt;span class="se"&gt;\T&lt;/span&gt;emp&lt;span class="se"&gt;\p&lt;/span&gt;ipenvkbeeio2trequirements&lt;span class="se"&gt;\p&lt;/span&gt;ipenv-0zsj0laj-constraints.txt &lt;span class="o"&gt;(&lt;/span&gt;line 3&lt;span class="o"&gt;))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Unable to resolve* since Pipenv failed to search for lower versions of &lt;code&gt;oslo.i18n&lt;/code&gt; to find one that is compatible with &lt;code&gt;pbr&amp;lt;1.0&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;*&lt;/strong&gt;: Be aware that Pipenv's strategy is "lock after install", so the incompatible package will be installed into the environment before the lock failure is reported.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Result of Poetry&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;As illustrated in the README, poetry successfully resolves with &lt;code&gt;oslo.i18n==2.1.0&lt;/code&gt; . It searches along the candidate list of &lt;code&gt;oslo.i18n&lt;/code&gt; and discard those that bring conflicts.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Result of PDM&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;pdm add oslo.utils&lt;span class="o"&gt;==&lt;/span&gt;1.4.0
&lt;span class="c"&gt;...
&lt;/span&gt;&lt;span class="go"&gt;  ✔ Install oslo.i18n 2.1.0 successful
&lt;/span&gt;&lt;span class="c"&gt;...
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Similarly, PDM also locks successfully with the same version of &lt;code&gt;oslo.i18n&lt;/code&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Environment Marker Propagation
&lt;/h2&gt;

&lt;p&gt;To make a cross-platform project template, we often need to define some platform-specific dependencies and those also may have their subdependencies. These platform-specific dependencies may not be able to build successfully on the source system. We don't want these dependencies and subdependencies to be installed on the systems that do not match the requirements.&lt;/p&gt;

&lt;p&gt;Let's start by adding a dependency &lt;code&gt;gevent; os_name == "posix"&lt;/code&gt;, which has several subdependencies. Commands are run from a Windows computer.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Result of Pipenv(Pipfile.lock)&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"_meta"&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;span class="nl"&gt;"hash"&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;span class="nl"&gt;"sha256"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"9ce84144d1fc47c173581ae74c51ffbe28ac242b1fe0e1642915e803f15d3063"&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="nl"&gt;"pipfile-spec"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"requires"&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;span class="nl"&gt;"sources"&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;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"pypi"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="nl"&gt;"url"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://pypi.org/simple"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="nl"&gt;"verify_ssl"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&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;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"default"&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;span class="nl"&gt;"gevent"&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;span class="nl"&gt;"hashes"&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;span class="err"&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="nl"&gt;"markers"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"os_name == 'posix'"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"version"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"==21.1.2"&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;span class="nl"&gt;"develop"&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;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Only &lt;code&gt;gevent&lt;/code&gt; is resolved with the marker in the lock file and Pipenv stops trying to find its children dependencies when the marker doesn't match the current system. This means if you use this Pipfile.lock to deploy on the target Linux server, some significant dependencies WILL NOT be installed!&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Result of Poetry&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;gevent&lt;/code&gt; together with &lt;code&gt;greenlet&lt;/code&gt;, &lt;code&gt;cffi&lt;/code&gt;, &lt;code&gt;pycparser&lt;/code&gt;, &lt;code&gt;zope.event&lt;/code&gt;, &lt;code&gt;zope.interface&lt;/code&gt; are pinned in the lock file and don't get installed to the environment, which is the expected behavior. But I didn't figure out how to add a requirement with markers from the CLI and I have to manually write it in &lt;code&gt;pyproject.toml&lt;/code&gt;. Further, if I run &lt;code&gt;poetry add pycparser&lt;/code&gt; after that, &lt;code&gt;pycparser&lt;/code&gt; can be installed correctly.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Result of PDM&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The same result as Poetry, except that in &lt;code&gt;pdm.lock&lt;/code&gt;, children dependencies also have the marker &lt;code&gt;os_name == 'nt'&lt;/code&gt; so that installers won't have to search the dependency tree to see whether a single package should be installed.&lt;/p&gt;

&lt;h1&gt;
  
  
  Conclusion
&lt;/h1&gt;

&lt;p&gt;On the performance perspective, Pipenv doesn't play well due to its design choice that it integrates with other third-party tools and libraries instead of building its own. Pipenv can only wrap, combine, and do a little improvement on those upstream libraries. Moreover, Pipenv doesn't meet the goal of reproducible environment as well. It can produce a determinsitic installation setup on the source system but it is not a good idea to deploy to a different system without a careful check.&lt;/p&gt;

&lt;p&gt;On contrast, Poetry and PDM are both doing great on performance and correctness, PDM is even better especially on the time cost and compatible dependency resolving. If you do not know this tool yet, &lt;a href="https://pdm.fming.dev" rel="noopener noreferrer"&gt;start now&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>python</category>
      <category>programming</category>
      <category>productivity</category>
      <category>packaging</category>
    </item>
    <item>
      <title>You don't really need a virtualenv</title>
      <dc:creator>Frost Ming</dc:creator>
      <pubDate>Fri, 22 Jan 2021 03:03:11 +0000</pubDate>
      <link>https://dev.to/frostming/you-don-t-really-need-a-virtualenv-4bae</link>
      <guid>https://dev.to/frostming/you-don-t-really-need-a-virtualenv-4bae</guid>
      <description>&lt;p&gt;When you develop a Python project, you need to install the project's dependencies. For a long time, tutorials and articles have told you to use a virtual environment to isolate the project's dependencies. This way you don't contaminate the working set of other projects, or the global interpreter, to avoid possible version conflicts. We usually have to do these things:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;python3 &lt;span class="nt"&gt;-m&lt;/span&gt; venv venv  &lt;span class="c"&gt;# make a virtualenv named `venv`&lt;/span&gt;
&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;.&lt;/span&gt; venv/bin/activate   &lt;span class="c"&gt;# activate the virtualenv&lt;/span&gt;
&lt;span class="gp"&gt;(venv) $&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;pip &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-r&lt;/span&gt; requirements.txt  &lt;span class="c"&gt;# install the dependencies&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;There are also workflow tools that simplify this process, such as &lt;a href="https://pipenv.pypa.io" rel="noopener noreferrer"&gt;Pipenv&lt;/a&gt; and &lt;a href="https://python-poetry.org/" rel="noopener noreferrer"&gt;Poetry&lt;/a&gt;. They create virtual environments for you without perception and then install dependencies into them. They are used by a wide range of users. A virtual environment contains a Python interpreter and has the same directory structure as a normal installation so that it can be used as if it were a standalone Python installation.&lt;/p&gt;

&lt;h2&gt;
  
  
  The problems with virtual environments
&lt;/h2&gt;

&lt;p&gt;Virtualenvs help us isolate project dependencies, but things get tricky when it comes to nested venvs: One installs the virtualenv manager(like Pipenv or Poetry) using a venv encapsulated Python, and creates more venvs using the tool which is based on an encapsulated Python. One day a minor release of Python is out and one has to check all those venvs and upgrade them if required before they can safely delete the out-dated Python version.&lt;/p&gt;

&lt;p&gt;Another scenario is global tools. There are many tools that are not tied to any specific virtualenv and are supposed to work with each of them. Examples are profiling tools and third-party REPLs. We also wish them to be installed in their own isolated environments. It's impossible to make them work with virtualenv, even if you have activated the virtualenv of the target project you want to work on because the tool is lying in its own virtualenv and it can only see the libraries installed in it. So we have to install the tool for each project.&lt;/p&gt;

&lt;p&gt;I've been maintaining the Pipenv project as a collaborator for the past two years and became a member of PyPA in early 2020. I am always thinking if the virtual environment is really a must-to-have for Python projects, just like &lt;code&gt;npm&lt;/code&gt;, it doesn't need a cloned &lt;code&gt;node&lt;/code&gt; binary, but just a &lt;code&gt;node_modules&lt;/code&gt; directory that is unique for each project.&lt;/p&gt;

&lt;h2&gt;
  
  
  PEP 582 -- Python local packages directory
&lt;/h2&gt;

&lt;p&gt;The solution has been existing for a long time. &lt;a href="https://www.python.org/dev/peps/pep-0582/" rel="noopener noreferrer"&gt;PEP 582&lt;/a&gt; was originated in 2018 and is still a draft proposal till the time I wrote this article, but I found out it is exactly what &lt;code&gt;node_modules&lt;/code&gt; are in Python.&lt;/p&gt;

&lt;p&gt;Say you have a project with the following structure:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;.
├── __pypackages__
│   └── 3.8
│       └── lib
└── my_script.py
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;As specified in the PEP 582, if you run &lt;code&gt;python3.8 /path/to/my_script.py&lt;/code&gt;, &lt;code&gt;__pypackages__/3.8/lib&lt;/code&gt; will be added to &lt;code&gt;sys.path&lt;/code&gt;, and the libraries inside will become import-able in &lt;code&gt;my_script.py&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Now let's review the two problems I mentioned in the last section and see how they change with the power of PEP 582. For the first problem, the main cause is that the virtual environment is bound to a cloned Python interpreter on which the subsequent library searching based. It takes advantage of Python's existing mechanisms without any other complex changes but makes the entire virtual environment to become unavailable when the Python interpreter is stale. With the local packages directory, you don't have a Python interpreter any more, the library path is directly appended to &lt;code&gt;sys.path&lt;/code&gt;, so you can freely move and copy it.&lt;/p&gt;

&lt;p&gt;For the second, once again, you just call the tool against the project you want to analyze, and the &lt;code&gt;__pypackages__&lt;/code&gt; sitting inside the project will be loaded automatically. This way you only need to keep one copy of the global tool and make it work with multiple projects.&lt;/p&gt;

&lt;h2&gt;
  
  
  PDM -- A new Python package manager and workflow tool
&lt;/h2&gt;

&lt;p&gt;Starting from the PEP, I made &lt;a href="https://pdm.fming.dev" rel="noopener noreferrer"&gt;PDM&lt;/a&gt;, a new Python package manager and workflow tool that leverages PEP 582 to get rid of virtualenv entirely. It installs dependencies into the local package directory &lt;code&gt;__package__&lt;/code&gt; and makes Python interpreters aware of it with &lt;a href="https://pdm.fming.dev/#enable-pep-582-globally" rel="noopener noreferrer"&gt;a very simple setup&lt;/a&gt;. It is not only an implementation of PEP 582 but also the only package manager that supports &lt;a href="https://www.python.org/dev/peps/pep-0621/" rel="noopener noreferrer"&gt;PEP 621&lt;/a&gt;, a new metadata format based on &lt;code&gt;pyproject.toml&lt;/code&gt; which becomes the standard recently. It is foreseen that pip will also gradually support this format. Besides, PDM uses the same dependency resolver as pip and has a full-featured plugin system, allowing for community-contributed plugins to enhance the functionalities.&lt;/p&gt;

&lt;p&gt;In PDM, PEP 582 is not mandatory, you can also stick with virtualenv. PDM can detect &lt;strong&gt;existing&lt;/strong&gt; venvs but not create new ones.&lt;/p&gt;

&lt;p&gt;Another thing that is noteworthy is its dependency resolution mechanism -- it tries to lock versions that are compatible with the &lt;code&gt;requires-python&lt;/code&gt; value of the project. Say your project requires Python 2.7 or 3.6 upper and you want to add &lt;code&gt;pytest&lt;/code&gt; as a development dependency, in Pipenv(ver. &lt;code&gt;2020.11.15&lt;/code&gt;) you have to pin &lt;code&gt;pytest = "&amp;lt;5"&lt;/code&gt; manually in &lt;code&gt;Pipfile&lt;/code&gt;. And in Poetry(ver. &lt;code&gt;1.1.4&lt;/code&gt;) if you run &lt;code&gt;poetry add -D pytest&lt;/code&gt; you will get:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;The current project's Python requirement (&amp;gt;&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;2.7,&amp;lt;3.0 &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt;3.6,&amp;lt;4.0&lt;span class="o"&gt;)&lt;/span&gt; is not compatible with some of the required packages Python requirement:
&lt;span class="gp"&gt;    - pytest requires Python &amp;gt;&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;3.6, so it will not be satisfied &lt;span class="k"&gt;for &lt;/span&gt;Python &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt;2.7,&amp;lt;3.0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Yes, it tells you to upgrade your Python requires version. However, in PDM, you can lock successfully:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="go"&gt;❯ pdm add -d pytest
Adding packages to dev dependencies: pytest
✔ 🔒 Lock successful
Changes are written to pdm.lock.
Changes are written to pyproject.toml.
Synchronizing working set with lock file: 11 to add, 0 to update, 0 to remove

  ✔ Install atomicwrites 1.4.0 successful
  ✔ Install colorama 0.4.4 successful
  ✔ Install packaging 20.8 successful
  ✔ Install more-itertools 5.0.0 successful
  ✔ Install pyparsing 2.4.7 successful
  ✔ Install attrs 20.3.0 successful
  ✔ Install pluggy 0.13.1 successful
  ✔ Install py 1.10.0 successful
  ✔ Install pytest 4.6.11 successful
  ✔ Install six 1.15.0 successful
  ✔ Install wcwidth 0.2.5 successful

🎉 All complete!
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;As you can see, &lt;code&gt;pytest&lt;/code&gt; is pinned to &lt;code&gt;4.*&lt;/code&gt; that is compatible with Python 2.7 and Python 3.6+.&lt;/p&gt;

&lt;p&gt;For more features and demos, please refer to the &lt;a href="https://pdm.fming.dev" rel="noopener noreferrer"&gt;PDM's website&lt;/a&gt; and &lt;a href="https://github.com/frostming/pdm" rel="noopener noreferrer"&gt;GitHub repo&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Some random words
&lt;/h2&gt;

&lt;p&gt;You may have seen this &lt;a href="https://xkcd.com/927/" rel="noopener noreferrer"&gt;comic&lt;/a&gt; before, there are already so many package managers in Python's world, do we need a new one? No, I think. Gladly we have seen many improvements in the Python packaging ecosystem and the official installer pip, such as &lt;a href="https://www.python.org/dev/peps/pep-0517/" rel="noopener noreferrer"&gt;PEP 517&lt;/a&gt;/&lt;a href="https://www.python.org/dev/peps/pep-0518/" rel="noopener noreferrer"&gt;PEP 518&lt;/a&gt;, and the &lt;a href="https://discuss.python.org/t/announcement-pip-20-2-release/4863" rel="noopener noreferrer"&gt;new dependency resolver&lt;/a&gt;, more to come in the future. But before the day comes, why not try something different from the traditional, why not make something new that makes at least myself happy. If you think so, PDM should be an option, hopefully.&lt;/p&gt;

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