<?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: Jürgen Hermann</title>
    <description>The latest articles on DEV Community by Jürgen Hermann (@jhermann).</description>
    <link>https://dev.to/jhermann</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%2F183673%2Fba212453-0d28-4499-aede-ffbf141739e1.png</url>
      <title>DEV Community: Jürgen Hermann</title>
      <link>https://dev.to/jhermann</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/jhermann"/>
    <language>en</language>
    <item>
      <title>SVG Thumbnails in Windows 11 with Microsoft PowerToys</title>
      <dc:creator>Jürgen Hermann</dc:creator>
      <pubDate>Sun, 27 Aug 2023 21:37:49 +0000</pubDate>
      <link>https://dev.to/jhermann/svg-thumbnails-in-windows-11-with-microsoft-powertoys-2nkh</link>
      <guid>https://dev.to/jhermann/svg-thumbnails-in-windows-11-with-microsoft-powertoys-2nkh</guid>
      <description>&lt;p&gt;Install the PowerToys from the &lt;a href="https://apps.microsoft.com/store/detail/microsoft-powertoys/XP89DCGQ3K6VLD"&gt;Microsoft Store&lt;/a&gt; so you get the Explorer extensions as shown in the video below.&lt;/p&gt;

&lt;p&gt;For avid Inkscape users, this is a great addition and gets you the same level of convenience any Linux system offers by default. 😏 🐧&lt;/p&gt;

&lt;p&gt;&lt;a href="https://youtu.be/ZAGDNwk7Bd8?si=BCxTVhc04yNb8HD8"&gt;https://youtu.be/ZAGDNwk7Bd8?si=BCxTVhc04yNb8HD8&lt;/a&gt;&lt;/p&gt;

</description>
      <category>svg</category>
      <category>windows11</category>
      <category>powertoys</category>
      <category>inkscape</category>
    </item>
    <item>
      <title>🗣 Communication Hack – Use Labelled Links</title>
      <dc:creator>Jürgen Hermann</dc:creator>
      <pubDate>Tue, 31 Mar 2020 09:09:08 +0000</pubDate>
      <link>https://dev.to/jhermann/communication-hack-use-labelled-links-30bl</link>
      <guid>https://dev.to/jhermann/communication-hack-use-labelled-links-30bl</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;Do your readers a favor and use the “Copy as Markdown for Chrome &amp;amp; Firefox” add-on to post web links.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;When you post links to some information bit on the web or your intranet, using &lt;em&gt;raw&lt;/em&gt; technical links typically reduces information content. For example, a link featuring just a post number (&lt;code&gt;…/post/0815&lt;/code&gt;) is purely technical, and takes up a lot of space while having almost zero information content – it just tells your audience the target site via the domain, and that is all.&lt;/p&gt;

&lt;p&gt;🌐 🚑  &lt;a href="https://github.com/chitsaou/copy-as-markdown#copy-as-markdown-for-chrome--firefox"&gt;Copy as Markdown for Chrome &amp;amp; Firefox&lt;/a&gt; to the rescue. &lt;/p&gt;

&lt;p&gt;The above link was created like this:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--LUQmvE7_--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/sqa7td7hyynkymbapity.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--LUQmvE7_--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/sqa7td7hyynkymbapity.png" alt="Copy as MD Example"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Using that context menu item copies the link &lt;em&gt;including&lt;/em&gt; its label (or the current selection if there is one, like shown above) as markdown text into your clipboard. Markdown is a universal format that can be pasted into lots of channels: Rocket.Chat and other web chats, blog sites like this one, many wikis (like Atlassian Confluence), etc.&lt;/p&gt;

&lt;p&gt;In Confluence, the trick to know is the “Insert Markup” menu item in the editor (&lt;code&gt;Ctrl-Shift-D&lt;/code&gt;), enabling you to paste the markdown text almost directly into any page – just make sure the format drop-down above the text area says markdown and not Confluence wiki markup.&lt;/p&gt;

&lt;p&gt;So do your audience a favor, install that add-on, and in future provide them with &lt;strong&gt;readable&lt;/strong&gt; links that allow them to judge whether they need to click-through on that link.&lt;/p&gt;

</description>
      <category>communication</category>
      <category>remote</category>
      <category>wfh</category>
      <category>lifehack</category>
    </item>
    <item>
      <title>Software Development is the Metamorphosis of Caffeine into Code</title>
      <dc:creator>Jürgen Hermann</dc:creator>
      <pubDate>Sun, 29 Mar 2020 20:58:39 +0000</pubDate>
      <link>https://dev.to/jhermann/software-development-is-the-metamorphosis-of-caffeine-into-code-1ck1</link>
      <guid>https://dev.to/jhermann/software-development-is-the-metamorphosis-of-caffeine-into-code-1ck1</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;A list of my personal coffee gear – a long answer to &lt;a href="https://dev.to/mayankjoshi/what-type-of-coffee-you-like-1hli"&gt;What type of coffee you like?&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Being able to brew a &lt;em&gt;decent&lt;/em&gt; cuppa joe &lt;em&gt;at home&lt;/em&gt; is more essential than ever, in times without access to &lt;a href="https://jobs.1und1.de/en/"&gt;my company's&lt;/a&gt; professional WMF coffee machines – about 7.5k€ if you buy them, a little out of my price range, and see the note on cleaning further below. 🏡 🍵 😋&lt;/p&gt;

&lt;p&gt;Let's start with a simple list of my gear, including current prices (from amazon.de).&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Product&lt;/th&gt;
&lt;th&gt;Price&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Aerobie AeroPress A80&lt;/td&gt;
&lt;td&gt;29€&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Accessory: Fellow Prismo&lt;/td&gt;
&lt;td&gt;29€&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Kitchen Timer&lt;/td&gt;
&lt;td&gt;14€&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Digital Scale&lt;/td&gt;
&lt;td&gt;14€&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Kettle: Uarter 2000W&lt;/td&gt;
&lt;td&gt;42€&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Coffee Grinder: Graef CM 800&lt;/td&gt;
&lt;td&gt;111€&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The mentioned prices should give you a hint whether you want to go down this specific rabbit hole. Keep in mind that people spend a whole lot of money on their hobbies, or coffee-2-go for that matter – so just regard drinking a decent cup as your hobby, then it's all normal and not snobbish at all. 😀&lt;/p&gt;

&lt;h2&gt;
  
  
  The Coffee
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--pDD6Io9c--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/0og34hb4jov6p3ztqnp6.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--pDD6Io9c--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/0og34hb4jov6p3ztqnp6.jpg" alt="TassKaff"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Finding the brand of coffee you like is all up to you – taste is subjective by nature, and that also includes milk and sweeteners.&lt;/p&gt;

&lt;p&gt;However, there are a few guiding principles: get it as &lt;em&gt;fresh(ly roasted)&lt;/em&gt; as possible, get &lt;em&gt;whole beans&lt;/em&gt;, grind them &lt;em&gt;right before brewing&lt;/em&gt; your cup, and get &lt;em&gt;100% Arabica only&lt;/em&gt; (cheap coffee is of the Robusta variant, or a blend). Spoiled coffee beans are uneven in shape and color, and are brittle with a lot of crumbling – avoid. One kg of non-specialty coffee beans will go for around 15€ in Germany, and a little more for fair-trade brands.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Coffee Maker
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--l9NmMGbK--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/ttdvajld1ho5lytm5mq9.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--l9NmMGbK--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/ttdvajld1ho5lytm5mq9.jpg" alt="Aerobie AeroPress A80"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The AeroPress is hailed by many as &lt;em&gt;the&lt;/em&gt; way for brewing single cups of coffee. It is certainly cheaper and way more sanitary than the usual fully automatic coffee machines for end-consumers, which normally develop a nice population of mildew and other gunk in their insides, unless you religiously clean them every day. For the AeroPress, you need 15 seconds of rinsing under running water. &lt;/p&gt;

&lt;p&gt;I evolved to the AeroPress via several simple electric coffee makers, an espresso machine, a fully automatic coffee maker, back to the simple things with various french presses, to finally settle on what I use now.&lt;/p&gt;

&lt;p&gt;As for dosage, I typically go for 25 g coffee per cup of about 400 ml beverage. With the AeroPress, you get a pretty concentrated brew, but adding more hot water after filling the cup gets you to your preferred strength. And there's a lot of other variables involved: dark or light roast, grind size, water temperature, extraction time. You have to experiment in the beginning to get to your personal favorite recipe.&lt;/p&gt;

&lt;p&gt;Because volumetric measurement definitely torpedoes repeatability of recipes, and extraction time is quite important, you also need these two things (possibly also as scales with an integrated timer):&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--5b41wode--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/59dtqh5ry2rho991txnt.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--5b41wode--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/59dtqh5ry2rho991txnt.png" alt="Timer + Scales"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You'll also find plenty of videos on the net showing you how to use an AeroPress, including all the variants and special recipes people came up with.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--BYHPx-Hh--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/bmdwjhfuewg51h4o3m0x.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--BYHPx-Hh--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/bmdwjhfuewg51h4o3m0x.jpg" alt="Fellow Prismo"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The &lt;em&gt;Fellow Prismo&lt;/em&gt; is an accessory to replace the original filter holder, featuring a pressure-sensitive valve and including a stainless steel filter. Using it you can make espresso-style coffee with the AeroPress, but I got mine because it is a way to use the so-called ‘inverted’ style of AeroPress recipes, without the involved hazard of juggling hot brown water around.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Grinder
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--W9p9ZahH--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/rsnh1pbj3tgm2m01xg34.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--W9p9ZahH--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/rsnh1pbj3tgm2m01xg34.jpg" alt="Graef CM80"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Using a good grinder is just as essential as using good coffee. You can ruin great coffee with a bad grinder, e.g. one that produces particle sizes from dust to grainy, all at the same time. And no grinder on this world can make stale or spoiled coffee beans any good.&lt;/p&gt;

&lt;p&gt;I have the CM 80 as shown above, but the available model is the CM 800 featured in the list at the top.&lt;/p&gt;

&lt;p&gt;If you get no workout otherwise, a very good manual burr grinder is also an option. 💪&lt;/p&gt;

&lt;h2&gt;
  
  
  The Kettle
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--_zFahYmL--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/anyqkzhtn7yaj48tiqzq.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--_zFahYmL--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/anyqkzhtn7yaj48tiqzq.jpg" alt="Water Kettle"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I replaced my plain old steel kettle by a glass one with temperature control (in 5°C steps) and a few other gimmicks. I really like the glass, especially when filling it – no narrow gauges you can only see from one specific angle, or similar things.&lt;/p&gt;

&lt;h2&gt;
  
  
  Closing Thoughts
&lt;/h2&gt;

&lt;p&gt;I never was a friend of coffee shops and their inherent waste of resources and, well, &lt;em&gt;money&lt;/em&gt;. I rather invest that money and some time to drink my first coffee at home, or take it with me in a steel vacuum bottle or cup. Working for a company offering great free coffee helps too.&lt;/p&gt;

&lt;p&gt;I can also recommend watching &lt;a href="https://www.youtube.com/channel/UCMb0O2CdPBNi-QqPk5T3gsQ"&gt;James Hoffmann&lt;/a&gt; on YouTube. Yes, he is a “coffee professional” (i.e. a barista by trade), but his tips are still quite grounded and useful for mere mortals, like a recent test of water filters. He's also a funny guy, watch his tests of various coffee drinks for a good laugh.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;Photo credits:&lt;/em&gt; &lt;a href="https://www.flickr.com/photos/adafruit/"&gt;Adafruit Industries&lt;/a&gt; [CC BY-NC-SA 2.0]&lt;/p&gt;
&lt;/blockquote&gt;

</description>
      <category>watercooler</category>
      <category>remote</category>
      <category>coffee</category>
      <category>wfh</category>
    </item>
    <item>
      <title>🌠 Listing My GitHub Stars</title>
      <dc:creator>Jürgen Hermann</dc:creator>
      <pubDate>Sun, 29 Mar 2020 14:01:41 +0000</pubDate>
      <link>https://dev.to/jhermann/listing-your-github-stars-5g3f</link>
      <guid>https://dev.to/jhermann/listing-your-github-stars-5g3f</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;&lt;a href="https://github.com/maguowei/starred"&gt;starred&lt;/a&gt; is a Python tool to create a repository that lists all your GitHub stars by topic.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Having a list of your stars organized by topic helps you as well as others to explore that database of curated know-how from another angle than what GitHub's web interface provides. On the “Stars” tab of your profile page, you can filter by language / technology or a keyword, and sort the list by history, popularity, or activity.&lt;/p&gt;

&lt;p&gt;The &lt;a href="https://github.com/maguowei/starred"&gt;starred&lt;/a&gt; CLI tool gets all your stars via the GitHub API and writes them to a README file that is then committed into a dedicated repository in your account.&lt;/p&gt;

&lt;p&gt;The &lt;a href="https://github.com/jhermann/observatory/blob/master/README-AtoO.md#readme"&gt;end result&lt;/a&gt; looks like this:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--MWlf8DaD--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/esiq5txxstdejkw7c0y6.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--MWlf8DaD--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/esiq5txxstdejkw7c0y6.png" alt="Sample ‘Starred’ Listing"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This also makes it easy to search for repositories and their descriptions, by using the in-page search of your bowser (&lt;code&gt;Ctrl-F&lt;/code&gt;).&lt;/p&gt;

&lt;p&gt;While we're on the topic of searching for keywords in your stars, the &lt;a href="https://github.com/wolfg1969/oh-my-stars"&gt;oh-my-stars&lt;/a&gt; tool does just that.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--XhfHC9_5--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/mseeq8d60sxmv1t5t2zi.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--XhfHC9_5--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/mseeq8d60sxmv1t5t2zi.png" alt="oh-my-stars"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;But that has to wait for another post…&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;Image credits:&lt;/em&gt; &lt;a href="http://www.johnsastrophotos.com/uploads/2/8/4/9/28492009/cometlovejoy2.jpg"&gt;John Vermette&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

</description>
      <category>showdev</category>
      <category>github</category>
      <category>python</category>
      <category>tools</category>
    </item>
    <item>
      <title>Packaging Python Applications for Debian</title>
      <dc:creator>Jürgen Hermann</dc:creator>
      <pubDate>Sat, 28 Mar 2020 05:00:00 +0000</pubDate>
      <link>https://dev.to/jhermann/packaging-python-applications-for-debian-2p0m</link>
      <guid>https://dev.to/jhermann/packaging-python-applications-for-debian-2p0m</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;Easily deploy any Python application in form of an ‘omnibus’ Debian packaxge, i.e. one that contains all the application's dependencies, just like in a Java WAR. A basic understanding of Debian packaging, the Linux command prompt, and Python tooling is assumed.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

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

&lt;p&gt;In this article, I'll show how to use &lt;code&gt;dh-virtualenv&lt;/code&gt; to create self-contained Debian packages to deploy a Python application. The resulting package is very similar to a &lt;em&gt;executable JAR&lt;/em&gt; that you can start via &lt;code&gt;java -jar&lt;/code&gt;, in that it contains all the moving parts except Python itself, without influencing or being influenced by version requirements of other applications. This also frees you from being restricted to the dependencies and their versions found on your target platforms, and makes porting to several different target environments easier.&lt;/p&gt;

&lt;p&gt;The advantage of using a Debian package for deployment as opposed to the native Python tool chain is that you are less dependent on typical development tools and services, i.e. to deploy to QA or production environments you need neither Internet access nor any compiler suites (for extension packages). To achieve the same with direct use of &lt;code&gt;virtualenv&lt;/code&gt; and &lt;code&gt;pip&lt;/code&gt;, you'd need to have an in-house PyPI repository accessible from production networks, and also release any extension packages as wheels pre-built for the target platform. Removing and updating an application is also much easier with Debian packages.&lt;/p&gt;

&lt;p&gt;To use &lt;code&gt;dh-virtualenv&lt;/code&gt;, you just have to extend your existing application project with a &lt;code&gt;debian&lt;/code&gt; subdirectory – project meta-data like &lt;code&gt;pip&lt;/code&gt; requirements and so on will be leveraged to build the final package, i.e. common tasks are delegated to the standard Python eco-system.&lt;/p&gt;

&lt;p&gt;Note that just like with any other form of omnibus packaging, you take over the responsibility to release security updates of the contained dependencies in a timely manner.&lt;/p&gt;

&lt;h2&gt;
  
  
  How does it work?
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;dh-virtualenv&lt;/code&gt; is a &lt;em&gt;debhelper&lt;/em&gt; plugin that extends the normal Debian tool chain for package building with the ability to create a Python &lt;a href="https://virtualenv.pypa.io/"&gt;virtualenv&lt;/a&gt; (an isolated Python environment), and then wrap that into the final Debian package.&lt;/p&gt;

&lt;p&gt;Depending on the details of the application, you often also have to provide some kind of configuration of the software itself, and possibly some means to run it as a service. This can be done in several ways:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;add a &lt;code&gt;debian/«pkg».install&lt;/code&gt; descriptor to add configuration files to the Debian package.&lt;/li&gt;
&lt;li&gt;provide a Puppet recipe or Ansible playbook that deploys the package and integrates it into the system.&lt;/li&gt;
&lt;li&gt;embed (default) configuration into the application's Python package (via the &lt;code&gt;include_package_data&lt;/code&gt; option of &lt;code&gt;setuptools&lt;/code&gt;).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;All those can be combined, e.g. provide defaults via Python package data, and then add external configuration that only provides values specific to the concrete host installation.&lt;/p&gt;

&lt;p&gt;A real-world example is the &lt;a href="https://github.com/jhermann/devpi-puppet/blob/master/templates/supervisord.conf"&gt;devpi supervisor ERB template&lt;/a&gt; that serves both the purpose of passing configuration to the application process (via command line options), and also starting and controlling that process (i.e. handle demonization and automatic startup on boot).&lt;/p&gt;

&lt;h2&gt;
  
  
  Installing the build tools
&lt;/h2&gt;

&lt;p&gt;Unsurprisingly, you need to &lt;a href="http://dh-virtualenv.readthedocs.org/en/latest/tutorial.html#step-1-install-dh-virtualenv"&gt;install dh-virtualenv&lt;/a&gt; to use it. Since it is architecture independant, you can choose to use a &lt;a href="https://packages.debian.org/sid/dh-virtualenv"&gt;recent release offered by Debian sid&lt;/a&gt; whatever your build platform is.&lt;/p&gt;

&lt;p&gt;If this is your first time to build a Debian package, you also need to add the basic tools for that:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;apt-get &lt;span class="nb"&gt;install &lt;/span&gt;build-essential debhelper devscripts equivs
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Finally, to take advantage of the available template for easily adding an inital &lt;code&gt;debian&lt;/code&gt; directory, &lt;a href="https://github.com/Springerle/springerle.github.io#installing-the-cookiecutter-cli"&gt;install the cookiecutter tool&lt;/a&gt;. Note that you can opt to &lt;a href="https://dockyard.readthedocs.io/en/latest/packaging-howto.html"&gt;build packages in a Docker container&lt;/a&gt; instead, with only Docker as a requirement on your build host.&lt;/p&gt;

&lt;h2&gt;
  
  
  Packaging an example project
&lt;/h2&gt;

&lt;p&gt;To add the necessary &lt;code&gt;debian&lt;/code&gt; directory with minimal effort, you can use the &lt;a href="https://github.com/Springerle/dh-virtualenv-mold#dh-virtualenv-mold"&gt;dh-virtualenv-mold&lt;/a&gt; cookiecutter. The following commands basically repeat what the &lt;a href="https://github.com/Springerle/dh-virtualenv-mold/blob/master/test.sh"&gt;integration test&lt;/a&gt; script of that project does, namely instantiate a Python project and then add debianization on top of it.&lt;/p&gt;

&lt;p&gt;To provide common defaults to &lt;code&gt;cookiecutter&lt;/code&gt;, it makes sense to have a &lt;code&gt;~/.cookiecutterrc&lt;/code&gt; file similar to &lt;a href="https://github.com/jhermann/ruby-slippers/blob/master/home/.cookiecutterrc"&gt;the one I use&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Let's first create a sample project:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;mkdir&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; ~/tmp/dh-venv-blog
&lt;span class="nb"&gt;cd&lt;/span&gt; ~/tmp/dh-venv-blog
cookiecutter &lt;span class="nt"&gt;--no-input&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="s2"&gt;"https://github.com/borntyping/cookiecutter-pypackage-minimal.git"&lt;/span&gt;
&lt;span class="nb"&gt;cd &lt;/span&gt;cookiecutter_pypackage_minimal/
python3 setup.py build
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;You can of course also use one of your own, then just check that out instead. Next, we add the &lt;code&gt;debian&lt;/code&gt; directory:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight shell"&gt;&lt;code&gt;cookiecutter &lt;span class="nt"&gt;--no-input&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="s2"&gt;"https://github.com/Springerle/dh-virtualenv-mold.git"&lt;/span&gt;
dch &lt;span class="nt"&gt;-r&lt;/span&gt; &lt;span class="s2"&gt;""&lt;/span&gt; &lt;span class="c"&gt;# insert proper date &amp;amp; distro&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Using &lt;code&gt;--no-input&lt;/code&gt; causes the template's defaults to be accepted – it avoids answering all the template's prompts. After all, this is just a demo not requiring sensible inputs. Take the time to have a look at what's in the &lt;code&gt;debian&lt;/code&gt; directory.&lt;/p&gt;

&lt;p&gt;You're now able to build the package and if that succeeds, print the contained meta data:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight shell"&gt;&lt;code&gt;dpkg-buildpackage &lt;span class="nt"&gt;-uc&lt;/span&gt; &lt;span class="nt"&gt;-us&lt;/span&gt; &lt;span class="nt"&gt;-b&lt;/span&gt;
dpkg-deb &lt;span class="nt"&gt;-I&lt;/span&gt; ../pyvenv-foobar_&lt;span class="k"&gt;*&lt;/span&gt;.deb
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;The last command should show you something like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight ini"&gt;&lt;code&gt;&lt;span class="err"&gt;Package:&lt;/span&gt; &lt;span class="err"&gt;pyvenv-foobar&lt;/span&gt;
&lt;span class="err"&gt;Version:&lt;/span&gt; &lt;span class="err"&gt;0.1.0&lt;/span&gt;
&lt;span class="err"&gt;Architecture:&lt;/span&gt; &lt;span class="err"&gt;amd64&lt;/span&gt;
&lt;span class="err"&gt;Maintainer:&lt;/span&gt; &lt;span class="err"&gt;Jürgen&lt;/span&gt; &lt;span class="err"&gt;Hermann&lt;/span&gt; &lt;span class="err"&gt;&amp;lt;jh@web.de&amp;gt;&lt;/span&gt;
&lt;span class="err"&gt;Installed-Size:&lt;/span&gt; &lt;span class="err"&gt;12877&lt;/span&gt;
&lt;span class="err"&gt;Pre-Depends:&lt;/span&gt; &lt;span class="err"&gt;dpkg&lt;/span&gt; &lt;span class="err"&gt;(&amp;gt;=&lt;/span&gt; &lt;span class="err"&gt;1.16.1),&lt;/span&gt; &lt;span class="err"&gt;python3,&lt;/span&gt; &lt;span class="err"&gt;python3-venv&lt;/span&gt;
&lt;span class="err"&gt;Section:&lt;/span&gt; &lt;span class="err"&gt;contrib/python&lt;/span&gt;
&lt;span class="err"&gt;Priority:&lt;/span&gt; &lt;span class="err"&gt;extra&lt;/span&gt;
&lt;span class="err"&gt;Homepage:&lt;/span&gt; &lt;span class="err"&gt;https://github.com/jschmoe/foobar&lt;/span&gt;
&lt;span class="err"&gt;Description:&lt;/span&gt; &lt;span class="err"&gt;A&lt;/span&gt; &lt;span class="err"&gt;Python&lt;/span&gt; &lt;span class="err"&gt;package&lt;/span&gt; &lt;span class="err"&gt;and&lt;/span&gt; &lt;span class="err"&gt;its&lt;/span&gt; &lt;span class="err"&gt;dependencies&lt;/span&gt; &lt;span class="err"&gt;packaged&lt;/span&gt; &lt;span class="err"&gt;up&lt;/span&gt; 
    &lt;span class="err"&gt;as&lt;/span&gt; &lt;span class="err"&gt;DEB&lt;/span&gt; &lt;span class="err"&gt;in&lt;/span&gt; &lt;span class="err"&gt;an&lt;/span&gt; &lt;span class="err"&gt;isolated&lt;/span&gt; &lt;span class="err"&gt;virtualenv.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Finally, install the new package via &lt;code&gt;dpkg -i&lt;/code&gt;, or upload it to a repository and use it from there with &lt;code&gt;apt&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Real-world examples
&lt;/h2&gt;

&lt;p&gt;These are examples of &lt;code&gt;dh-virtualenv&lt;/code&gt; packaging for non-trivial applications:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/jhermann/devpi-enterprisey/tree/master/debianized-devpi#readme"&gt;devpi&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/ekalinin/nodeenv/tree/master/debian-upstream"&gt;nodeenv&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/1and1/debianized-sentry#readme"&gt;debianized-sentry&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/1and1/debianized-jupyterhub#readme"&gt;jupyterhub&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The last two show how to integrate Python web applications into &lt;code&gt;systemd&lt;/code&gt;, instead of using &lt;code&gt;supervisor&lt;/code&gt; like the &lt;code&gt;devpi&lt;/code&gt; example. The &lt;code&gt;jupyterhub&lt;/code&gt; one also demonstrates the integration of a Python project with server-side Javascript (in a NodeJS environment).&lt;/p&gt;

</description>
      <category>devops</category>
      <category>python</category>
      <category>deployment</category>
      <category>debian</category>
    </item>
    <item>
      <title>Release of rudiments v0.4.0</title>
      <dc:creator>Jürgen Hermann</dc:creator>
      <pubDate>Fri, 27 Mar 2020 20:59:04 +0000</pubDate>
      <link>https://dev.to/jhermann/release-of-rudiments-v0-4-0-44fm</link>
      <guid>https://dev.to/jhermann/release-of-rudiments-v0-4-0-44fm</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;New release of my Python package with fundamental elements for any Python project, like configuration handling.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;a href="https://github.com/jhermann/rudiments#readme"&gt;rudiments&lt;/a&gt; is a Python package that collects ‘miscellaneous’ functionality that is unspecific in nature and shared among many projects. The package provides runtime support for my &lt;a href="https://github.com/Springerle"&gt;Springerle&lt;/a&gt; cookiecutters, which enable you to quickly create new Python projects that work out of the box.&lt;/p&gt;

&lt;p&gt;The new &lt;a href="https://github.com/jhermann/rudiments/releases/tag/v0.4.0"&gt;release 0.4.0&lt;/a&gt; fixes some minor problems regarding newer &lt;code&gt;Click&lt;/code&gt; versions, and path handling under Windows. It also drops Python 2.7 support, so Python 3.5 or higher is required now.&lt;/p&gt;

&lt;p&gt;See the &lt;a href="https://rudiments.readthedocs.io/en/latest/usage.html"&gt;rudiments documentation&lt;/a&gt; for details on using the contained features.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;Image credits:&lt;/em&gt; &lt;a href="https://www.flickr.com/photos/annola/"&gt;annola on Flickr&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

</description>
      <category>showdev</category>
      <category>python</category>
      <category>pypi</category>
    </item>
    <item>
      <title>Embedding Graphs Into Your Sphinx Documents</title>
      <dc:creator>Jürgen Hermann</dc:creator>
      <pubDate>Wed, 25 Mar 2020 05:00:00 +0000</pubDate>
      <link>https://dev.to/jhermann/embedding-graphs-into-your-sphinx-documents-3o47</link>
      <guid>https://dev.to/jhermann/embedding-graphs-into-your-sphinx-documents-3o47</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;Embed GraphViz ‘dot language’ graphs into your documentation, and hot-link the nodes to any HTTP resource.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The &lt;a href="https://www.sphinx-doc.org/en/master/usage/extensions/graphviz.html"&gt;sphinx.ext.graphviz&lt;/a&gt; extension allows you to directly embed GraphViz ‘dot language’ graphs into your document files. They are then rendered to PNG or SVG images, which get added to your generated HTML documentation. Using SVG allows you to hot-link your nodes to any HTTP resource.&lt;/p&gt;

&lt;p&gt;Before use, you have to activate the extension with just a few changes to your &lt;code&gt;docs/conf.py&lt;/code&gt;&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;extensions&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="c1"&gt;# …
&lt;/span&gt;    &lt;span class="s"&gt;'sphinx.ext.graphviz'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;]&lt;/span&gt;

&lt;span class="c1"&gt;# …
&lt;/span&gt;
&lt;span class="c1"&gt;# -- GraphViz configuration ----------------------------------
&lt;/span&gt;&lt;span class="n"&gt;graphviz_output_format&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;'svg'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;This is an example for what you can then add to your documentation:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--bel-YFIl--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://jhermann.github.io/blog/images/copied_from_nb/img/python/sphinx-ext-graphviz-sample.svg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--bel-YFIl--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://jhermann.github.io/blog/images/copied_from_nb/img/python/sphinx-ext-graphviz-sample.svg" alt="Sphinx and GraphViz Data Flow"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;As long as the nodes have a &lt;code&gt;href&lt;/code&gt; attribute, the SVG rendering contains them and thus node labels become clickable hyperlinks.&lt;/p&gt;

&lt;p&gt;And here's the related markup that needs to be added to one of your &lt;code&gt;.rst&lt;/code&gt; files:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="err"&gt;..&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;graphviz::&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="err"&gt;:name:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;sphinx.ext.graphviz&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="err"&gt;:caption:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;Sphinx&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;and&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;GraphViz&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;Data&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;Flow&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="err"&gt;:alt:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;How&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;Sphinx&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;and&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;GraphViz&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;Render&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;the&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;Final&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;Document&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="err"&gt;:align:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;center&lt;/span&gt;&lt;span class="w"&gt;

     &lt;/span&gt;&lt;span class="err"&gt;digraph&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"sphinx-ext-graphviz"&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;size=&lt;/span&gt;&lt;span class="s2"&gt;"6,4"&lt;/span&gt;&lt;span class="err"&gt;;&lt;/span&gt;&lt;span class="w"&gt;
         &lt;/span&gt;&lt;span class="err"&gt;rankdir=&lt;/span&gt;&lt;span class="s2"&gt;"LR"&lt;/span&gt;&lt;span class="err"&gt;;&lt;/span&gt;&lt;span class="w"&gt;
         &lt;/span&gt;&lt;span class="err"&gt;graph&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="err"&gt;fontname=&lt;/span&gt;&lt;span class="s2"&gt;"Verdana"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;fontsize=&lt;/span&gt;&lt;span class="s2"&gt;"12"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="err"&gt;;&lt;/span&gt;&lt;span class="w"&gt;
         &lt;/span&gt;&lt;span class="err"&gt;node&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="err"&gt;fontname=&lt;/span&gt;&lt;span class="s2"&gt;"Verdana"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;fontsize=&lt;/span&gt;&lt;span class="s2"&gt;"12"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="err"&gt;;&lt;/span&gt;&lt;span class="w"&gt;
         &lt;/span&gt;&lt;span class="err"&gt;edge&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="err"&gt;fontname=&lt;/span&gt;&lt;span class="s2"&gt;"Sans"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;fontsize=&lt;/span&gt;&lt;span class="s2"&gt;"9"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="err"&gt;;&lt;/span&gt;&lt;span class="w"&gt;

         &lt;/span&gt;&lt;span class="err"&gt;sphinx&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="err"&gt;label=&lt;/span&gt;&lt;span class="s2"&gt;"Sphinx"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;shape=&lt;/span&gt;&lt;span class="s2"&gt;"component"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                 &lt;/span&gt;&lt;span class="err"&gt;href=&lt;/span&gt;&lt;span class="s2"&gt;"https://www.sphinx-doc.org/"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                 &lt;/span&gt;&lt;span class="err"&gt;target=&lt;/span&gt;&lt;span class="s2"&gt;"_blank"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="err"&gt;;&lt;/span&gt;&lt;span class="w"&gt;
         &lt;/span&gt;&lt;span class="err"&gt;dot&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="err"&gt;label=&lt;/span&gt;&lt;span class="s2"&gt;"GraphViz"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;shape=&lt;/span&gt;&lt;span class="s2"&gt;"component"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
              &lt;/span&gt;&lt;span class="err"&gt;href=&lt;/span&gt;&lt;span class="s2"&gt;"https://www.graphviz.org/"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
              &lt;/span&gt;&lt;span class="err"&gt;target=&lt;/span&gt;&lt;span class="s2"&gt;"_blank"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="err"&gt;;&lt;/span&gt;&lt;span class="w"&gt;
         &lt;/span&gt;&lt;span class="err"&gt;docs&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="err"&gt;label=&lt;/span&gt;&lt;span class="s2"&gt;"Docs (.rst)"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;shape=&lt;/span&gt;&lt;span class="s2"&gt;"folder"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
               &lt;/span&gt;&lt;span class="err"&gt;fillcolor=green&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;style=filled&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="err"&gt;;&lt;/span&gt;&lt;span class="w"&gt;
         &lt;/span&gt;&lt;span class="err"&gt;svg_file&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="err"&gt;label=&lt;/span&gt;&lt;span class="s2"&gt;"SVG Image"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;shape=&lt;/span&gt;&lt;span class="s2"&gt;"note"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;fontcolor=white&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                   &lt;/span&gt;&lt;span class="err"&gt;fillcolor=&lt;/span&gt;&lt;span class="s2"&gt;"#3333ff"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;style=filled&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="err"&gt;;&lt;/span&gt;&lt;span class="w"&gt;
         &lt;/span&gt;&lt;span class="err"&gt;html_files&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="err"&gt;label=&lt;/span&gt;&lt;span class="s2"&gt;"HTML Files"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;shape=&lt;/span&gt;&lt;span class="s2"&gt;"folder"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                     &lt;/span&gt;&lt;span class="err"&gt;fillcolor=yellow&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;style=filled&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="err"&gt;;&lt;/span&gt;&lt;span class="w"&gt;

         &lt;/span&gt;&lt;span class="err"&gt;docs&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;sphinx&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="err"&gt;label=&lt;/span&gt;&lt;span class="s2"&gt;" parse "&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="err"&gt;;&lt;/span&gt;&lt;span class="w"&gt;
         &lt;/span&gt;&lt;span class="err"&gt;sphinx&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;dot&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="err"&gt;label=&lt;/span&gt;&lt;span class="s2"&gt;" call "&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;style=dashed&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;arrowhead=none&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="err"&gt;;&lt;/span&gt;&lt;span class="w"&gt;
         &lt;/span&gt;&lt;span class="err"&gt;dot&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;svg_file&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="err"&gt;label=&lt;/span&gt;&lt;span class="s2"&gt;" draw "&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="err"&gt;;&lt;/span&gt;&lt;span class="w"&gt;
         &lt;/span&gt;&lt;span class="err"&gt;sphinx&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;html_files&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="err"&gt;label=&lt;/span&gt;&lt;span class="s2"&gt;" render "&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="err"&gt;;&lt;/span&gt;&lt;span class="w"&gt;
         &lt;/span&gt;&lt;span class="err"&gt;svg_file&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;html_files&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="err"&gt;style=dashed&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="err"&gt;;&lt;/span&gt;&lt;span class="w"&gt;
     &lt;/span&gt;&lt;span class="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;For all this to work, you need the &lt;a href="https://www.graphviz.org/"&gt;GraphViz suite of tools&lt;/a&gt; installed on the machine that renders the documentation.&lt;/p&gt;

</description>
      <category>python</category>
      <category>documentation</category>
      <category>sphinx</category>
      <category>graphviz</category>
    </item>
    <item>
      <title>A JupyterHub Showcase: DevOps Intelligence</title>
      <dc:creator>Jürgen Hermann</dc:creator>
      <pubDate>Sun, 22 Mar 2020 06:00:00 +0000</pubDate>
      <link>https://dev.to/jhermann/a-jupyterhub-showcase-devops-intelligence-5ea</link>
      <guid>https://dev.to/jhermann/a-jupyterhub-showcase-devops-intelligence-5ea</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;Originally published in 2019 as &lt;a href="https://blog.jupyter.org/devops-intelligence-3ff48a76b525"&gt;DevOps Intelligence&lt;/a&gt; in the &lt;a href="https://blog.jupyter.org/"&gt;Jupyter Blog&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;em&gt;DevOps Intelligence&lt;/em&gt; turns data from software development and delivery processes into actionable insight, just like BI does for the business side. Jupyter is the ideal instrument for that, with its combination of powerful coding environments and a user interface facilitating experimentation with ultra-short feedback cycles.&lt;/p&gt;

&lt;p&gt;A Jupyter-based setup supports risk analysis and decision making within development and operations processes – typical business intelligence / data science procedures can be applied to the ‘business of making and running software’. The idea is to create feedback loops, and facilitate human decision making by automatically providing reliable input in form of up-to-date facts. After all development is our business — so let's have KPIs for developing, releasing, and operating software.&lt;/p&gt;

&lt;h2&gt;
  
  
  Typical Use-Cases
&lt;/h2&gt;

&lt;p&gt;Here are some obvious application areas where data analysis can be helpful on the technical side.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Migration processes of all kinds (current state, progress tracking, achievement of objectives)&lt;/li&gt;
&lt;li&gt;Inventory reporting for increased transparency and support of operational decisions&lt;/li&gt;
&lt;li&gt;Automate internal reporting processes to free up scarce assets and human expertise&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Platform Architecture
&lt;/h2&gt;

&lt;p&gt;A simple &lt;a href="https://jupyter.org/hub"&gt;JupyterHub&lt;/a&gt; setup can enable you to do analysis on your already available but under-used and hardly understood data, without any great investment of effort or capital. By adding a single JupyterHub host, you can use the built-in Python3 kernel to access existing internal data sources.&lt;/p&gt;

&lt;p&gt;The following diagram shows what role JupyterHub can play in an existing environment.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--397ZHBeS--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://github.com/1and1/debianized-jupyterhub/raw/master/docs/_static/img/devops-intelligence.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--397ZHBeS--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://github.com/1and1/debianized-jupyterhub/raw/master/docs/_static/img/devops-intelligence.png" alt="DevOps Intelligence Architecture"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;To make such a deployment easy, the &lt;a href="https://github.com/1and1/debianized-jupyterhub#jupyterhub-debian-packaging"&gt;1and1/debianized-jupyterhub&lt;/a&gt; project provides a JupyterHub service including a fully equipped Python3 kernel as a single Debian package – only Python3, NodeJS, and Chromium packages must be installed in addition to the &lt;code&gt;jupyterhub&lt;/code&gt; one. If you raised an eyebrow on Chromium being in that list, it's used by JavaScript-based visualization frameworks to render PNG images.&lt;/p&gt;

&lt;p&gt;Including a &lt;a href="https://github.com/1and1/debianized-jupyterhub#securing-your-jupyterhub-web-service-with-an-ssl-off-loader"&gt;NginX-powered SSL off-loader&lt;/a&gt;, the &lt;a href="https://github.com/1and1/debianized-jupyterhub#how-to-set-up-a-simple-service-instance"&gt;complete setup&lt;/a&gt; can be done in under an hour.&lt;/p&gt;

&lt;h2&gt;
  
  
  Use-Case: Migration Reporting
&lt;/h2&gt;

&lt;p&gt;At the time of this writing (early 2019), a widespread challenge is migration from Oracle Java to other vendors, and also to start migration from Java 8 to newer versions (Java 11). If you do that at scale across many machines and teams, you definitely need some kind of governance, and constant feedback on the current status and the rate of progress.&lt;/p&gt;

&lt;p&gt;What follows is an excerpt from a productive notebook, with anonymized data about &lt;a href="https://adoptopenjdk.net/"&gt;AdoptOpenJDK&lt;/a&gt; deployments. That data was originally retrieved from a system called &lt;em&gt;“Patch Management Reporting”&lt;/em&gt;, which collects information about installed packages for all hosts in the data center. We're in the yellow &lt;em&gt;“Data Sources”&lt;/em&gt; box of the above figure here.&lt;/p&gt;

&lt;p&gt;First off, we read the data and show the value sets of categorical columns, plus a sample.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;numpy&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;pandas&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;pd&lt;/span&gt;

&lt;span class="n"&gt;raw_data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pd&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;read_csv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"../_data/cmdb-aoj.csv"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;sep&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;','&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'♯ of Records: {}&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;'&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;format&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;raw_data&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt;

&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;raw_data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;columns&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;:]:&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;startswith&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'Last '&lt;/span&gt;&lt;span class="p"&gt;):&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;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;'='&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;list&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;sorted&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;raw_data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="n"&gt;fillna&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;''&lt;/span&gt;&lt;span class="p"&gt;)))))&lt;/span&gt;

&lt;span class="k"&gt;print&lt;/span&gt;&lt;span class="p"&gt;();&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;raw_data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;head&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="n"&gt;transpose&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;♯ of Records: 104

Distribution = ['Debian 8.10', 'Debian 8.11', 'Debian 8.6', 'Debian 8.9',
                'Debian 9.6', 'Debian 9.7', 'Debian 9.8']
Architecture = ['amd64']
Environment = ['--', 'DEV', 'LIVE', 'QA']
Team = ['Team Blue', 'Team Green', 'Team Red', 'Team Yellow']
Installed version = ['11.0.2.9-83(amd64)', '11.0.2.9-85(amd64)', '8.202.b08-66(amd64)',
                     '8.202.b08-83(amd64)', '8.202.b08-85(amd64)']

                                    0                   1                   2
CMDB_Id                     108380195           298205230           220678839
Distribution              Debian 8.11          Debian 9.6         Debian 8.11
Architecture                    amd64               amd64               amd64
Environment                       DEV                  --                 DEV
Team                         Team Red            Team Red            Team Red
Last seen            2019-03-18 06:42    2019-03-18 06:42    2019-03-18 06:42
Last modified        2019-03-18 06:42    2019-03-18 06:42    2019-03-18 06:42
Installed version  11.0.2.9-83(amd64)  11.0.2.9-83(amd64)  11.0.2.9-83(amd64)
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Next comes the usual data cleanup. The &lt;code&gt;Distribution&lt;/code&gt; column is a bit diverse, and not everyone has Debian codenames and associated major versions memorized. The &lt;code&gt;map_distro&lt;/code&gt; function fixes that.&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;def&lt;/span&gt; &lt;span class="nf"&gt;map_distro&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="s"&gt;"""Helper to create canonical OS names."""&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'.'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'Debian 7'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;'wheezy'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'Debian 8'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;'jessie'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'Debian 9'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;'stretch'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'Debian 10'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;'buster'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'squeeze'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;'Squeeze [6]'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'wheezy'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;'Wheezy [7]'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'jessie'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;'Jessie [8]'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'stretch'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;'Stretch [9]'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'buster'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;'Buster [10]'&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;Together with other cleanup steps, the mapper function is applied in a &lt;a href="https://towardsdatascience.com/dplyr-style-data-manipulation-with-pipes-in-python-380dcb137000"&gt;dfply&lt;/a&gt; pipeline. The result can be controlled by showing a sample of data points with unique version numbers.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;dfply&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;

&lt;span class="n"&gt;cleaned&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;raw_data&lt;/span&gt;
    &lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;mutate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Version&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;X&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;'Installed version'&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'[()]'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;expand&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
    &lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;mutate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Environment&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;X&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Environment&lt;/span&gt;
              &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;fillna&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'--'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'--'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;'N/A'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;upper&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
    &lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;mutate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Distribution&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;X&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Distribution&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;apply&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;map_distro&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;drop&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;X&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CMDB_Id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;X&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;'Last seen'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;X&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;'Last modified'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;X&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;'Installed version'&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;span class="p"&gt;)&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;cleaned&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;distinct&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;X&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Version&lt;/span&gt;&lt;span class="p"&gt;)).&lt;/span&gt;&lt;span class="n"&gt;transpose&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;                       0            13            14            62  \
Distribution   Jessie [8]  Stretch [9]    Jessie [8]   Stretch [9]
Architecture        amd64        amd64         amd64         amd64
Environment           DEV          N/A           DEV           DEV
Team             Team Red    Team Blue      Team Red     Team Blue
Version       11.0.2.9-83  11.0.2.9-85  8.202.b08-83  8.202.b08-85

                        68
Distribution    Jessie [8]
Architecture         amd64
Environment            DEV
Team             Team Blue
Version       8.202.b08-66
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Time to present the cleaned up data, starting with a table of teams and their number of installed packages. In the production notebook, an API of the corporate identity management is used to enrich the table with contact data of the team leads. Having the organizational data available also makes it possible to filter or aggregate the data by business units.&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;counts&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;cleaned&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;groupby&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="s"&gt;'Team'&lt;/span&gt;&lt;span class="p"&gt;]).&lt;/span&gt;&lt;span class="n"&gt;size&lt;/span&gt;&lt;span class="p"&gt;()&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;counts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;reset_index&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="s"&gt;'Count'&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;          Team  Count
0    Team Blue     42
1   Team Green     16
2     Team Red     45
3  Team Yellow      1
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;To create a heatmap of how diverse a team's version spectrum is, we calculate percentages of versions per team.&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;percentage&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;cleaned&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;groupby&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="s"&gt;'Team'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;'Version'&lt;/span&gt;&lt;span class="p"&gt;]).&lt;/span&gt;&lt;span class="n"&gt;size&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="n"&gt;reset_index&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="s"&gt;'Count'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;percentage&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;percentage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;assign&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Percent&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;
    &lt;span class="n"&gt;percentage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;apply&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;lambda&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;100.0&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="n"&gt;counts&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]],&lt;/span&gt; &lt;span class="n"&gt;axis&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;))&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;percentage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;head&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;        Team       Version  Count    Percent
0  Team Blue   11.0.2.9-85      1   2.380952
1  Team Blue  8.202.b08-66      1   2.380952
2  Team Blue  8.202.b08-85     40  95.238095
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;&lt;a href="http://holoviews.org/"&gt;HoloViews&lt;/a&gt; makes creating the heatmap including a label overlay a breeze.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;holoviews&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;hv&lt;/span&gt;
&lt;span class="n"&gt;hv&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;extension&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'bokeh'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;publish&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;  &lt;span class="c1"&gt;# publishing or interactive mode?
&lt;/span&gt;
&lt;span class="n"&gt;heatmap&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;hv&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;HeatMap&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;percentage&lt;/span&gt;&lt;span class="p"&gt;[[&lt;/span&gt;&lt;span class="s"&gt;'Version'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;'Team'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;'Percent'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;'Count'&lt;/span&gt;&lt;span class="p"&gt;]]).&lt;/span&gt;&lt;span class="n"&gt;opts&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;hv&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;opts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;HeatMap&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;title&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;'Version Distribution by Team'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;width&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;480&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;xrotation&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;25&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;zlim&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;cmap&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;'kbc_r'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;clipping_colors&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;NaN&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;'#ffffe0'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="n"&gt;colorbar&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;tools&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;'hover'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;toolbar&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;None&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;publish&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="s"&gt;'right'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="n"&gt;sort&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="n"&gt;label_dimension&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;hv&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Dimension&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'Percent'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;value_format&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="k"&gt;lambda&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;'%.1f'&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;labels&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;hv&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Labels&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;heatmap&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;vdims&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;label_dimension&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="n"&gt;opts&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;hv&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;opts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Labels&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;text_color&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;'Percent'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;text_font_size&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;'10pt'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;text_font_style&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;'bold'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;chart&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;heatmap&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;labels&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;publish&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;time&lt;/span&gt;
    &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;phantomjs_bin&lt;/span&gt;
    &lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;IPython.display&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;HTML&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;clear_output&lt;/span&gt;

    &lt;span class="o"&gt;%&lt;/span&gt;&lt;span class="n"&gt;env&lt;/span&gt; &lt;span class="n"&gt;BOKEH_PHANTOMJS_PATH&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;phantomjs_bin&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;executable_path&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="n"&gt;chart_img&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;'img/devops/aoj-heatmap.png'&lt;/span&gt;
    &lt;span class="n"&gt;hv&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;save&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;chart&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;chart_img&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;chart&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;HTML&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'&amp;lt;img src="{}?{}"&amp;gt;&amp;lt;/img&amp;gt;'&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;format&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;chart_img&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="p"&gt;()))&lt;/span&gt;
    &lt;span class="n"&gt;clear_output&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="n"&gt;chart&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--h6T3hQzo--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/pinfuk88ei6eezrazjpf.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--h6T3hQzo--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/pinfuk88ei6eezrazjpf.png" alt="Versions Heatmap"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;From the heatmap, you can easily glance whether a team uses predominantly one version, and how recent the used versions are.&lt;/p&gt;

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

&lt;p&gt;Using a platform powered by &lt;a href="https://twitter.com/ProjectJupyter"&gt;Project Jupyter&lt;/a&gt; and a big chunk of the scientific Python stack lets you easily mold your data into the shape you need, and then choose from a wide range of visualization options to bring your message across.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;👍 &lt;em&gt;Image credits:&lt;/em&gt; &lt;a href="https://commons.wikimedia.org/wiki/File:Devops-toolchain.svg"&gt;Devops-toolchain&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

</description>
      <category>showdev</category>
      <category>devops</category>
      <category>datascience</category>
      <category>jupyter</category>
    </item>
    <item>
      <title>Using Python3's ‘venv’ with tox</title>
      <dc:creator>Jürgen Hermann</dc:creator>
      <pubDate>Sat, 21 Mar 2020 22:27:28 +0000</pubDate>
      <link>https://dev.to/jhermann/using-python3-s-venv-with-tox-2d9l</link>
      <guid>https://dev.to/jhermann/using-python3-s-venv-with-tox-2d9l</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;Using the built-in Python module in favor of &lt;code&gt;virtualenv&lt;/code&gt; to create your testing or project automation environments.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;code&gt;tox&lt;/code&gt; is a generic virtualenv management and test command line tool, especially useful for multi-environment testing. It has a plugin architecture, with plenty of both built-in and 3rd party extensions. &lt;/p&gt;

&lt;p&gt;This post assumes you are already familiar with &lt;code&gt;tox&lt;/code&gt; and have a working configuration for it. If not, check out &lt;a href="https://tox.readthedocs.io/"&gt;its documentation&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;In order to make &lt;code&gt;tox&lt;/code&gt; use the built-in virtual environment &lt;code&gt;venv&lt;/code&gt; of Python 3.3+, there is a plugin named &lt;a href="https://pypi.org/project/tox-venv/"&gt;tox-venv&lt;/a&gt; that switches from using &lt;code&gt;virtualenv&lt;/code&gt; to &lt;code&gt;venv&lt;/code&gt; whenever it is available.&lt;/p&gt;


&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--vJ70wriM--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://practicaldev-herokuapp-com.freetls.fastly.net/assets/github-logo-ba8488d21cd8ee1fee097b8410db9deaa41d0ca30b004c0c63de0a479114156f.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/tox-dev"&gt;
        tox-dev
      &lt;/a&gt; / &lt;a href="https://github.com/tox-dev/tox-venv"&gt;
        tox-venv
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      Use Python 3 venvs for Python 3 test environments
    &lt;/h3&gt;
  &lt;/div&gt;
&lt;/div&gt;


&lt;p&gt;Typically, &lt;code&gt;venv&lt;/code&gt; is more robust when faced with ever-changing runtime environments and versions of related tooling (&lt;code&gt;pip&lt;/code&gt;, &lt;code&gt;setuptools&lt;/code&gt;, …).&lt;/p&gt;

&lt;p&gt;To enable that plugin, add this to your &lt;code&gt;tox.ini&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="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;requires&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;tox-venv&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;That merely triggers &lt;code&gt;tox&lt;/code&gt; to check (on startup) that the plugin is installed. You still have to add it to your &lt;code&gt;dev-requirements.txt&lt;/code&gt; or a similar file, so it gets installed together with &lt;code&gt;tox&lt;/code&gt;. You can also install &lt;code&gt;tox&lt;/code&gt; globally using &lt;code&gt;dephell jail install tox tox-venv&lt;/code&gt; – see the first post in this series for details.&lt;/p&gt;

&lt;p&gt;The end result is this (call &lt;code&gt;tox -v&lt;/code&gt; to see those messages):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight ini"&gt;&lt;code&gt;&lt;span class="err"&gt;py38&lt;/span&gt; &lt;span class="err"&gt;create:&lt;/span&gt; &lt;span class="err"&gt;…/.tox/py38&lt;/span&gt;
  &lt;span class="err"&gt;…/.tox$&lt;/span&gt; &lt;span class="err"&gt;/usr/bin/python3.8&lt;/span&gt; &lt;span class="err"&gt;-m&lt;/span&gt; &lt;span class="err"&gt;venv&lt;/span&gt; &lt;span class="err"&gt;py38&lt;/span&gt; &lt;span class="err"&gt;&amp;gt;…/log/py38-0.log&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;And there you have it, no more virtualenv package needed. 🎉 🎊&lt;/p&gt;

</description>
      <category>python</category>
      <category>virtualenv</category>
      <category>testing</category>
    </item>
    <item>
      <title>Add level setting to dashboard</title>
      <dc:creator>Jürgen Hermann</dc:creator>
      <pubDate>Sat, 21 Mar 2020 11:42:43 +0000</pubDate>
      <link>https://dev.to/jhermann/add-level-setting-to-dashboard-23p3</link>
      <guid>https://dev.to/jhermann/add-level-setting-to-dashboard-23p3</guid>
      <description>&lt;p&gt;On the dashboard, in the meta data summary box after the "Manage" button, please include the level setting. Otherwise it is tedious to impossible to maintain them after the fact, or get an idea whether your level settings are consistent across posts.&lt;/p&gt;

</description>
      <category>meta</category>
      <category>dashboard</category>
      <category>feature</category>
    </item>
    <item>
      <title>Sort Order of Posts in a Series?</title>
      <dc:creator>Jürgen Hermann</dc:creator>
      <pubDate>Sat, 21 Mar 2020 10:37:28 +0000</pubDate>
      <link>https://dev.to/jhermann/sort-order-of-posts-in-a-series-d8</link>
      <guid>https://dev.to/jhermann/sort-order-of-posts-in-a-series-d8</guid>
      <description>&lt;p&gt;I'm a bit surprised about the (in a timeline) 2nd post &lt;a href="https://dev.to/jhermann/blogging-about-my-repositories-to-guide-interest-gm7"&gt;Blogging About My Repositories to Guide Interest&lt;/a&gt; being the first in the generated list of posts. Any hints to fix this?&lt;/p&gt;

&lt;p&gt;Maybe related to the &lt;em&gt;original&lt;/em&gt; date of posts at the source? If that, it might be a worthwhile feature to allow explicit ordering (&lt;code&gt;series: #«PRIO» «TITLE»&lt;/code&gt;, with prio defaulting to the UNIX timestamp used currently), or else sort by order of publishing on &lt;code&gt;dev.to&lt;/code&gt; instead of any sources.&lt;/p&gt;

</description>
      <category>meta</category>
      <category>series</category>
    </item>
    <item>
      <title>Using R-style Data Pipelines in Notebooks</title>
      <dc:creator>Jürgen Hermann</dc:creator>
      <pubDate>Tue, 17 Mar 2020 05:00:00 +0000</pubDate>
      <link>https://dev.to/jhermann/using-r-style-data-pipelines-in-notebooks-gam</link>
      <guid>https://dev.to/jhermann/using-r-style-data-pipelines-in-notebooks-gam</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;Mutate Pandas data frames simply and elegantly with data pipelines.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

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

&lt;p&gt;This post shows how mutating data frames can be written more elegantly (and thus understood more easily) by using &lt;em&gt;data pipelines&lt;/em&gt;. R users know this concept from the &lt;code&gt;dplyr&lt;/code&gt; package, and Python offers a similar one named &lt;code&gt;dfply&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Setting the Stage
&lt;/h2&gt;

&lt;p&gt;We start off with some global definitions…&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;numpy&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;pandas&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;pd&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;The sample data (about OS package deployments) is read into the &lt;code&gt;raw_data&lt;/code&gt; dataframe from a CSV file.&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;raw_data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pd&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;read_csv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"../assets/data/cmdb-packages.csv"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;sep&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;','&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'♯ of Records: {}&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;'&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;format&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;raw_data&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt;

&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;raw_data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;columns&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;:]:&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;startswith&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'Last '&lt;/span&gt;&lt;span class="p"&gt;):&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;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;'='&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;list&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;sorted&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;raw_data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="n"&gt;fillna&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;''&lt;/span&gt;&lt;span class="p"&gt;)))))&lt;/span&gt;

&lt;span class="n"&gt;raw_data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;head&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="n"&gt;transpose&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;





&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;♯ of Records: 146

Distribution = ['Debian 8.11', 'Debian 8.6', 'Debian 8.9', 'jessie']
Architecture = ['amd64']
Environment = ['', 'Canary', 'DEV', 'LIVE', 'QA']
Team = ['Automation', 'Big Data', 'Email', 'Ops App1', 'Ops Linux', 'Persistence', 'Platform']
Installed version = ['41.15-2(amd64)', '42.28-2(amd64)', '42.44-1(amd64)', '45.11-1(amd64)', '48.33-1(amd64)']
&lt;/code&gt;&lt;/pre&gt;&lt;/div&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;0&lt;/th&gt;
&lt;th&gt;1&lt;/th&gt;
&lt;th&gt;2&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;CMDB_Id&lt;/td&gt;
&lt;td&gt;274656589&lt;/td&gt;
&lt;td&gt;153062618&lt;/td&gt;
&lt;td&gt;282201163&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Distribution&lt;/td&gt;
&lt;td&gt;jessie&lt;/td&gt;
&lt;td&gt;jessie&lt;/td&gt;
&lt;td&gt;jessie&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Architecture&lt;/td&gt;
&lt;td&gt;amd64&lt;/td&gt;
&lt;td&gt;amd64&lt;/td&gt;
&lt;td&gt;amd64&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Environment&lt;/td&gt;
&lt;td&gt;DEV&lt;/td&gt;
&lt;td&gt;DEV&lt;/td&gt;
&lt;td&gt;LIVE&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Team&lt;/td&gt;
&lt;td&gt;Ops App1&lt;/td&gt;
&lt;td&gt;Ops App1&lt;/td&gt;
&lt;td&gt;Ops App1&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Last seen&lt;/td&gt;
&lt;td&gt;2019-02-18 11:43&lt;/td&gt;
&lt;td&gt;2019-02-18 11:56&lt;/td&gt;
&lt;td&gt;2019-02-18 12:04&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Last modified&lt;/td&gt;
&lt;td&gt;2019-02-18 11:43&lt;/td&gt;
&lt;td&gt;2019-02-18 11:56&lt;/td&gt;
&lt;td&gt;2019-02-18 12:04&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Installed version&lt;/td&gt;
&lt;td&gt;42.28-2(amd64)&lt;/td&gt;
&lt;td&gt;42.28-2(amd64)&lt;/td&gt;
&lt;td&gt;48.33-1(amd64)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The &lt;code&gt;map_distro()&lt;/code&gt; helper function is used in the following sections to clean up the &lt;code&gt;Distribution&lt;/code&gt; column.&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;def&lt;/span&gt; &lt;span class="nf"&gt;map_distro&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="s"&gt;"""Helper to create canonical OS names."""&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'.'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'Debian 7'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;'wheezy'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'Debian 8'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;'jessie'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'Debian 9'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;'stretch'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'Debian 10'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;'buster'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'squeeze'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;'Squeeze [6]'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'wheezy'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;'Wheezy [7]'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'jessie'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;'Jessie [8]'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'stretch'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;'Stretch [9]'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'buster'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;'Buster [10]'&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;h2&gt;
  
  
  Data Cleaning With Pandas
&lt;/h2&gt;

&lt;p&gt;This code cleans up the imported data using the &lt;em&gt;Pandas&lt;/em&gt; API.&lt;/p&gt;

&lt;p&gt;To get sensible version statistics, we split off the auxiliary information in the version column (anything after &lt;code&gt;-&lt;/code&gt;), leaving just the &lt;em&gt;upstream&lt;/em&gt; part of the version string. The environment classifier is also cleaned up a little, and distributions are mapped to a canonical set of names. Some unused columns are dropped.&lt;/p&gt;

&lt;p&gt;Finally, a subset of unique version samples is selected.&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;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;raw_data&lt;/span&gt;
&lt;span class="n"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;assign&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Version&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;'Installed version'&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'-'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;expand&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;span class="n"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;assign&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Environment&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Environment&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;fillna&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'UNDEFINED'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;upper&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
&lt;span class="n"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;assign&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Distribution&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Distribution&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;apply&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;map_distro&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="n"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;drop&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;columns&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;'CMDB_Id'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;'Last seen'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;'Last modified'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;'Installed version'&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;span class="n"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;drop_duplicates&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;subset&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;'Version'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;keep&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;'first'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;transpose&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&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;0&lt;/th&gt;
&lt;th&gt;2&lt;/th&gt;
&lt;th&gt;26&lt;/th&gt;
&lt;th&gt;45&lt;/th&gt;
&lt;th&gt;62&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Distribution&lt;/td&gt;
&lt;td&gt;Jessie [8]&lt;/td&gt;
&lt;td&gt;Jessie [8]&lt;/td&gt;
&lt;td&gt;Jessie [8]&lt;/td&gt;
&lt;td&gt;Jessie [8]&lt;/td&gt;
&lt;td&gt;Jessie [8]&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Architecture&lt;/td&gt;
&lt;td&gt;amd64&lt;/td&gt;
&lt;td&gt;amd64&lt;/td&gt;
&lt;td&gt;amd64&lt;/td&gt;
&lt;td&gt;amd64&lt;/td&gt;
&lt;td&gt;amd64&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Environment&lt;/td&gt;
&lt;td&gt;DEV&lt;/td&gt;
&lt;td&gt;LIVE&lt;/td&gt;
&lt;td&gt;LIVE&lt;/td&gt;
&lt;td&gt;UNDEFINED&lt;/td&gt;
&lt;td&gt;DEV&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Team&lt;/td&gt;
&lt;td&gt;Ops App1&lt;/td&gt;
&lt;td&gt;Ops App1&lt;/td&gt;
&lt;td&gt;Platform&lt;/td&gt;
&lt;td&gt;Email&lt;/td&gt;
&lt;td&gt;Platform&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Version&lt;/td&gt;
&lt;td&gt;42.28&lt;/td&gt;
&lt;td&gt;48.33&lt;/td&gt;
&lt;td&gt;41.15&lt;/td&gt;
&lt;td&gt;45.11&lt;/td&gt;
&lt;td&gt;42.44&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Data Cleaning With Pipelines
&lt;/h2&gt;

&lt;p&gt;This does the exact same processing as the code above, but is arguably more readable and maintained more easily:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;It has less boilerplate, and makes the use of pipelined processing transparent.&lt;/li&gt;
&lt;li&gt;Each step clearly states what it does to the data.&lt;/li&gt;
&lt;li&gt;When steps are copied into other pipelines, the &lt;code&gt;X&lt;/code&gt; placeholder ensures you use the data of &lt;em&gt;this&lt;/em&gt; pipeline (the code is more DRY).
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;dfply&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;

&lt;span class="n"&gt;piped&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;raw_data&lt;/span&gt;
    &lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;mutate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Version&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;X&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;'Installed version'&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'-'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;expand&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
    &lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;mutate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Environment&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;X&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Environment&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;fillna&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'UNDEFINED'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;upper&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
    &lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;mutate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Distribution&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;X&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Distribution&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;apply&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;map_distro&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;drop&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;X&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CMDB_Id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;X&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;'Last seen'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;X&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;'Last modified'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;X&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;'Installed version'&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
    &lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;distinct&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;X&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Version&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;piped&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;transpose&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;The result is identical to the pure Pandas code, as expected.&lt;/p&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;0&lt;/th&gt;
&lt;th&gt;2&lt;/th&gt;
&lt;th&gt;26&lt;/th&gt;
&lt;th&gt;45&lt;/th&gt;
&lt;th&gt;62&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Distribution&lt;/td&gt;
&lt;td&gt;Jessie [8]&lt;/td&gt;
&lt;td&gt;Jessie [8]&lt;/td&gt;
&lt;td&gt;Jessie [8]&lt;/td&gt;
&lt;td&gt;Jessie [8]&lt;/td&gt;
&lt;td&gt;Jessie [8]&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Architecture&lt;/td&gt;
&lt;td&gt;amd64&lt;/td&gt;
&lt;td&gt;amd64&lt;/td&gt;
&lt;td&gt;amd64&lt;/td&gt;
&lt;td&gt;amd64&lt;/td&gt;
&lt;td&gt;amd64&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Environment&lt;/td&gt;
&lt;td&gt;DEV&lt;/td&gt;
&lt;td&gt;LIVE&lt;/td&gt;
&lt;td&gt;LIVE&lt;/td&gt;
&lt;td&gt;UNDEFINED&lt;/td&gt;
&lt;td&gt;DEV&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Team&lt;/td&gt;
&lt;td&gt;Ops App1&lt;/td&gt;
&lt;td&gt;Ops App1&lt;/td&gt;
&lt;td&gt;Platform&lt;/td&gt;
&lt;td&gt;Email&lt;/td&gt;
&lt;td&gt;Platform&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Version&lt;/td&gt;
&lt;td&gt;42.28&lt;/td&gt;
&lt;td&gt;48.33&lt;/td&gt;
&lt;td&gt;41.15&lt;/td&gt;
&lt;td&gt;45.11&lt;/td&gt;
&lt;td&gt;42.44&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;To learn more about &lt;code&gt;dfply&lt;/code&gt;, read the &lt;a href="https://towardsdatascience.com/dplyr-style-data-manipulation-with-pipes-in-python-380dcb137000"&gt;dplyr-style Data Manipulation with Pipes in Python&lt;/a&gt; blog post, which has more examples.&lt;/p&gt;

&lt;h2&gt;
  
  
  Reference Links
&lt;/h2&gt;

&lt;h3&gt;
  
  
  dfply
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://towardsdatascience.com/dplyr-style-data-manipulation-with-pipes-in-python-380dcb137000"&gt;dplyr-style Data Manipulation with Pipes in Python – Towards Data Science&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/kieferk/dfply"&gt;kieferk/dfply: dplyr-style piping operations for Pandas dataframes&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Alternatives
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/has2k1/plydata"&gt;has2k1/plydata: A grammar for data manipulation in Python&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/shaypal5/pdpipe"&gt;shaypal5/pdpipe: Easy pipelines for Pandas dataframes&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>python</category>
      <category>datascience</category>
      <category>jupyter</category>
      <category>pandas</category>
    </item>
  </channel>
</rss>
