<?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.us-east-2.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>OpenSCAD Model With Animation Video and MakerWorld Multi-Plate Support</title>
      <dc:creator>Jürgen Hermann</dc:creator>
      <pubDate>Sat, 29 Aug 2026 09:41:58 +0000</pubDate>
      <link>https://dev.to/jhermann/openscad-model-with-animation-video-and-makerworld-multi-plate-support-4834</link>
      <guid>https://dev.to/jhermann/openscad-model-with-animation-video-and-makerworld-multi-plate-support-4834</guid>
      <description>&lt;p&gt;&lt;em&gt;Contents&lt;/em&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Motivation and Purpose&lt;/li&gt;
&lt;li&gt;Using an Animation to Visualize Key Concepts&lt;/li&gt;
&lt;li&gt;
Working With the Model

&lt;ul&gt;
&lt;li&gt;1. Get the Files&lt;/li&gt;
&lt;li&gt;2. Choose the Dimensions&lt;/li&gt;
&lt;li&gt;3. Inspect the Assembly&lt;/li&gt;
&lt;li&gt;4. Export the &lt;em&gt;MakerWorld&lt;/em&gt; Plates&lt;/li&gt;
&lt;li&gt;5. Attach the Hoses&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Create the Model Animation&lt;/li&gt;
&lt;li&gt;Reference Links&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Motivation and Purpose
&lt;/h2&gt;

&lt;p&gt;I made a &lt;a href="https://makerworld.com/en/models/3220030-ac-hose-bayonet-quick-connectors" rel="noopener noreferrer"&gt;parametric bayonet connector for AC hoses&lt;/a&gt;, to attach such hoses to a mobile AC unit and a typical window kit taking the hot air outside. It is part of my &lt;a href="https://makerworld.com/en/collections/33754690-air-conditioning" rel="noopener noreferrer"&gt;Air Conditioning Collection&lt;/a&gt; on &lt;em&gt;MakerWorld&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;And of course, I prefer code over using some GUI CAD application, leading straight to &lt;em&gt;OpenSCAD&lt;/em&gt; as the established standard for 3D-models-as-code. It is also one of the few ways &lt;em&gt;MakerWorld&lt;/em&gt; models can be made customizable by the end user.&lt;/p&gt;

&lt;p&gt;The connector allows attaching a hose with a simple push and a short twist, instead of needing a threaded joint or tools every time the hose is removed.&lt;/p&gt;

&lt;p&gt;The design has three printable parts:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A female connector with bayonet slots on the inside.&lt;/li&gt;
&lt;li&gt;A male connector with matching lugs.&lt;/li&gt;
&lt;li&gt;A female adapter with a wider fitting section for joining to an existing tube, like the one on an air intake cover.&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fqm3ilc3y5uryqu26i0se.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fqm3ilc3y5uryqu26i0se.jpg" alt="Model Parts" width="687" height="184"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The male lugs fit into the slots in the female connector. Push the parts together, twist them, and the lugs travel along the horizontal parts of the L-shaped slots. This is the same basic idea used by bayonet light fittings, camera mounts, and other quick-release connectors. See the &lt;a href="https://en.wikipedia.org/wiki/Bayonet_mount" rel="noopener noreferrer"&gt;Bayonet mount overview&lt;/a&gt; for useful background.&lt;/p&gt;

&lt;p&gt;The hose itself is held on the printed connector with a worm-drive hose clamp. A screw on the clamp pulls the perforated band tight around the hose. See the &lt;a href="https://en.wikipedia.org/wiki/Hose_clamp" rel="noopener noreferrer"&gt;Hose clamp reference&lt;/a&gt; for an explanation of that mechanism.&lt;/p&gt;

&lt;h2&gt;
  
  
  Using an Animation to Visualize Key Concepts
&lt;/h2&gt;

&lt;p&gt;The OpenSCAD file contains code that creates a 10-scene animation. It shows the female connector turning to expose the slots, the hose and male connector moving into position, the male part twisting to lock, and the parts separating again. It also emphasises the parametric nature of the model by showing a change of the two main parameters, hose diameter and fitting height.&lt;/p&gt;

&lt;p&gt;The animation is not just decoration — it makes the hidden bayonet slots easier to understand before printing the parts.&lt;/p&gt;

&lt;h2&gt;
  
  
  Working With the Model
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Get the Files
&lt;/h3&gt;

&lt;p&gt;Clone the &lt;a href="https://github.com/jhermann/things" rel="noopener noreferrer"&gt;things&lt;/a&gt; repo to get all required model files.&lt;/p&gt;

&lt;p&gt;Install the &lt;a href="https://openscad.org/downloads.html" rel="noopener noreferrer"&gt;OpenSCAD Nightly Build&lt;/a&gt; to view SCAD files, or alternatively add a SCAD live preview extension like &lt;a href="https://marketplace.visualstudio.com/items?itemName=Carve3D.carve" rel="noopener noreferrer"&gt;Carve&lt;/a&gt; to &lt;em&gt;VS Code&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;The &lt;a href="https://github.com/jhermann/things/blob/main/models/ac-bayonet-connectors/hose-connector-multi-plate.scad" rel="noopener noreferrer"&gt;hose-connector-multi-plate.scad&lt;/a&gt; script targets some special features offered by &lt;em&gt;MakerWorld&lt;/em&gt;, using the bespoke &lt;code&gt;mw_plate_N()&lt;/code&gt; module names. To see this, open the &lt;a href="https://makerworld.com/en/makerlab/parametricModelMaker?designId=3220030&amp;amp;from=model_page&amp;amp;modelName=hose-connector-female.scad&amp;amp;scadUrl=https%3A%2F%2Fraw.githubusercontent.com%2Fjhermann%2Fthings%2Frefs%2Fheads%2Fmain%2Fmodels%2Fac-bayonet-connectors%2Fhose-connector-multi-plate.scad&amp;amp;unikey=f5a0b360-ef3e-4e34-942e-b1f3741da9e8" rel="noopener noreferrer"&gt;&lt;em&gt;MakerWorld&lt;/em&gt; Parametric Model Maker&lt;/a&gt;, where you can also download a 3MF file for your slicer.&lt;/p&gt;

&lt;p&gt;For creating the animation video, the project also includes a subtitle file named &lt;a href="https://github.com/jhermann/things/blob/main/models/ac-bayonet-connectors/subtitles.srt" rel="noopener noreferrer"&gt;subtitles.srt&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Choose the Dimensions
&lt;/h3&gt;

&lt;p&gt;Open the &lt;a href="https://makerworld.com/en/makerlab/parametricModelMaker?designId=3220030&amp;amp;from=model_page&amp;amp;modelName=hose-connector-female.scad&amp;amp;scadUrl=https%3A%2F%2Fraw.githubusercontent.com%2Fjhermann%2Fthings%2Frefs%2Fheads%2Fmain%2Fmodels%2Fac-bayonet-connectors%2Fhose-connector-multi-plate.scad&amp;amp;unikey=f5a0b360-ef3e-4e34-942e-b1f3741da9e8" rel="noopener noreferrer"&gt;&lt;em&gt;MakerWorld&lt;/em&gt; Parametric Model Maker&lt;/a&gt; link. Wait for the model to load, then use the parameter controls to enter your measurements in millimetres:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Set &lt;code&gt;hose_diameter&lt;/code&gt; to the inside diameter of your hose.&lt;/li&gt;
&lt;li&gt;Set &lt;code&gt;hose_connector_height&lt;/code&gt; to the length of the straight fitting section.&lt;/li&gt;
&lt;li&gt;Adjust &lt;code&gt;wall_thickness&lt;/code&gt; if you prefer a different wall size.&lt;/li&gt;
&lt;li&gt;Adjust &lt;code&gt;tolerance_gap&lt;/code&gt; if your printer consistently makes holes too tight or too loose.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The default values are a 150 mm hose diameter, a 45 mm fitting height, a 2.5 mm wall thickness, and a 0.15 mm tolerance gap. The diameter and length are limited by what fits into your printer's build volume.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F8akswssm4s6m8lad5ujg.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F8akswssm4s6m8lad5ujg.png" alt="Model Parameter" width="800" height="678"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Measure your real hose before entering the diameter value; nominal hose sizes are not always the same as their measured inside diameter. Check the preview after changing the parameters and make sure the hose can slide over the fitting by using the measuring tool in your slicer. Section 4 explains how to download the customized 3MF with all three plates.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Inspect the Assembly
&lt;/h3&gt;

&lt;p&gt;Open &lt;code&gt;hose-connector-multi-plate.scad&lt;/code&gt; in OpenSCAD and press Preview (F5) or Render (F6). The script starts with the parts arranged as an assembly. The file uses lower detail for previews and finer curves for final output, so the preview should be faster than a final render.&lt;/p&gt;

&lt;p&gt;The variables &lt;code&gt;animate = 0&lt;/code&gt; and &lt;code&gt;local = 0&lt;/code&gt;, set at the top of the script, fit the model maker case. Comment the line with those settings and uncomment the line below it for local use and watching the animation within OpenSCAD&lt;/p&gt;

&lt;p&gt;The &lt;em&gt;MakerWorld&lt;/em&gt; model creator looks out for the three modules &lt;code&gt;mw_plate_1()&lt;/code&gt;, &lt;code&gt;mw_plate_2()&lt;/code&gt;, and &lt;code&gt;mw_plate_3()&lt;/code&gt;, placing each printable part on its own plate.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Export the &lt;em&gt;MakerWorld&lt;/em&gt; Plates
&lt;/h3&gt;

&lt;p&gt;Upload the SCAD file through the &lt;em&gt;MakerWorld&lt;/em&gt; Parametric Model Maker link above and set your dimensions in the customizer. The generated download is a 3MF containing three plates:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Plate 1: the female hose connector.&lt;/li&gt;
&lt;li&gt;Plate 2: the male hose connector.&lt;/li&gt;
&lt;li&gt;Plate 3: the female adapter with the tube extension.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Open the 3MF in your slicer, check the fit and orientation, and slice the parts. The models are intended to print without supports on a printer that can handle 45° overhangs.&lt;/p&gt;

&lt;p&gt;Do not use PLA for these parts. Use a material with better heat and UV resistance instead, such as PETG or PCTG, and choose a material and print settings suitable for the temperatures and environment around your AC equipment.&lt;/p&gt;

&lt;h3&gt;
  
  
  5. Attach the Hoses
&lt;/h3&gt;

&lt;p&gt;Push each hose over its connector. Do not forget to fit a worm-drive hose clamp over the hose end before that, and tighten it carefully. The clamp is not included in the animation, but it is needed to secure the hose to the printed connector.&lt;/p&gt;

&lt;p&gt;To connect the printed parts, line up the male lugs with the female slot openings, push the male connector fully home, and twist in the direction marked on the outside of the female connector housing. To remove it, twist in the opposite direction.&lt;/p&gt;

&lt;h2&gt;
  
  
  Create the Model Animation
&lt;/h2&gt;

&lt;p&gt;The SCAD file divides the animation video into 10 scenes and uses OpenSCAD's &lt;code&gt;$t&lt;/code&gt; animation value to progress through them, going from 0.0 to 1.0 in steps depending on the amount of requested frames. OpenSCAD documents &lt;code&gt;$t&lt;/code&gt;, animation steps, and camera variables in its &lt;a href="https://en.wikibooks.org/wiki/OpenSCAD_User_Manual/Other_Language_Features#Animation" rel="noopener noreferrer"&gt;animation documentation&lt;/a&gt;. The &lt;a href="https://openscad.org/documentation.html" rel="noopener noreferrer"&gt;OpenSCAD documentation page&lt;/a&gt; also links to the language reference and command-line manual.&lt;/p&gt;

&lt;p&gt;From a terminal in the model folder, run the commands from the README. On Windows, OpenSCAD is likely not on your &lt;code&gt;PATH&lt;/code&gt; and a working command may look like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;"C:\Program Files\OpenSCAD (Nightly)\openscad.exe" -o frame.png --colorscheme "Nocturnal Gem" --animate 180 --camera=400,400,-300,0,0,50 hose-connector-multi-plate.scad

convert -delay 7 -loop 0 frame*.png hose-connector-animation.gif

ffmpeg -y -framerate 6 -i frame%05d.png -vf "subtitles=subtitles.srt" -c:v libx264 -pix_fmt yuv420p hose-connector-animation.mp4
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The first command asks OpenSCAD for 180 animation frames and defines the camera position by specifying an eye line. The second combines the generated frames into a looping GIF using &lt;a href="https://imagemagick.org/" rel="noopener noreferrer"&gt;ImageMagick&lt;/a&gt;. The last command creates a MP4 video at six frames per second and burns in the captions using &lt;a href="https://ffmpeg.org/" rel="noopener noreferrer"&gt;FFmpeg&lt;/a&gt;. On Windows, these last two commands are probably better executed in a WSL virtual machine.&lt;/p&gt;

&lt;p&gt;On Linux or macOS, replace the Windows executable path with &lt;code&gt;openscad&lt;/code&gt; if it is on your &lt;code&gt;PATH&lt;/code&gt;. Recent ImageMagick installations may use &lt;code&gt;magick&lt;/code&gt; instead of &lt;code&gt;convert&lt;/code&gt;, so the second command may be:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;magick -delay 7 -loop 0 frame*.png hose-connector-animation.gif
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The frame files are temporary render output. Keep the GIF or MP4 and remove the individual PNG files when you are finished and do not need them anymore.&lt;/p&gt;

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

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/jhermann/things/blob/main/models/ac-bayonet-connectors/README.md" rel="noopener noreferrer"&gt;Project README and print notes&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/jhermann/things/blob/main/models/ac-bayonet-connectors/hose-connector-multi-plate.scad" rel="noopener noreferrer"&gt;Multi-plate OpenSCAD source&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://makerworld.com/en/collections/33754690-air-conditioning" rel="noopener noreferrer"&gt;&lt;em&gt;MakerWorld&lt;/em&gt; Air Conditioning collection&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://openscad.org/downloads.html" rel="noopener noreferrer"&gt;OpenSCAD downloads&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://openscad.org/documentation.html" rel="noopener noreferrer"&gt;OpenSCAD documentation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://en.wikibooks.org/wiki/OpenSCAD_User_Manual/Other_Language_Features#Animation" rel="noopener noreferrer"&gt;OpenSCAD animation reference&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://en.wikipedia.org/wiki/Bayonet_mount" rel="noopener noreferrer"&gt;Bayonet mount background&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://en.wikipedia.org/wiki/Hose_clamp" rel="noopener noreferrer"&gt;Worm-drive hose clamp background&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>3d</category>
      <category>openscad</category>
      <category>3dprinting</category>
      <category>3dmodelling</category>
    </item>
    <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>
  </channel>
</rss>
