<?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: goyder</title>
    <description>The latest articles on DEV Community by goyder (@goyder).</description>
    <link>https://dev.to/goyder</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F64415%2Fff8a14fd-14ac-4c56-9f64-7183c382e1bf.jpeg</url>
      <title>DEV Community: goyder</title>
      <link>https://dev.to/goyder</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/goyder"/>
    <language>en</language>
    <item>
      <title>Running (ML) in the 90's</title>
      <dc:creator>goyder</dc:creator>
      <pubDate>Sat, 13 Feb 2021 05:27:56 +0000</pubDate>
      <link>https://dev.to/goyder/running-ml-in-the-90-s-3nn</link>
      <guid>https://dev.to/goyder/running-ml-in-the-90-s-3nn</guid>
      <description>&lt;p&gt;In this series, I'm documenting my experiences with attempting to write and execute a machine learning program (&lt;em&gt;any&lt;/em&gt; machine learning program) in Python 1.6 on a NeXT hardware emulator running NeXTSTEP.&lt;/p&gt;

&lt;h2&gt;
  
  
  Goal
&lt;/h2&gt;

&lt;p&gt;We're almost there. NeXTSTEP is running. Python's compiled. &lt;/p&gt;

&lt;p&gt;Our end goal is in reach: we now want to write a basic ML script for Python 1.6.&lt;/p&gt;

&lt;p&gt;It's time.&lt;/p&gt;

&lt;h2&gt;
  
  
  Challenges and decisions
&lt;/h2&gt;

&lt;h3&gt;
  
  
  What "ML" do we want to write?
&lt;/h3&gt;

&lt;p&gt;So here's the first question: what "machine learning" script or program should I develop? &lt;/p&gt;

&lt;p&gt;First, we should consider computational power is limited. NeXT machines ran at double-digit &lt;em&gt;megahertz&lt;/em&gt; speeds. Whatever algorithm we choose needs to be relatively simple. &lt;/p&gt;

&lt;p&gt;This is not to mention that even relative to our limited computational power, Python is going to be &lt;em&gt;slow&lt;/em&gt;. Nowadays, with supercomputers in our pockets, I think we often forget that Python is a "slow", high level language. When you are working on early 90s hardware, this point will be clear. (So GPU-enabled deep neural networks are off the table for now.)&lt;/p&gt;

&lt;p&gt;Ultimately, I decided to implement the logistic regression algorithm. It's a classic binary classifier - often one of, if not the, first taught - and I love its elegance of transforming the classic linear model into the odds ratio into a probability. &lt;/p&gt;

&lt;p&gt;&lt;iframe width="710" height="399" src="https://www.youtube.com/embed/slBI5YuVUTM"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;p&gt;&lt;em&gt;For a visual refresher of the whole process, consider this video.&lt;/em&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  Getting the data
&lt;/h4&gt;

&lt;p&gt;For data, I ran with the classics - I decided I would work with the &lt;a href="https://en.wikipedia.org/wiki/Iris_flower_data_set"&gt;Iris&lt;/a&gt; dataset. This is a data set most often used for &lt;em&gt;multiclass&lt;/em&gt; classification, so we'll have to convert it into a binary classification problem.&lt;/p&gt;

&lt;p&gt;A copy of the dataset is readily availble from the &lt;a href="https://archive.ics.uci.edu/ml/datasets/iris"&gt;UCI Machine Learning Repository&lt;/a&gt;, and is easy to transfer to Previous with our NFS.&lt;/p&gt;

&lt;h3&gt;
  
  
  A very basic, but very functional development environment
&lt;/h3&gt;

&lt;p&gt;Key question: if we're going to write code in Previous, how are we actually going to &lt;em&gt;write&lt;/em&gt; it?&lt;/p&gt;

&lt;p&gt;Interestingly, NeXTSTEP doesn't seem to have an IDE in our modern sense. There's &lt;a href="http://www.nextcomputers.org/NeXTfiles/Docs/NeXTStep/3.3/nd/DevTools/02_ProjectBuilder/ProjectBuilder.htmld/index.html"&gt;Project Builder&lt;/a&gt;, which is certainly intended to be the "hub of application development", but this doesn't have a dedicated text editor environment - it offloads that duty to the "Edit" text editor utility. &lt;/p&gt;

&lt;p&gt;If we're just working in text editors, we might as well default to the classic development environment: multiple terminal windows running a shell and Vi. (Vim isn't available by default on NeXTSTEP.)&lt;/p&gt;

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

&lt;p&gt;&lt;em&gt;I wish this looked cooler. I'll have to figure out how to make it neon green text on a black background.&lt;/em&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Writing in earnest
&lt;/h3&gt;

&lt;p&gt;With the "development environment" up and going, I got to work on writing the scripts:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://gist.github.com/goyder/2738a0f8f2e8a044c2f5c42e2bc8664a"&gt;&lt;code&gt;1-d-split-dataset.py&lt;/code&gt;&lt;/a&gt;, to transform our dataset into a form ready for binary classification.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://gist.github.com/goyder/db059e68d3dbcb146bbdbfdd23ee9f38"&gt;&lt;code&gt;logistic_regression.py&lt;/code&gt;&lt;/a&gt;, containing the main logistic regression model class.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://gist.github.com/goyder/7afd63c6e7684c425ce14fcd7de74ec8"&gt;&lt;code&gt;2-m-logistic-regression.py&lt;/code&gt;&lt;/a&gt;, to train the logistic regression model on the dataset and use it on some test values.&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  Adapting to Python 1.6
&lt;/h4&gt;

&lt;p&gt;If you look at the code in the above files, it definitely &lt;em&gt;looks&lt;/em&gt; like modern Python. Most of the hallmarks are there - indentation, clean syntax, all the things we love.&lt;/p&gt;

&lt;p&gt;There are definitely a few major and minor differences in Python 1.6, though. In particular I noted:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;numpy&lt;/code&gt; and &lt;code&gt;pandas&lt;/code&gt; are sorely missed. (&lt;code&gt;numpy&lt;/code&gt; apparently existed for Python 1, but I could not find a copy anywhere. &lt;code&gt;pandas&lt;/code&gt; was years off in the era of 1.x.) There's not a strong system for carrying out linear algebra or numerical calculations, hence the preponderance of &lt;code&gt;map&lt;/code&gt; and &lt;code&gt;lambda&lt;/code&gt; calculations and the minimally-implemented matrix multiplication (really just the dot product) in the &lt;code&gt;logistic_regression.py&lt;/code&gt; script.&lt;/li&gt;
&lt;li&gt;There's a surprising number of "utility" features not yet implemented - things like list comprehensions, the &lt;code&gt;csv&lt;/code&gt; module, and even the &lt;code&gt;zip&lt;/code&gt; function are absent and missed! &lt;/li&gt;
&lt;li&gt;There's all kinds of minor differences - there's no &lt;code&gt;import &amp;lt;module&amp;gt; as &amp;lt;name&amp;gt;&lt;/code&gt; functionality, for example. If you want to import a module, you better like its name, mister!&lt;/li&gt;
&lt;li&gt;And of course who could forget when &lt;code&gt;print&lt;/code&gt; wasn't a function? &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Overall, while it's missing a lot of modern conveniences and there are some syntax differences, it's surprisingly quick to get moving in, provided you have the &lt;a href="https://docs.python.org/release/0.6/"&gt;documentation&lt;/a&gt; open.&lt;/p&gt;

&lt;p&gt;(I still desperately miss &lt;code&gt;numpy&lt;/code&gt; and &lt;code&gt;pandas&lt;/code&gt;, though.)&lt;/p&gt;

&lt;h3&gt;
  
  
  The proof in the pudding
&lt;/h3&gt;

&lt;p&gt;Okay. Final proof. If we call the entry point to our machine learning script, will it run?&lt;/p&gt;

&lt;p&gt;&lt;a href="https://webmshare.com/play/OxvPy"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--BuZoHUEy--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/lxj483cfswfi67uaolor.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Click through for the stunning conclusion!&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Yes!&lt;/strong&gt; Albeit pretty slowly. Regardless, it was wonderful to see these scripts running to completion after all of the hard work and tweaking required to get to this point. &lt;/p&gt;

&lt;h2&gt;
  
  
  What next?
&lt;/h2&gt;

&lt;p&gt;There's nothing more! &lt;/p&gt;

&lt;p&gt;In this first article of this series, I set out to a challenge with three components:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;em&gt;Write&lt;/em&gt; and &lt;em&gt;execute&lt;/em&gt; a machine learning program;&lt;/li&gt;
&lt;li&gt;In &lt;em&gt;Python 1.6&lt;/em&gt;,&lt;/li&gt;
&lt;li&gt;Compiled from source in &lt;em&gt;NeXTSTEP&lt;/em&gt;. &lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The challenge has been achieved, and this silly trophy has been claimed! &lt;/p&gt;

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

&lt;p&gt;Overall, this has been a really fun experience and a good way to spend a couple of weekends. Besides satisfying my curiosity around NeXT and NeXTSTEP, I've certainly picked up bit of technical know-how as well - in particular, I've broadened my horizons around software compilation (modern and otherwise) and *nix disk management and mounting.&lt;/p&gt;

&lt;p&gt;That's all there is - I hope you've enjoyed this series, and I hope you can use my materials provided in these articles to pursue your own NeXT adventures.&lt;/p&gt;




&lt;h2&gt;
  
  
  Series review
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Where have we got to?
&lt;/h3&gt;

&lt;p&gt;At this point we have:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Compiled Previous&lt;/li&gt;
&lt;li&gt;Found pre-built images of NeXTSTEP.&lt;/li&gt;
&lt;li&gt;Successfully mounted and launched an image of &lt;em&gt;NeXTSTEP 4.3&lt;/em&gt;.&lt;/li&gt;
&lt;li&gt;Set up a fileshare so we can easily get files onto the system.&lt;/li&gt;
&lt;li&gt;Found an image of the NeXTSTEP developer tools.&lt;/li&gt;
&lt;li&gt;Installed the developer tools we so have the capability to build Python from source.&lt;/li&gt;
&lt;li&gt;Downloaded the source code for Python 1.6 and transferred it onto the NeXT machine.&lt;/li&gt;
&lt;li&gt;Compiled a (hacky, slightly incomplete) version of Python 1.6.&lt;/li&gt;
&lt;li&gt;Wrote a Python 1.6 script to carry out a logistic regression script.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  How did we get here?
&lt;/h3&gt;

&lt;p&gt;In this sesh we:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Downloaded the classic &lt;a href="https://archive.ics.uci.edu/ml/datasets/Iris"&gt;Iris&lt;/a&gt; dataset and transferred it into the Previous environment via the NFS.&lt;/li&gt;
&lt;li&gt;Found the &lt;a href="https://docs.python.org/release/1.6/"&gt;Python 1.6 reference documentation&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;Wrote a script to transform the raw dataset into a form suitable for our logistic regression script.&lt;/li&gt;
&lt;li&gt;Wrote a logistic regression model that allows for training and prediction.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Successfully ran the logistic regression model script &lt;em&gt;in&lt;/em&gt; Python 1.6 &lt;em&gt;in&lt;/em&gt; NeXTSTEP!&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  What could we explore further?
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://numpy.org/"&gt;NumPy&lt;/a&gt;, Python's most critical array based tool, apparently existed in one form or another in Python 1.6. It'd be interesting to find and install a compatible version.&lt;/li&gt;
&lt;li&gt;Speaking of installing packages, Python 1.6 was a time before &lt;code&gt;pip&lt;/code&gt; (and before &lt;code&gt;easy_install&lt;/code&gt;, for that matter) - it would be a good exercise to try managing this Python package environment in its earlier form, if only to gain an appreciation for how good we have it now.&lt;/li&gt;
&lt;li&gt;It would be fun to explore building a ML tool in NeXT's in-built developer tools, to really get the authentic NeXT ML solution!&lt;/li&gt;
&lt;li&gt;If we deployed Previous instance into a Docker container and somehow set this up via an API, we could truly take the claim of the very worst Dockerised ML solution ever (not to mention this would be a fun and interesting technical challenge).&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>python</category>
      <category>machinelearning</category>
      <category>retro</category>
    </item>
    <item>
      <title>Compiling Python from source, for fun and frustration</title>
      <dc:creator>goyder</dc:creator>
      <pubDate>Wed, 10 Feb 2021 14:03:59 +0000</pubDate>
      <link>https://dev.to/goyder/compiling-python-from-source-for-fun-and-frustration-1f61</link>
      <guid>https://dev.to/goyder/compiling-python-from-source-for-fun-and-frustration-1f61</guid>
      <description>&lt;p&gt;In this series, I'm documenting my experiences with attempting to write and execute a machine learning program (&lt;em&gt;any&lt;/em&gt; machine learning program) in Python 1.6 on a NeXT hardware emulator running NeXTSTEP.&lt;/p&gt;

&lt;p&gt;As of the last post, we have:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Previous, the NeXT emulator, up and running.&lt;/li&gt;
&lt;li&gt;
&lt;em&gt;NeXTSTEP 3.3&lt;/em&gt; up and running.&lt;/li&gt;
&lt;li&gt;An NFS server up and running, and the NeXT machine connected to it.&lt;/li&gt;
&lt;li&gt;The NeXTSTEP DEVELOPER environment installed and functional.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We're now at the pointy end - it's time to attempt to compile Python 1.6 from source.&lt;/p&gt;

&lt;h2&gt;
  
  
  Goal
&lt;/h2&gt;

&lt;p&gt;Our goal for this session is simple and clear: compile Python 1.6 from source, and confirm the interpreter works.&lt;/p&gt;

&lt;h2&gt;
  
  
  Challenges and decisions
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Getting the source
&lt;/h3&gt;

&lt;p&gt;The source code for Python 1.6 is actually still available on the &lt;a href="https://www.python.org/"&gt;main Python website&lt;/a&gt;, although it's not well advertised nowadays. &lt;/p&gt;

&lt;p&gt;Interestingly, the &lt;a href="https://www.python.org/download/releases/1.6/download/"&gt;download page for Python 1.6&lt;/a&gt; has some broken HTML, and it's not possible to click through to agree to the license agreement. However, the &lt;a href="https://www.python.org/download/releases/1.6.1/download"&gt;download page for Python 1.6.1&lt;/a&gt; is still functioning, so I went with that. &lt;/p&gt;

&lt;p&gt;Using the NFS previously set up, it was straightforward to move the source &lt;a href="https://en.wikipedia.org/wiki/Tar_(computing)"&gt;tarball&lt;/a&gt;  into Previous, unpack it, and start to look through the README.&lt;/p&gt;

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

&lt;p&gt;&lt;em&gt;It's always valuable to look at the README.&lt;/em&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Getting going...
&lt;/h3&gt;

&lt;p&gt;Flicking through the README, I note that there's special installation instructions for each platform, including NeXT... however, there's nothing other than a note about "fat binaries" (a term which prompted a Google from me, but not much else).&lt;/p&gt;

&lt;p&gt;The build instructions are straightforward: first, I just have to run the script &lt;code&gt;./configure&lt;/code&gt;, which I promptly do. This runs through a huge number of checks, all of which are satisfactory:&lt;/p&gt;

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

&lt;p&gt;And then I just have to run &lt;code&gt;./make&lt;/code&gt;, which begins strongly:&lt;/p&gt;

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

&lt;p&gt;&lt;em&gt;Look at those compilations fly!&lt;/em&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  ...and stopping...
&lt;/h3&gt;

&lt;p&gt;Pretty quickly after compilation, it halts with the following error:&lt;/p&gt;

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

&lt;p&gt;Seems like there's an issue with the NeXTSTEP DEVELOPER provided C standard library implementation - apparently they've mixed in some &lt;a href=""&gt;Objective-C&lt;/a&gt;. This is maybe not surprising (Objective-C was the primary language of choice for NeXT, and for Apple products from OS X onwards until Swift came along), but I am a little surprised it would be implemented in such a way to crash regular C compilation.&lt;/p&gt;

&lt;p&gt;Fortunately, the fix has been provided in the error message (that's friendly!) and that can be implemented under compiler options in the very well organised &lt;code&gt;Makefile&lt;/code&gt;, changing&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight make"&gt;&lt;code&gt;&lt;span class="c"&gt;# Compiler options passed to subordinate makes
&lt;/span&gt;&lt;span class="nv"&gt;OPT&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;       &lt;span class="nt"&gt;-g&lt;/span&gt; &lt;span class="nt"&gt;-02&lt;/span&gt; &lt;span class="nt"&gt;-OPT&lt;/span&gt;:0limit&lt;span class="o"&gt;=&lt;/span&gt;0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;to&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight make"&gt;&lt;code&gt;&lt;span class="c"&gt;# Compiler options passed to subordinate makes
&lt;/span&gt;&lt;span class="nv"&gt;OPT&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;       &lt;span class="nt"&gt;-g&lt;/span&gt; &lt;span class="nt"&gt;-02&lt;/span&gt; &lt;span class="nt"&gt;-OPT&lt;/span&gt;:0limit&lt;span class="o"&gt;=&lt;/span&gt;0 &lt;span class="nt"&gt;-ObjC&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With this change made, calling &lt;code&gt;make&lt;/code&gt; has the compilation process buzzing along!&lt;/p&gt;

&lt;h3&gt;
  
  
  ...and stopping again...
&lt;/h3&gt;

&lt;p&gt;However, this doesn't last. Once we get to the &lt;code&gt;Modules&lt;/code&gt; section of the compilation process, it bombs out on a compilation error - not enough arguments for a C function, it seems.&lt;/p&gt;

&lt;p&gt;I'm not terribly familiar with C compilation, so I'm really coming up the learning cliff on this one, and I take a bit deep dive. After a fair bit of investigation I find:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Something's crashing in the &lt;code&gt;_locales&lt;/code&gt; module. The code is making a call to &lt;code&gt;strcoll&lt;/code&gt; and crapping out, saying there's not enough arguments. That feels like a very blatant oversight for the reasonably-mature-at-this-point Python codebase, so I take a closer look.&lt;/li&gt;
&lt;li&gt;In the Python codebase, I find the line where it's dying, and confirm there's a call to &lt;code&gt;strcoll&lt;/code&gt; with two arguments, &lt;code&gt;s1&lt;/code&gt; and &lt;code&gt;s2&lt;/code&gt;. The call in &lt;code&gt;_localmodule.C&lt;/code&gt; is perfectly normal - totally textbook! This is a &lt;a href="http://www.cplusplus.com/reference/cstring/strcoll/"&gt;bog-standard implementation&lt;/a&gt; of a bog-standard C function. &lt;/li&gt;
&lt;li&gt;I then dug into the &lt;code&gt;string.h&lt;/code&gt; file installed with NeXTSTEP DEVELOPER and it's... odd? The function has a totally different signature:
&lt;code&gt;int strcoll(char *to, size_t maxsize, const char *from)&lt;/code&gt;
This &lt;code&gt;maxsize&lt;/code&gt; argument seems to come out of nowhere, and is borking the install.&lt;/li&gt;
&lt;li&gt;Looking up this specific implementation, the &lt;em&gt;only&lt;/em&gt; reference I find to this signature is in the book &lt;a href="https://books.google.com.au/books?id=NGkECAAAQBAJ&amp;amp;lpg=PA467&amp;amp;ots=9PyBkAKke1&amp;amp;dq=strcoll(to%2C%20maxsize%2C%20from)&amp;amp;pg=PA467#v=onepage&amp;amp;q=strcoll(to,%20maxsize,%20from)&amp;amp;f=false"&gt;"Software Engineering in C"&lt;/a&gt;, which notes that it "documents the proposed ANSI standard C, which is expected to be ratified in 1987."&lt;/li&gt;
&lt;/ul&gt;

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

&lt;ul&gt;
&lt;li&gt;I looked up the ANSI standard C implementation of &lt;code&gt;strcoll&lt;/code&gt;, and &lt;code&gt;maxsize&lt;/code&gt; was nowhere to be seen.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Or, summarising these findings up in a chain of "whys":&lt;/p&gt;

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

&lt;p&gt;What a journey.&lt;/p&gt;

&lt;p&gt;Overall, something's a little awry here - I would love to find out the history of this strange implementation, because I could find naught in my searching. &lt;/p&gt;

&lt;h3&gt;
  
  
  A limiting, but effective, workaround
&lt;/h3&gt;

&lt;p&gt;Thinking there might be a solution in the README, I went back and re-read it. After reading from top to bottom, I found an interesting message for installing under SunOS 4.x:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;When using the standard "cc" compiler, certain modules not be compilable because they use non-K&amp;amp;R syntax. You should be able to get a basic Python interpreter by commenting out such modules in the Modules/Setup file, but I really recommend using gcc.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This sounds good. We're certainly getting errors due to non-standard syntax (although perhaps not like they meant).&lt;/p&gt;

&lt;p&gt;I'd previously looked into &lt;code&gt;gcc&lt;/code&gt;, but couldn't find a  non-NeXT provided  implementation for NeXTSTEP. However, a "basic python interpreter" could certainly be sufficient. &lt;/p&gt;

&lt;p&gt;Sure enough, I was able to jump into the &lt;code&gt;Modules/Setup&lt;/code&gt; file and begin commenting out the troublesome modules as they cropped up during compilation. (Interestingly, it seems there's a lot of intended variation in what modules to install, based on the notes within this file.)&lt;/p&gt;

&lt;p&gt;This feels janky, but effective. I can only hope the finished product is sufficient for my needs.&lt;/p&gt;

&lt;h3&gt;
  
  
  Hello, snek
&lt;/h3&gt;

&lt;p&gt;Slowly but surely I made progress until it is complete: the &lt;code&gt;make&lt;/code&gt; process ran to completion (approximately 1 hour of uninterrupted compilation time) and I am greeted with a single blessed &lt;code&gt;python&lt;/code&gt; executable file. &lt;/p&gt;

&lt;p&gt;I run it, and I am greeted with the beautiful sight of a Python interpreter (and one minor error):&lt;/p&gt;

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

&lt;p&gt;&lt;code&gt;[GCC neXT DevKit-based CPP 3.1] on next3_3&lt;/code&gt; - I'd like to think that's a compilation message that isn't often seen. &lt;/p&gt;

&lt;p&gt;I ran some basic tests, instantiated and worked with a variety of data types - the interpreter seems good and functional. Success!&lt;/p&gt;

&lt;h2&gt;
  
  
  What next?
&lt;/h2&gt;

&lt;p&gt;With the Python interpreter up and running, the only thing left to do is write the basic machine learning script in mid-90s era Python and claim victory!&lt;/p&gt;




&lt;h2&gt;
  
  
  Series review
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Where have we got to?
&lt;/h3&gt;

&lt;p&gt;At this point we have:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Compiled Previous&lt;/li&gt;
&lt;li&gt;Found pre-built images of NeXTSTEP.&lt;/li&gt;
&lt;li&gt;Successfully mounted and launched an image of &lt;em&gt;NeXTSTEP 3.3&lt;/em&gt;.&lt;/li&gt;
&lt;li&gt;Set up a fileshare so we can easily get files onto the system.&lt;/li&gt;
&lt;li&gt;Found an image of the NeXTSTEP developer tools.&lt;/li&gt;
&lt;li&gt;Installed the developer tools we so have the capability to build Python from source.&lt;/li&gt;
&lt;li&gt;Downloaded the source code for Python 1.6 and transferred it onto the NeXT machine.&lt;/li&gt;
&lt;li&gt;Compiled a (hacky, slightly incomplete) version of Python 1.6.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  How did we get here?
&lt;/h3&gt;

&lt;p&gt;In this sesh we:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Pulled the Python 1.6.1 source code from the &lt;a href="https://www.python.org/download/releases/1.6.1/download/"&gt;Python website&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Transferred the files onto the NeXT machine via our NFS&lt;/li&gt;
&lt;li&gt;Found a "workaround", albeit a limiting one, for our issues with compiling Python 1.6 from source

&lt;ul&gt;
&lt;li&gt;By commenting out troublesome modules from the &lt;code&gt;Modules/Setup&lt;/code&gt; file, we are able to progress and complete compilation.&lt;/li&gt;
&lt;/ul&gt;


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

&lt;h3&gt;
  
  
  What could we explore further?
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;The guidance for compilation noted that the removal of modules might not be necessary if using a bog-standard &lt;em&gt;gcc&lt;/em&gt; implementation, rather than NeXT's custom version. It could be worthwhile to find an implementation of &lt;em&gt;gcc&lt;/em&gt; for &lt;em&gt;NeXTSTEP 3.3&lt;/em&gt; - I've seen enough rumblings on the NeXT forums to think this is possible. &lt;/li&gt;
&lt;li&gt;There are updates available for &lt;em&gt;NeXTSTEP 3.3&lt;/em&gt;, and potentially the NEXTSTEP DEVELOPER codebase. Applying these updates may help solve the non-standard C libraries.&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>python</category>
      <category>machinelearning</category>
      <category>retro</category>
    </item>
    <item>
      <title>Installing the hottest development environment of 1995</title>
      <dc:creator>goyder</dc:creator>
      <pubDate>Sun, 07 Feb 2021 12:50:45 +0000</pubDate>
      <link>https://dev.to/goyder/installing-the-hottest-development-environment-of-1995-45dh</link>
      <guid>https://dev.to/goyder/installing-the-hottest-development-environment-of-1995-45dh</guid>
      <description>&lt;p&gt;In this series, I'm documenting my experiences with attempting to write and execute a machine learning program (&lt;em&gt;any&lt;/em&gt; machine learning program) in Python 1.6 on a NeXT hardware emulator running NeXTSTEP.&lt;/p&gt;

&lt;p&gt;At this point, I've managed to get the NeXT machine emulator going, found and launched &lt;em&gt;NeXTSTEP 3.3&lt;/em&gt;, and even connected it a file server on my local network. We're &lt;em&gt;almost&lt;/em&gt; ready to start compiling Python 1.6 from source - we just need to make sure we have the tools to do so.&lt;/p&gt;

&lt;h2&gt;
  
  
  Goal
&lt;/h2&gt;

&lt;p&gt;Our goal in this session is to get some standard developer tools in &lt;em&gt;NeXTSTEP&lt;/em&gt; installed so we can compile Python 1.6 from source.&lt;/p&gt;

&lt;h2&gt;
  
  
  Challenges and decisions
&lt;/h2&gt;

&lt;h3&gt;
  
  
  What "developer tools" do we need?
&lt;/h3&gt;

&lt;p&gt;The standard implementation for Python is "CPython" - this is the Python programming language written in C and Python code. (For interest, there are various other implementations such as &lt;a href="//en.wikipedia.org/wiki/PyPy"&gt;PyPy&lt;/a&gt; or &lt;a href="//en.wikipedia.org/wiki/Jython"&gt;Jython&lt;/a&gt; that are worth exploring.) &lt;/p&gt;

&lt;p&gt;CPython 1.6 is what we aim to install, so we'll need make sure we have some version of the &lt;a href="https://cs.brown.edu/courses/cs033/docs/guides/tools.pdf"&gt;standard C development tools&lt;/a&gt; installed.&lt;/p&gt;

&lt;h4&gt;
  
  
  ...Do we already have them?
&lt;/h4&gt;

&lt;p&gt;If you bought the $4,999 developer edition of NeXTSTEP/OPENSTEP, it shipped with "NeXTSTEP DEVELOPER", a second disk that had all of the NeXT development tools (see section 3.5 &lt;a href="https://www.levenez.com/NeXTSTEP/faq.html"&gt;here&lt;/a&gt; for more details). This disk includes a precompiled instance of &lt;a href="https://en.wikipedia.org/wiki/GNU_Compiler_Collection"&gt;GCC&lt;/a&gt;, include files, linker libraries, and all of the custom NeXT software that made developing software 10x faster (according to NeXT's marketing team). These tools should be what we need.&lt;/p&gt;

&lt;p&gt;However, none of the these programs and utilities are currently installed. This makes sense -  the pre-built disk image I used to &lt;em&gt;NeXTSTEP 3.3&lt;/em&gt; was totally fresh and appeared to be at the first config step, so it's up to me to install those tools!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.worthpoint.com/worthopedia/nextstep-developer-software-lot-1888093898"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--QNgx97mh--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/i/yld8fmvdjklxk315k72h.jpg" width="800" height="800"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Finding NeXTSTEP DEVELOPER
&lt;/h3&gt;

&lt;p&gt;How do we get our hands on NeXTSTEP DEVELOPER? Well, pretty much the way we got our hands on NeXTSTEP - search through &lt;a href="https://en.wikipedia.org/wiki/Abandonware"&gt;Abandonware sites&lt;/a&gt;.  &lt;/p&gt;

&lt;p&gt;Very quickly I was able to find an ISO (disk image) of &lt;a href="https://archive.org/details/nextstep3-3dev"&gt;NeXTSTEP DEVELOPER on WinWorld&lt;/a&gt;, the same site that hosted the pre-built disk image of NeXTSTEP 3.3 I'm running. Thanks, WinWorld! &lt;/p&gt;

&lt;h3&gt;
  
  
  Installing NeXTSTEP DEVELOPER
&lt;/h3&gt;

&lt;p&gt;We've got an &lt;code&gt;.ISO&lt;/code&gt; file sitting on our harddrive - now we have to get that into our virtual environment.&lt;/p&gt;

&lt;h4&gt;
  
  
  Mounting...
&lt;/h4&gt;

&lt;p&gt;First, we need to mount the image into our environment. To do so, we'll use the SCSI connection options within Previous to create a virtual disk drive, and then mount the &lt;code&gt;.ISO&lt;/code&gt; file. Our SCSI connections should look like this:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--NqGWBNL5--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/i/1bw2rgzpxkwo4yw7yqiu.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--NqGWBNL5--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/i/1bw2rgzpxkwo4yw7yqiu.png" width="800" height="647"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Effectively giving us a pretty standard machine, with a harddrive and CD drive attached:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--O5y0OWIL--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/i/5504negkt7hxvxnq97h7.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--O5y0OWIL--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/i/5504negkt7hxvxnq97h7.png" width="800" height="560"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;NeXT recognises the CD and it shows up as a new drive, just like on a modern Mac:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--fhRsTK8V--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/i/pfbn4g55v41a4unbv7at.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--fhRsTK8V--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/i/pfbn4g55v41a4unbv7at.png" width="800" height="647"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  ...and installing
&lt;/h4&gt;

&lt;p&gt;With the disk ready to go, we can now install the development tools. It turns out this is &lt;em&gt;super&lt;/em&gt; straightforward - under the path &lt;code&gt;NextCD/Packages/&lt;/code&gt; there are four &lt;code&gt;.pkg&lt;/code&gt; files:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;DeveloperDoc.pkg&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;DeveloperLibs.pkg&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;DeveloperTools.pkg&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;GNUSource.pkg&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;And when we open these files, they load in NeXT's in-built Installer app, making installation a breeze. Just make sure to launch them as a user account with sufficient permissions via the OpenSesame utility.&lt;/p&gt;

&lt;p&gt;Not knowing what order these should be loaded in (and noting that &lt;a href="https://blog.pizzabox.computer/posts/hp712-nextstep-part-2/"&gt;others&lt;/a&gt; were unable to find much information on install order, either) I went ahead and started installing in the following order &lt;code&gt;DeveloperTools&lt;/code&gt;, &lt;code&gt;DeveloperLibs&lt;/code&gt;, &lt;code&gt;GNUSource&lt;/code&gt;, and finally &lt;code&gt;DeveloperDoc&lt;/code&gt;. &lt;/p&gt;

&lt;p&gt;Each installation was successful on the first run (provided I remembered to run with sufficient permissions!):&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--1KRPJzes--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/i/0asulj2sdki15gezwky4.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--1KRPJzes--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/i/0asulj2sdki15gezwky4.png" width="800" height="647"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;All in all, a surprisingly smooth process.&lt;/p&gt;

&lt;h3&gt;
  
  
  A final test to make sure this works
&lt;/h3&gt;

&lt;p&gt;To make sure that we actually have a working set of C development tools working, let's write the classic stub of "Hello World" and make sure it compiles. &lt;/p&gt;

&lt;p&gt;We'll write a minimal &lt;code&gt;helloworld.C&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight c"&gt;&lt;code&gt;&lt;span class="cp"&gt;#include&lt;/span&gt; &lt;span class="cpf"&gt;&amp;lt;stdio.h&amp;gt;&lt;/span&gt;&lt;span class="cp"&gt;
&lt;/span&gt;
&lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="nf"&gt;main&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;void&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;printf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Hello, world!&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="k"&gt;return&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And fire off the compilation command and verification to check that it works:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;localhost&amp;gt; cc helloworld.C &lt;span class="nt"&gt;-o&lt;/span&gt; hello
localhost&amp;gt; hello
Hello, world!
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And as we can see from the following screenshot - all good! &lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--cMaXx1QS--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/i/7sdom5zkoa7jbczs74lo.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--cMaXx1QS--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/i/7sdom5zkoa7jbczs74lo.png" width="800" height="647"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We've passed a basic smoke test and can venture into deeper waters.&lt;/p&gt;

&lt;h2&gt;
  
  
  What next?
&lt;/h2&gt;

&lt;p&gt;With a working C compiler, we can now attempt to compile Python 1.6 from source. &lt;/p&gt;

&lt;p&gt;Considering the scope of our "Hello, world" test run, this is a bit like going from building a blinky LED circuit to constructing a Volkswagen Mini, but fortune favours the bold!&lt;/p&gt;




&lt;h2&gt;
  
  
  Series review
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Where have we got to?
&lt;/h3&gt;

&lt;p&gt;At this point we have:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Compiled Previous.&lt;/li&gt;
&lt;li&gt;Found pre-built images of NeXTSTEP.&lt;/li&gt;
&lt;li&gt;Successfully mounted and launched in image of &lt;em&gt;NeXTSTEP 3.3&lt;/em&gt;.&lt;/li&gt;
&lt;li&gt;Set up a fileshare so we can easily get files onto the system.&lt;/li&gt;
&lt;li&gt;Found an image of the NeXTSTEP developer tools.&lt;/li&gt;
&lt;li&gt;Installed the developer tools so we have the capability to build Python from source.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  How did we get here?
&lt;/h3&gt;

&lt;p&gt;In this sesh we:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Located an ISO of NeXTSTEP DEVELOPER (&lt;a href="https://archive.org/details/nextstep3-3dev"&gt;here&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;Found a variety of useful information sources on it, including &lt;a href="https://www.levenez.com/NeXTSTEP/faq.html"&gt;this very thorough FAQ&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;Mounted the NEXTSTEP DEVELOPER disk and installed the relevant software using NeXT's standard install processes, running them with elevated permissions using OpenSesame. &lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  What could we explore further?
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;We've barely scratched the surface of the NeXTSTEP DEVELOPER tools. These tools are the technical grandparents of all modern Apple development tools, and it would be fascinating to step through. 

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://www.youtube.com/watch?v=_4hs4K7AEvQ"&gt;This video&lt;/a&gt; gives a good walk through of some of the tools (and happens to cover a lot of the same content of this series!)&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;li&gt;I'd like to suss out the ideal text editor - I've been happily working in Vi via the terminal but I'd be surprised if there wasn't a preferred, reasonably well featured text editor built into the NeXTSTEP OS. &lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>python</category>
      <category>machinelearning</category>
      <category>nextjs</category>
      <category>retro</category>
    </item>
    <item>
      <title>Networking through the ages</title>
      <dc:creator>goyder</dc:creator>
      <pubDate>Sun, 07 Feb 2021 01:45:31 +0000</pubDate>
      <link>https://dev.to/goyder/networking-through-the-ages-2783</link>
      <guid>https://dev.to/goyder/networking-through-the-ages-2783</guid>
      <description>&lt;p&gt;In this series, I'm documenting my experiences with attempting to write and execute a machine learning program (&lt;em&gt;any&lt;/em&gt; machine learning program) in Python 1.6 on a NeXT hardware emulator running NeXTSTEP.&lt;/p&gt;

&lt;p&gt;In the last article, I managed to finally launch a copy of NeXTSTEP 3.3 on the Previous emulator - which is rad, because:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;That's one of the three goals I had! (I'll take my dopamine reward now, please.)&lt;/li&gt;
&lt;li&gt;It's very enjoyable to use the NeXTSTEP operating system (OS). I'm not to do a tour of NeXTSTEP because there is &lt;a href="https://www.youtube.com/watch?v=0sOyuiPtlok&amp;amp;t"&gt;lots&lt;/a&gt; and &lt;a href="https://www.youtube.com/watch?v=TIrTh80Z8jw"&gt;lots&lt;/a&gt; and &lt;a href="https://www.youtube.com/watch?v=tB0uqZTwZOE"&gt;lots&lt;/a&gt; of wonderful content in this space, but needless to say, it's very neat. It feels robust and full-featured in a way older OSes often... don't.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--vpOUuwsF--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/i/ow2pphuiy1nuhrqnkwex.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--vpOUuwsF--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/i/ow2pphuiy1nuhrqnkwex.png" alt="Very neat things happening in NeXTSTEP" width="800" height="647"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Pictured: many neat things happening.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;However, there is a key bit of functionality we're missing: file transfers. &lt;/p&gt;

&lt;h2&gt;
  
  
  Goal
&lt;/h2&gt;

&lt;p&gt;Our goal for this session is simple: figure out a simple way to be able to move files from the outside world &lt;em&gt;into&lt;/em&gt; my NeXTSTEP instance. &lt;/p&gt;

&lt;p&gt;This is going to be required to get files from the internet into the NeXTSTEP environment (like the Python source code, for example).&lt;/p&gt;

&lt;h2&gt;
  
  
  Challenges and decisions
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Why not directly connect to the internet?
&lt;/h3&gt;

&lt;p&gt;Interestingly, it might have actually been possible to find a web browser, connect Previous to the internet, and go download the files directly. However, I was wary of this approach for two reasons:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Safety&lt;/strong&gt; - this "machine" is decades old, and while I doubt there's too much malware for NeXTSTEP floating around, I don't want to take this ancient, unsecured device outside.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Pragmatism&lt;/strong&gt; - The world wide web has moved on &lt;em&gt;a lot&lt;/em&gt; since 1994. Even if I just accessed 90s era FTP servers, I suspect this would be painful (...but interesting - perhaps this is an experiment for another day)!&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://store.steampowered.com/app/844590/Hypnospace_Outlaw/"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--sVp9jhD5--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/i/g3y7mrgj39bs06qggjsw.jpg" alt="Old internet" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Come back to me, o internet of the 90s!&lt;/em&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  What are the options for transferring files?
&lt;/h3&gt;

&lt;p&gt;Okay, so what else could I do? A few paths come to mind:&lt;/p&gt;

&lt;h4&gt;
  
  
  "Removable media"
&lt;/h4&gt;

&lt;p&gt;If I was working with a physical machine, the simplest way I would get files to and fro would be via some kind of removable media - CDs, floppy disks, external harddrives, for example.&lt;/p&gt;

&lt;p&gt;Now, because I'm working with a virtual machine, I could probably do the same with virtual removable media. Previous offers all the media connection options of an actual NeXT device, mainly via emulating &lt;a href="https://en.wikipedia.org/wiki/SCSI"&gt;SCSI connections&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--YSWVhwlg--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/i/urf77v3fkoqcx9mkdiy0.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--YSWVhwlg--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/i/urf77v3fkoqcx9mkdiy0.png" alt="SCSI Connections" width="643" height="465"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Using these SCSI connections, I see two main options:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Removable drives - I could create virtual hard-disks or (shudder) floppy disk images that I could write to on my Ubuntu host machine, load into Previous, and access within NeXTSTEP.&lt;/li&gt;
&lt;li&gt;I could also create &lt;a href="https://en.wikipedia.org/wiki/Optical_disc_image"&gt;ISO files&lt;/a&gt; to pass files back and forth, very much as if I was burning them onto CDs. &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Neither are super appealing options, to be honest - both will require a lot of shuttling back and forth of imaginary "removable media"; I'd prefer to leave that in the 90s. But we could give the hard-disk option a go.&lt;/p&gt;

&lt;h4&gt;
  
  
  Trying anyway
&lt;/h4&gt;

&lt;p&gt;The most straightforward path I saw was to build a "fake" disk drive, mount it in Ubuntu, and write the relevant files to it; I could then mount it to the NeXTSTep instance. &lt;/p&gt;

&lt;p&gt;The main challenge is file systems and partitioning - things have changed between 1994 and 2020!&lt;/p&gt;

&lt;p&gt;NeXT is fully featured, and is happy to format a blank disk presented to it; but mounting the consequently formatted disk in Ubuntu is painful because its auto-file-system-detection doesn't readily recognise NeXT's own ancient file-system. &lt;/p&gt;

&lt;p&gt;I think this approach has legs, but I decided to explore other options in the meantime.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;UPDATE&lt;/strong&gt; - I eventually cracked this! The key thing is to manually set the &lt;a href="https://www.kernel.org/doc/html/latest/admin-guide/ufs.html"&gt;file system for mounting to a specific NeXTSTEP option&lt;/a&gt;. Unfortunately, this only allows read-only access, which means that this approach is &lt;em&gt;not&lt;/em&gt; suitable for our needs. Regardless, example code is as follows:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;losetup &lt;span class="nt"&gt;-f&lt;/span&gt;  &lt;span class="c"&gt;# find next loop device, e.g. /dev/loopXX&lt;/span&gt;
&lt;span class="nb"&gt;sudo &lt;/span&gt;losetup /dev/loopXX NS33_2GB_dup.dd  &lt;span class="c"&gt;# assign to loop&lt;/span&gt;
&lt;span class="nb"&gt;sudo &lt;/span&gt;mount &lt;span class="nt"&gt;-t&lt;/span&gt; ufs &lt;span class="nt"&gt;-o&lt;/span&gt; &lt;span class="nv"&gt;ufstype&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;nextstep /dev/loopXX /mnt/next 
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  &lt;strong&gt;Network file systems&lt;/strong&gt;
&lt;/h4&gt;

&lt;p&gt;Previous had two files in its core directory of promise: &lt;code&gt;networking.howto.txt&lt;/code&gt; and &lt;code&gt;filesharing.howto.txt&lt;/code&gt;. The former walked through how to connect NeXTSTEP to your local network; the latter how to set up a &lt;a href="https://en.wikipedia.org/wiki/Network_File_System"&gt;Network File System&lt;/a&gt; server on a Mac and connect the machine to it.&lt;/p&gt;

&lt;p&gt;"Surely not," I thought. "No way I could spin up a file server on my local network on a 2018 Mac and have this 26 year OS connect and talk to it happily."&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--uWJLbfmi--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/i/gqdga3jc4m1543013513.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--uWJLbfmi--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/i/gqdga3jc4m1543013513.png" alt="Proposed NFS network" width="481" height="543"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;The proposed network solution.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;It turns out that, in fact, you can. &lt;/p&gt;

&lt;h4&gt;
  
  
  A "brief" unsuccessful diversion
&lt;/h4&gt;

&lt;p&gt;The instructions, &lt;code&gt;filesharing.howto.txt&lt;/code&gt;, were a guide on how to set-up a NFS on a separate Mac OS X. I do have a Mac, but it made sense to me (and would be far more convenient) to set up the NFS on the Linux machine which Previous was running on.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--vkxocNZ7--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/i/vl8q4szy4eh203fg0ous.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--vkxocNZ7--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/i/vl8q4szy4eh203fg0ous.png" alt="Desired NFS network" width="477" height="475"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;A desired alternative network solution - a bit more elegant.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;However, after a few hours of configuring NFS server options on my Linux machine and combing through the NeXTSTEP forums for guides, I was stuck, with no file transfers to show for it. NeXT simply could not see the NFS server on the same machine.&lt;/p&gt;

&lt;p&gt;I still &lt;em&gt;think&lt;/em&gt; this should be possible, but the problem could be in Previous, or network emulation, or in connecting a NFS client to a server running on the same machine - honestly, there were too many paths for me to go down to even identify an obvious next option, so I sighed, admitted defeat, and went back to the guide.&lt;/p&gt;

&lt;h4&gt;
  
  
  Getting this going
&lt;/h4&gt;

&lt;p&gt;After following the instructions that came with Previous, I was able to connect to my Mac from Previous in under 10 minutes. Ha! This was helped along by the fact that NeXTSTEP actually has inbuilt utilities to connect to NFS servers:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--nWXit40b--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/i/vksgy6rzs0vn77af3zxz.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--nWXit40b--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/i/vksgy6rzs0vn77af3zxz.png" alt="NFS utilities" width="800" height="647"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Pictured: functional and friendly utilities.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;And I was able to happily create a textfile on my Mac and access it via NeXT - a process that felt very similar to &lt;a href="https://bulbapedia.bulbagarden.net/wiki/Trade#Trading_between_game_generations"&gt;inter-generational trading in Pokémon&lt;/a&gt;, if we're to be honest.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--fhOM4R3Q--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/i/j82d759r61tq71xa9j1f.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--fhOM4R3Q--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/i/j82d759r61tq71xa9j1f.png" alt="Functioning NFS system" width="800" height="604"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;It's slow as hell, but functional.&lt;/p&gt;

&lt;h2&gt;
  
  
  What next?
&lt;/h2&gt;

&lt;p&gt;Well, there we are: I can now get files into NeXTSTEP quickly and easily through a fileshare. In particular, this opens the door to accessing the source code we need to install Python.&lt;/p&gt;




&lt;h2&gt;
  
  
  Series review
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Where have we got to?
&lt;/h3&gt;

&lt;p&gt;At this point we have:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Compiled Previous.&lt;/li&gt;
&lt;li&gt;Found pre-built images of NeXTSTEP. &lt;/li&gt;
&lt;li&gt;Successfully mounted and launched an image of &lt;em&gt;NeXTSTEP 3.3&lt;/em&gt;.&lt;/li&gt;
&lt;li&gt;Set up a fileshare so we can easily get files onto the system.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  How did we get here?
&lt;/h3&gt;

&lt;p&gt;In this sesh we:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Connected Previous to the local network, using the &lt;a href="https://github.com/probonopd/previous/blob/master/networking.howto.txt"&gt;provided instructions&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;Set up a NFS server on a Mac and then connected to it from Previous, again using the &lt;a href="https://github.com/probonopd/previous/blob/master/filesharing.howto.txt"&gt;provided instructions&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  What could we explore further?
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;How much &lt;em&gt;can&lt;/em&gt; we connect a NeXT machine to the internet? Can we get a mid-90s era web browser installed and see what's accessible?

&lt;ul&gt;
&lt;li&gt;Seems there's &lt;a href="http://www.classiccmp.org/pipermail/cctalk/2015-November/015285.html"&gt;some interest&lt;/a&gt; in this...&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;li&gt;I never did give the virtual floppy/CD/HD approach much of a go - the latter would be a great activity with lots to learn about images, mounting, and filesystems.&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>python</category>
      <category>machinelearning</category>
      <category>nextjs</category>
      <category>retro</category>
    </item>
    <item>
      <title>Abandonware and You: Installing Yesterday's Technology of Tomorrow, Today</title>
      <dc:creator>goyder</dc:creator>
      <pubDate>Thu, 04 Feb 2021 13:03:53 +0000</pubDate>
      <link>https://dev.to/goyder/abandonware-and-you-installing-yesterday-s-technology-of-tomorrow-today-23ah</link>
      <guid>https://dev.to/goyder/abandonware-and-you-installing-yesterday-s-technology-of-tomorrow-today-23ah</guid>
      <description>&lt;p&gt;In this series, I'm documenting my experiences with attempting to write and execute a machine learning program (&lt;em&gt;any&lt;/em&gt; machine learning program) in Python 1.6 on a NeXT hardware emulator running NeXTSTEP.&lt;/p&gt;

&lt;p&gt;As outlined in the previous article, I've managed to locate, compile and launch the emulator for the NeXT hardware, charmingly named &lt;a href="http://previous.alternative-system.com/"&gt;Previous&lt;/a&gt;. &lt;/p&gt;

&lt;p&gt;At this point, it's a bit like the point where you've bought and assembled your PC but not yet installed the operating system (OS). What and how are we going to install on this fresh machine?&lt;/p&gt;

&lt;h2&gt;
  
  
  Goal
&lt;/h2&gt;

&lt;p&gt;The high level goal here is to get some instance of NeXTSTEP up and running on Previous. &lt;/p&gt;

&lt;p&gt;That &lt;em&gt;sounds&lt;/em&gt; relatively straightforward - Previous is, after all, a emulator designed to run NeXTSTEP, so it shouldn't be that hard, should it? &lt;/p&gt;

&lt;p&gt;We'll find out.&lt;/p&gt;

&lt;h2&gt;
  
  
  Challenges and decisions
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Defining hardware vs software and the little inbetween
&lt;/h3&gt;

&lt;p&gt;The first thing I found I had to get clear in my mind was what Previous &lt;em&gt;was&lt;/em&gt;, and what it &lt;em&gt;wasn't&lt;/em&gt;. This was a little fuzzy because of what you get when you first run Previous.&lt;/p&gt;

&lt;p&gt;When you boot Previous with the absolute default settings, you are greeted with a very limited command-line interpreter - is this the boot-up of NeXTSTEP? Is this our OS?&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--YCqzQt3i--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/i/wzc69thburw0cjikvk9j.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--YCqzQt3i--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/i/wzc69thburw0cjikvk9j.png" alt="Alt Text" width="800" height="647"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Nah, it turns out - this is the "&lt;a href="http://n-1.nl/next/hardware/info/rom-monitor.html"&gt;ROM Monitor&lt;/a&gt;". &lt;/p&gt;

&lt;p&gt;This is essentially the &lt;a href="https://en.wikipedia.org/wiki/BIOS"&gt;BIOS&lt;/a&gt; of the device. This is best described as &lt;em&gt;firmware&lt;/em&gt; - low level software that talks close to the hardware. We can use this to boot &lt;em&gt;into&lt;/em&gt; our OS.&lt;/p&gt;

&lt;p&gt;Thus, we have:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Previous emulating the hardware, &lt;/li&gt;
&lt;li&gt;Previous running the ROM monitor firmware,&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;But we are missing an OS to run on top of these.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--QEI1WpkD--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/i/c1p8pw153mxp8l2umpd5.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--QEI1WpkD--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/i/c1p8pw153mxp8l2umpd5.png" alt="Alt Text" width="325" height="310"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  How do we get an Operating System?
&lt;/h3&gt;

&lt;p&gt;The first question one might have is "where do you get 30 year old operating systems?", and the answer is, unsurprisingly, "the internet". &lt;/p&gt;

&lt;p&gt;NeXTSTEP comes under the broad banner of &lt;a href="https://en.wikipedia.org/wiki/Abandonware"&gt;"abandonware"&lt;/a&gt; - software that is ignored by its owner and manufacturer (the fact that NeXT went defunct 24 years ago may explain this). Crucially, this means that this software will often be archived and freely shared online, which is exactly the case here. &lt;/p&gt;

&lt;p&gt;I found a few good options to choose from, including &lt;a href="http://www.shawcomputing.net/resources/next/hardware/boot_floppies/boot_floppies.html"&gt;a source that provides boot floppies&lt;/a&gt; and &lt;a href="https://winworldpc.com/product/nextstep/3x"&gt;another source that provides pre-built hard-disk images&lt;/a&gt;. In both instances, we have all kinds of versions of NeXTSTEP to choose.&lt;/p&gt;

&lt;h3&gt;
  
  
  What version of NeXTSTEP to install?
&lt;/h3&gt;

&lt;p&gt;I've been describing "NeXTSTEP" as if it was a single OS, but the fact is that it can refer to many different versions, from the first public release of 1988 (&lt;em&gt;NeXTSTEP 0.8&lt;/em&gt; - not even 1.0!) to the final version from 1997 (&lt;em&gt;OPENSTEP 4.2&lt;/em&gt;). &lt;/p&gt;

&lt;p&gt;A &lt;em&gt;huge&lt;/em&gt; amount of development went on during this time, and hardware and software compatibility varied, so picking the right version is important.&lt;/p&gt;

&lt;p&gt;After doing a bit of digging, I decided on &lt;em&gt;NeXTSTEP 3.3&lt;/em&gt; to start. This was the last version under the "NeXTSTEP" name, and was apparently quite mature and trusted - a lot of the references I see in software and READMEs are to this version.&lt;/p&gt;

&lt;h3&gt;
  
  
  From scratch or pre-built?
&lt;/h3&gt;

&lt;p&gt;Okay, if we're going to run with &lt;em&gt;NeXTSTEP 3.3&lt;/em&gt;, how do we want to go about doing so? We actually have a couple of options:&lt;/p&gt;

&lt;p&gt;First, we can get copies of the original boot floppies and do a completely fresh install on a totally empty "hard-drive" using Previous - this is akin to putting in a fresh, blank hard-drive into your new computer and doing a fresh install from the system CD/USB.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--0iqvJnVJ--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/i/bqvjg5aycqwidupodvpd.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--0iqvJnVJ--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/i/bqvjg5aycqwidupodvpd.png" alt="Alt Text" width="620" height="913"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Secondly, we can download an image of a hard-drive that already has NeXTSTEP installed and boot that - a bit like putting in a hard-drive from your old machine with Windows already installed.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--uSzsM0j9--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/i/2tyez2om46dy97fyxb5p.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--uSzsM0j9--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/i/2tyez2om46dy97fyxb5p.png" alt="Alt Text" width="330" height="389"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Ultimately, I went for the latter option - while I found &lt;a href="http://www.nextcomputers.org/forums/index.php?topic=4254.0"&gt;some guides&lt;/a&gt; for installing from scratch, this path seemed fraught with challenges that &lt;em&gt;may&lt;/em&gt; be NeXT and &lt;em&gt;may&lt;/em&gt; be the emulation - a recipe for disaster. Let's take the "safe way" and see how that goes.&lt;/p&gt;

&lt;h3&gt;
  
  
  Download and mount
&lt;/h3&gt;

&lt;p&gt;I picked the &lt;a href="https://winworldpc.com/download/0c6a74c3-8e53-3f11-c3a4-c2a90f7054ef"&gt;"NeXTSTEP 3.3 HD"&lt;/a&gt; download from WinWorld and away I went. (The fact that there have only been 72 downloads of these images in total via WinWorld makes me think I'm down a pretty esoteric path.) &lt;/p&gt;

&lt;p&gt;Inside the compressed file, you'll find a file called &lt;code&gt;NS33_2GB.dd&lt;/code&gt; - this is a pre-built image with NeXTSTEP 3.3 installed to the point where it's ready for you, the user, to start configuring and using it. It also includes a robust README that explains in very welcome detail exactly how to configure Previous to get the image up and going (neatly moving past the confusing ROM monitor from above).&lt;/p&gt;

&lt;p&gt;When I followed those instructions, the boot went smoothly and I was greeting with a config screen:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--BjCQ9jRY--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/i/qttduzi7ai806pcz7vc8.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--BjCQ9jRY--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/i/qttduzi7ai806pcz7vc8.png" alt="Alt Text" width="800" height="647"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;And, following the initial configuration and booting up the Mailbox app, I was greeting with the "famous" welcome email from the man himself, Steve Jobs:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--rIposE72--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/i/9w29gu3gly0j8o3n0c6t.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--rIposE72--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/i/9w29gu3gly0j8o3n0c6t.png" alt="Alt Text" width="800" height="647"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Looking good - NeXTSTEP 3.3 is up and running! Now to start using it...&lt;/p&gt;

&lt;h2&gt;
  
  
  What next?
&lt;/h2&gt;

&lt;p&gt;With a fresh install of NeXTSTEP, we now have to figure out how to &lt;em&gt;use&lt;/em&gt; it.&lt;/p&gt;




&lt;h2&gt;
  
  
  Series review
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Where have we got to?
&lt;/h3&gt;

&lt;p&gt;At this point we have:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Compiled Previous.&lt;/li&gt;
&lt;li&gt;Found pre-built images of NeXTSTEP. &lt;/li&gt;
&lt;li&gt;Successfully mounted and launched an image of NeXTSTEP 3.3.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  What did we find?
&lt;/h3&gt;

&lt;p&gt;In this sesh, we:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Found a good source of &lt;a href="http://www.shawcomputing.net/resources/next/hardware/boot_floppies/boot_floppies.html"&gt;NeXTSTEP boot floppies&lt;/a&gt;

&lt;ul&gt;
&lt;li&gt;Importantly, we also found a guide to &lt;a href="http://www.nextcomputers.org/forums/index.php?topic=4254.0"&gt;install those boot floppies under Previous.&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;li&gt;Found a good source of &lt;a href="https://winworldpc.com/product/nextstep/3x"&gt;NeXTSTEP OS and resource images&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  What could we explore further?
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Other versions of NeXTSTEP - how different is 0.8? What about 4.2?&lt;/li&gt;
&lt;li&gt;Building from boot floppies - how hard would it actually be to a fresh install from scratch if we wanted to?&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>python</category>
      <category>machinelearning</category>
      <category>nextjs</category>
      <category>retro</category>
    </item>
    <item>
      <title>Moving forward with Previous</title>
      <dc:creator>goyder</dc:creator>
      <pubDate>Thu, 28 Jan 2021 12:55:29 +0000</pubDate>
      <link>https://dev.to/goyder/moving-forward-with-previous-3bc4</link>
      <guid>https://dev.to/goyder/moving-forward-with-previous-3bc4</guid>
      <description>&lt;p&gt;In this series, I'm documenting my experiences with attempting to write and execute a machine learning program in Python 1.6 on a NeXT hardware emulator running NeXTSTEP. &lt;/p&gt;

&lt;p&gt;As I outlined in the first article, my motivations carrying out this project are basically:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Curiosity&lt;/li&gt;
&lt;li&gt;Interest in picking up technical skills&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Today's article is going to be more focused on the latter motivation, as I figure out the best way to run the 30-something-year-old NeXTSTEP operating system on my current machine (a Linux desktop with 9-ish year old hardware, for reference).&lt;/p&gt;

&lt;h2&gt;
  
  
  Goal
&lt;/h2&gt;

&lt;p&gt;Our purpose here is relatively straightforward: let's explore our options for &lt;a href="https://en.wikipedia.org/wiki/Emulator"&gt;emulation platforms&lt;/a&gt; to let us run NeXTSTEP, pick one, and get it going.&lt;/p&gt;

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

&lt;p&gt;&lt;em&gt;(I'd love to actually carry all of this out via an actual NeXT machine, but their age, importance, and the fact that not very many were actually built or sold means that it's very difficult to find one.)&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Challenges and decisions
&lt;/h2&gt;

&lt;h3&gt;
  
  
  What platform to use?
&lt;/h3&gt;

&lt;p&gt;One of the key challenges I encountered straight off the bat is trying to figure out what hardware I want to emulate. Two options jump out at me:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;First, there is a emulator available that replicates the NeXT machines, charmingly called &lt;a href="http://previous.alternative-system.com/"&gt;"Previous"&lt;/a&gt;. (Points for that.)

&lt;ul&gt;
&lt;li&gt;I'd actually very briefly played around with this years ago but gave up quickly as I didn't really know what I was doing, and the learning curve was significant.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;li&gt;The other option is to run one of the later versions of NeXTSTEP that were made available on non-NeXT hardware. (These versions were known as &lt;a href="https://winworldpc.com/product/nextstep/4x"&gt;OPENSTEP&lt;/a&gt;, of course not to be confused with the API spec &lt;a href="https://en.wikipedia.org/wiki/OpenStep"&gt;OpenStep&lt;/a&gt;) Notably, "non-NeXT hardware" means x86 hardware, meaning it can be run under &lt;a href="https://www.virtualbox.org/"&gt;VirtualBox&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The documentation and guides in both cases seem... dicey. I daresay there'll be interesting debugging regardless. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--U3_vZCua--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/7mctujoup5bk6x1zofy4.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--U3_vZCua--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/7mctujoup5bk6x1zofy4.gif" alt="File footage"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Depicted: debugging.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Reviewing both of the options, I decided to go with Previous because:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;It's the most true to the spirit of this project (it's the closest to authentic hardware, and the non-Intel chipset it's emulating makes this even more pointlessly quixotic!)&lt;/li&gt;
&lt;li&gt;The VirtualBox option is only available for later versions of NeXTSTEP which were ported to x86 chipsets.&lt;/li&gt;
&lt;li&gt;It &lt;em&gt;does&lt;/em&gt; seem to have the most support, features, and, most importantly, &lt;a href="http://www.nextcomputers.org/forums"&gt;active forum community&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Seriously, an active and devoted forum cannot be understated for its value in troubleshooting.&lt;/p&gt;

&lt;h3&gt;
  
  
  Getting Previous installed
&lt;/h3&gt;

&lt;p&gt;Having decided on Previous, I jumped into figuring out how to get it running on my machine. Off the bat, I was a touch nervous, primarily because the &lt;a href="http://previous.alternative-system.com/index.php/news"&gt;latest news article on the homepage&lt;/a&gt; was from 2016, and that started with &lt;em&gt;"very long time since last updated this homepage..."&lt;/em&gt;. This didn't scream "strongly supported and frequently updated" to me, but at least they're at a 1.X version.&lt;/p&gt;

&lt;p&gt;With &lt;a href="http://previous.alternative-system.com/index.php/download"&gt;no prebuilt builds available to download&lt;/a&gt;, I set about following the instructions on how to &lt;a href="http://previous.alternative-system.com/index.php/build"&gt;build from source&lt;/a&gt;. Again, these instructions are old, but they do look technically possible, so adapted them into script and give it a go. (The script is available below.)&lt;/p&gt;

&lt;p&gt;I type &lt;code&gt;./install.sh&lt;/code&gt; into the shell and begin a few minutes of tense waiting while the compilation process proceeds... &lt;/p&gt;

&lt;p&gt;Watching intensely, no significant errors crop up...&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Stay on target... stay on target...&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;And bang. The build executes flawlessly first time, to my amazement. I launch the still-warm, freshly built executable and am greeted with a blank launch menu:&lt;/p&gt;

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

&lt;p&gt;I've succeeded. Now I just have to explore what it is I've succeeded in doing.&lt;/p&gt;

&lt;h2&gt;
  
  
  What next?
&lt;/h2&gt;

&lt;p&gt;Okay, it seems like we have a hardware emulator going - now we have to get some software to run on it. &lt;/p&gt;

&lt;p&gt;Stay tuned.&lt;/p&gt;




&lt;h2&gt;
  
  
  Series review
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Where have we got to?
&lt;/h3&gt;

&lt;p&gt;At this point, we have:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Compiled Previous. &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;(&lt;em&gt;As this project proceeds, this trail of breadcrumbs will be more useful.&lt;/em&gt;) &lt;/p&gt;

&lt;h3&gt;
  
  
  What did we find?
&lt;/h3&gt;

&lt;p&gt;In this sesh, we: &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Found the &lt;a href="http://previous.alternative-system.com/"&gt;Previous website&lt;/a&gt;, and&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://gist.github.com/goyder/003704dc37b19928ddb1988ee31ee0bc"&gt;Adapted the install script&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  What could we explore further?
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Other emulation options - how well do the Virtualbox emulators work? Are there significant differences?&lt;/li&gt;
&lt;li&gt;What's the feasibility of the online implementations? If I write a ML algorithm in one of the &lt;a href="https://virtuallyfun.com/wordpress/2016/02/07/nextstep-in-your-browser/"&gt;Docker-run systems&lt;/a&gt; have I created the very worst containerised ML solution possible?&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>python</category>
      <category>machinelearning</category>
      <category>retro</category>
    </item>
    <item>
      <title>Writing machine learning code on 20 year old software on 30 year old hardware</title>
      <dc:creator>goyder</dc:creator>
      <pubDate>Mon, 25 Jan 2021 14:23:38 +0000</pubDate>
      <link>https://dev.to/goyder/writing-machine-learning-code-on-20-year-old-software-on-30-year-old-hardware-2c75</link>
      <guid>https://dev.to/goyder/writing-machine-learning-code-on-20-year-old-software-on-30-year-old-hardware-2c75</guid>
      <description>&lt;p&gt;In this series, I'm going to document my experiences with attempting to write and execute a machine learning program in Python 1.6 on a NeXT hardware emulator running NeXTSTEP. &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Two points up front&lt;/strong&gt;: &lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;This is a pointless, silly thing to do.&lt;/li&gt;
&lt;li&gt;I hope you end up enjoying the ride as much as I do.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;There's a good chance that the challenge outlined may be mostly nonsensical to you, and it's not clear why it's a silly thing to do. &lt;/p&gt;

&lt;p&gt;Let me paint the broad strokes.&lt;/p&gt;

&lt;h2&gt;
  
  
  The goal
&lt;/h2&gt;

&lt;p&gt;The challenge I've outlined has three components:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;em&gt;Write&lt;/em&gt; and &lt;em&gt;execute&lt;/em&gt; a machine learning program;&lt;/li&gt;
&lt;li&gt;In &lt;em&gt;Python 1.6&lt;/em&gt;;&lt;/li&gt;
&lt;li&gt;Compiled from source in &lt;em&gt;NeXTSTEP&lt;/em&gt;.&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Machine learning
&lt;/h3&gt;

&lt;p&gt;To be clear, the easiest part of this challenge be the &lt;em&gt;machine learning&lt;/em&gt; component. I'm very much of the school of thought that the term 'machine learning' covers a broad church which includes the foundational but insanely useful algorithms like linear and logistic regression. These are relatively simple algorithms to implement in any programming environment. &lt;/p&gt;

&lt;p&gt;Call this part of the challenge an excuse to frame this whole exercise as "an exploration in the worst possible way in writing ML". &lt;/p&gt;

&lt;h3&gt;
  
  
  Python 1.6
&lt;/h3&gt;

&lt;p&gt;&lt;em&gt;Python 1.6&lt;/em&gt; - What's interesting about that? Well, Python is a wonderful, fully-featured and robust language - and it's certainly &lt;em&gt;my&lt;/em&gt; go-to language, especially in the realm of data analysis and machine learning. Personally, I began using Python in its venerable Python 2.7 version, before making the jump to various 3.x versions full-time over the last five years.&lt;/p&gt;

&lt;p&gt;But Python is a surprisingly old language, with its first release being in 1991, or fully 30 years ago! &lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--LPnhPPh3--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/i/phe1z6u5liash471st11.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--LPnhPPh3--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/i/phe1z6u5liash471st11.png" alt="Not an official text" width="406" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Warning: may not be an official Python document.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;The language has made incredible leaps and bounds in power and functionality even the limited time I've been using it. I'm curious to rewind the clock 21 years to its 1.6 release and explore the idiosyncracies of these early releases.&lt;/p&gt;

&lt;h3&gt;
  
  
  NeXT and NeXTSTEP
&lt;/h3&gt;

&lt;p&gt;Finally, perhaps the most challenging to explain: NeXT and NeXTSTEP. &lt;a href="https://en.wikipedia.org/wiki/NeXT"&gt;NeXT&lt;/a&gt; was a hardware and software company that existed in the mid 1980s-90s; their core software product was an operating system (OS) called NeXTSTEP. There is &lt;em&gt;a lot&lt;/em&gt; to unpack around NeXT, but there are three main reasons why I'm intrigued:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;NeXT was the baby of Steve Jobs, and is most often associated with his "second act" between being cast out of Apple and eventually rejoining; it's a period of his life often brushed over, perhaps because of just how poorly he managed NeXT. This is charitably referred to as the time when he made mistakes and learnt to be a leader. NeXT has a long and dramatic history which I cannot pretend to do justice; &lt;a href="https://www.amazon.com/Steve-Jobs-Next-Big-Thing/dp/0689121350"&gt;it's definitely worth exploring&lt;/a&gt; if you're interested in computing lore.&lt;/li&gt;
&lt;li&gt;Drama aside, the NeXTSTEP OS was genuinely innovative for its time; it was an impressive technical achievement and regarded as a powerful development environment. NeXT machines and NeXTSTEP were associated with many technological touchstones of the 90s: &lt;a href="https://www.forbes.com/sites/quora/2016/09/01/why-john-carmack-chose-next-for-developing-doom-and-other-favorites/?sh=6c97663614d1"&gt;Doom&lt;/a&gt; and the &lt;a href="https://collection.sciencemuseumgroup.org.uk/objects/co8232360/next-cube-computer-1990-personal-computer"&gt;World Wide Web&lt;/a&gt; (pick your preferred order of importance) were both developed on NeXT products.&lt;/li&gt;
&lt;li&gt;Finally, there's a good chance you still use a descendent of NeXTSTEP. NeXTSTEP eventually formed the basis of Apple's Mac OS X, and consequently MacOS, iOS, iPadOS, etc. NeXTSTEP is history, but its lineage goes on.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--mnr-YoSI--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/i/gfpxcw6hracka932a8f7.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--mnr-YoSI--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/i/gfpxcw6hracka932a8f7.png" alt="NeXT Logo" width="300" height="300"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Also, NeXT had a really neat logo (which of course has an associated story).&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  But... why?
&lt;/h2&gt;

&lt;p&gt;Why would I burn my limited free time on a silly project like this? That's a very legitimate question. There are two primary reasons.&lt;/p&gt;

&lt;p&gt;Pragmatically, &lt;strong&gt;technological broadening and skill development&lt;/strong&gt; is a common and welcome outcome. I always find that carrying out a silly challenge like this introduces a bunch of unexpected technical challenges, and solving them broadens my horizons greatly. With this challenge, I expected I'm going to learn a good amount around compiling programs from scratch and manipulating disk images -- and who knows what else.  &lt;/p&gt;

&lt;p&gt;To quote &lt;a href="https://mitpress.mit.edu/books/men-machines-and-modern-times-50th-anniversary-edition"&gt;Elting E. Morison&lt;/a&gt;, we should consider "the story of the three princes of Serendip who set out to find some interesting object on a journey through their realm. They did not find the particular object of their search, but along the way they discovered many new things simply because they were looking for something." In other words - set an arbitrary goal, accomplish other things along the way.&lt;/p&gt;

&lt;p&gt;But my main driver is not personal development - it's &lt;strong&gt;curiosity&lt;/strong&gt;. Here is an opportunity to dive backwards in time to explore and use tools and portions of "history". I want to see what was saved and what was lost. &lt;/p&gt;

&lt;p&gt;Seeing systems and objects in their "prototypical" forms has long been a passion of mine; as a child, I spent a lot more time exploring the &lt;a href="http://info.sonicretro.org/Sonic_the_Hedgehog_2_(Simon_Wai_prototype)"&gt;Sonic the Hedgehog 2 prototype community&lt;/a&gt; than I ever did playing the completed game!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--mjRLNsG7--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/i/01jb7rggb8wnlndkpqph.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--mjRLNsG7--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/i/01jb7rggb8wnlndkpqph.png" alt="Sonic 2 Beta Titlescreen" width="320" height="224"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;P.S., did you know they just recently found a &lt;a href="https://tcrf.net/Proto:Sonic_the_Hedgehog_(Genesis)"&gt;Sonic 1 prototype&lt;/a&gt; after two decades of searching?&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Future articles and next steps
&lt;/h2&gt;

&lt;p&gt;(Pardon the pun.) &lt;/p&gt;

&lt;p&gt;Future articles are going to cover the numerous steps on the road to achieve the goals set out above.&lt;/p&gt;

&lt;p&gt;Because this is going to knit together a lot of publically available resources, future articles will include some main sections:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Challenges and decisions&lt;/strong&gt;, covering some of the surprising bumps in the road I assume I will encounter,&lt;/li&gt;
&lt;li&gt;A &lt;strong&gt;series review&lt;/strong&gt;, covering what's been done so far,&lt;/li&gt;
&lt;li&gt;A &lt;strong&gt;summary of findings&lt;/strong&gt; including all of the key links and resources I've hit upon, and;&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Things to explore further&lt;/strong&gt;, noting things that I was curious about but didn't dig into more.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Hopefully these will help form useful resources for any folks who want to dig around in the same sandpit as me.&lt;/p&gt;

&lt;p&gt;See you next time!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--KJ_m40KD--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/i/oxw4voa5wyyr0h1u3lg7.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--KJ_m40KD--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/i/oxw4voa5wyyr0h1u3lg7.png" alt="Waving hand" width="120" height="120"&gt;&lt;/a&gt;&lt;/p&gt;

</description>
      <category>python</category>
      <category>nextjs</category>
      <category>machinelearning</category>
      <category>justforfun</category>
    </item>
    <item>
      <title>Automatic Reporting in Python - Part 3: Packaging It Up</title>
      <dc:creator>goyder</dc:creator>
      <pubDate>Sat, 29 Dec 2018 15:08:22 +0000</pubDate>
      <link>https://dev.to/goyder/automatic-reporting-in-python---part-3-packaging-it-up-1185</link>
      <guid>https://dev.to/goyder/automatic-reporting-in-python---part-3-packaging-it-up-1185</guid>
      <description>&lt;p&gt;As outlined in my previous posts (&lt;a href="https://dev.to/goyder/automatic-reporting-in-python---part-1-from-planning-to-hello-world-32n1"&gt;Part I&lt;/a&gt; and &lt;a href="https://dev.to/goyder/automatic-reporting-in-python---part-2-from-hello-world-to-real-insights-8p3"&gt;Part II&lt;/a&gt; available on this fine website), the goal of this project is to make an automatic reporting tool.&lt;/p&gt;

&lt;p&gt;In this series of guides, the outcome I'm shooting for is a single HTML page that allows me to interrogate and compare the output of machine learning models.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2F9nu22hxwy0qhyhh51bgp.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2F9nu22hxwy0qhyhh51bgp.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;At the &lt;a href="https://dev.to/goyder/automatic-reporting-in-python---part-2-from-hello-world-to-real-insights-8p3"&gt;conclusion of the previous tutorial&lt;/a&gt;, the reporting tool was actually showing some genuine use! It could accept a number of &lt;code&gt;.csv&lt;/code&gt; summaries of machine learning summary files and output a single &lt;code&gt;.html&lt;/code&gt; page that presented the information is a form that was... functional.&lt;/p&gt;

&lt;p&gt;There three main features that I'd like to add to the tool for now:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The report looks incredibly dull. Readability counts! We need to improve the &lt;em&gt;&lt;code&gt;a e s t h e t i c s&lt;/code&gt;&lt;/em&gt; of the report.&lt;/li&gt;
&lt;li&gt;It's hard to dig into the tables - currently it's just 100 rows presented with no tools to search. Some basic search functionality would be grouse.&lt;/li&gt;
&lt;li&gt;The datasets that we want to run the report with are currently hard-coded into the script. This needs to be split out to make the tool slightly more flexible.&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  A brief comment
&lt;/h3&gt;

&lt;p&gt;Compared to the previous posts, this post is much more of an exploratory, learning experience - this post represents a neophyte's attempt to build a working tool, rather than a beautiful depiction of all that is possible. If you can see a much better approach for anything listed in this post, please feel free to share it with myself and all the other readers!&lt;/p&gt;

&lt;p&gt;But without further ado, let's take a crack at improving the aesthetics.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step Seven - Improving the Aesthetics
&lt;/h2&gt;

&lt;p&gt;Those of you with an understanding of HTML pages might know what comes next: Cascading Style Sheets, known more commonly as CSS.&lt;/p&gt;

&lt;h3&gt;
  
  
  Taking your first steps down a path
&lt;/h3&gt;

&lt;p&gt;As indicated above, the challenge in the context of this tutorial is that this is an &lt;em&gt;enormous&lt;/em&gt; field that I personally am not actually particularly well-versed in, having only dabbled and hacked in this space. However, I &lt;em&gt;am&lt;/em&gt; familiar with learning new things.&lt;/p&gt;

&lt;p&gt;So, if this is your first real introduction to CSS, let me take you down the same path I would recommend in learning any new tech:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Do some background reading on the fundamentals of CSS. Hit up &lt;a href="https://en.wikipedia.org/wiki/Cascading_Style_Sheets" rel="noopener noreferrer"&gt;Wikipedia&lt;/a&gt;. If you're keen, hit up &lt;a href="https://www.w3.org/TR/2011/REC-CSS2-20110607/intro.html#html-tutorial" rel="noopener noreferrer"&gt;the CSS standard!&lt;/a&gt; Never be afraid to Google "simple introduction to [topic]." &lt;/li&gt;
&lt;li&gt;As you read and explore, make note of potential good resources of future information. I would encourage everyone to take a look for an &lt;a href="https://github.com/sindresorhus/awesome" rel="noopener noreferrer"&gt;"Awesome List"&lt;/a&gt; relevant to your topic - and in case, the Awesome CSS List is &lt;a href="https://github.com/awesome-css-group/awesome-css#readme" rel="noopener noreferrer"&gt;here&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;With a basic understanding of what the hell is going on under your belt, play around to your heart's content with local files and implement as much as you can in local scratch files. In this case, create small (or large?) HTML pages and figure out how to structure the CSS neatly. I find this really helps to get to know some of the practical realities and challenges of working with the language before I start diving into frameworks.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Fqd69w9m656yttbcdtupa.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Fqd69w9m656yttbcdtupa.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Being a bit more mercenary
&lt;/h3&gt;

&lt;p&gt;For self improvement, I find nothing beats spending the time working up solutions from scratch. Of course, if you're trying to hack together a solution for a business need, it may not behoove you to spend hours and days coming up with an elegant CSS framework from the beginning.&lt;/p&gt;

&lt;p&gt;Instead, we may like to quickly jump off of &lt;em&gt;someone else's&lt;/em&gt; CSS framework. Fortunately, there are a number of these available, with a great number of them focusing on being 'minimal'. A quick &lt;a href="https://www.google.com/search?q=minimal+css+framework" rel="noopener noreferrer"&gt;Google search&lt;/a&gt; should get you started down this path. &lt;/p&gt;

&lt;h3&gt;
  
  
  Integration
&lt;/h3&gt;

&lt;p&gt;How is the CSS file to be integrated our report? There's a few options that are typically at play:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;em&gt;Download the CSS file and keep it as external file.&lt;/em&gt; The advantage is that we have our &lt;code&gt;.html&lt;/code&gt; and &lt;code&gt;.css&lt;/code&gt; files neatly separated, and we have full control over both; the far more significant disadvantage is that now if we want to move our report around, we have to drag a bunch of &lt;code&gt;.css&lt;/code&gt; files around with it.&lt;/li&gt;
&lt;li&gt;
&lt;em&gt;Use a &lt;a href="https://www.webopedia.com/TERM/C/CDN.html" rel="noopener noreferrer"&gt;Content Delivery Network (CDN)&lt;/a&gt; copy of the CSS file&lt;/em&gt;. Most frameworks will offer a CDN link  for their file: this is essentially a link to an efficient, readily available copy of the data. The advantage is that you can get a CSS up and going in your page just by dropping a single link in the &lt;code&gt;&amp;lt;head&amp;gt;&lt;/code&gt; section of your &lt;code&gt;.html&lt;/code&gt;, no mussin' and fussin' with local files. The disadvantage is that you don't have control of the file, and an internet connection is required.&lt;/li&gt;
&lt;li&gt;A slightly more complex option is to have local copies of the CSS file, and then write them &lt;em&gt;into&lt;/em&gt; the &lt;code&gt;.html&lt;/code&gt; file. This could probably be done relatively easily and sustainibly if we got clever with our templating. The advantage is that we'd have our report in a single file, &lt;em&gt;and&lt;/em&gt; it wouldn't require an internet connection to use; the disadvantage is that it is going to require a bit more effort to get set up. (This is commonly used approach when creating standalone versions of interactive pages. Write a &lt;a href=""&gt;Jupyter Notebook&lt;/a&gt; to &lt;code&gt;.html&lt;/code&gt;, inspect the file, and you'll find all the CSS and JavaScript magic packaged up in the &lt;code&gt;&amp;lt;head&amp;gt;&lt;/code&gt; section.)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;At this early stage of prototyping, I prefer to use CDNs if possible. The advantage of being able to swap CSS frameworks just by changing a single line of code and not having to bother with local files is worth the cost of not being able to play with and edit the framework. Optimisation (in the form of being able to automatically integrate the CSS into the &lt;code&gt;.html&lt;/code&gt; file) can come a little later.&lt;/p&gt;

&lt;p&gt;To start with, I'm going to use &lt;a href="https://milligram.io/" rel="noopener noreferrer"&gt;Milligram&lt;/a&gt;, a lightweight little framework. To get started using the CDN method, I simply follow the provided instructions to integrate into the CDN. Under our &lt;code&gt;templates/report.html&lt;/code&gt; file, I'll add the requisite links into the &lt;code&gt;&amp;lt;head&amp;gt;&lt;/code&gt; section:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;report.html&lt;/code&gt;&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;!DOCTYPE html&amp;gt;
&amp;lt;html lang="en"&amp;gt;
&amp;lt;head&amp;gt;
    &amp;lt;meta charset="UTF-8"&amp;gt;
    &amp;lt;title&amp;gt;{{ title }}&amp;lt;/title&amp;gt;
    &amp;lt;link rel="stylesheet" href="//fonts.googleapis.com/css?family=Roboto:300,300italic,700,700italic"&amp;gt;
    &amp;lt;link rel="stylesheet" href="//cdn.rawgit.com/necolas/normalize.css/master/normalize.css"&amp;gt;
    &amp;lt;link rel="stylesheet" href="//cdn.rawgit.com/milligram/milligram/master/dist/milligram.min.css"&amp;gt;
&amp;lt;/head&amp;gt;
&amp;lt;!-- body section continues below... --&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And all of a sudden, our plain, early 90's looking webpage has been transformed into something a bit more pleasing to the eye:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Fhiynq39nr7j4vu6yddc9.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Fhiynq39nr7j4vu6yddc9.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;But we note there's something still not quite right here - primarily, why does the page (and the table in particular) always take up the whole width of the window? Why doesn't this look right on mobile? &lt;/p&gt;

&lt;p&gt;It turns out just adding a bunch of &lt;code&gt;.css&lt;/code&gt; files isn't quite enough. We need to make sure the layout of our &lt;code&gt;.html&lt;/code&gt; pages match what's expected by the &lt;code&gt;.css&lt;/code&gt; layout.&lt;/p&gt;

&lt;h3&gt;
  
  
  HTML layouts
&lt;/h3&gt;

&lt;p&gt;Like most topics in this space, the layout of your HTML page is a &lt;em&gt;reasonably&lt;/em&gt; intuitive concept, while simultaneously being a problem that you spend years diving into. What makes it a bit more challenging is that despite there being a number of &lt;a href="https://www.w3schools.com/css/css_website_layout.asp" rel="noopener noreferrer"&gt;somewhat&lt;/a&gt; &lt;a href="https://www.beginnersguidetohtml.com/guides/css/layout/div-tags" rel="noopener noreferrer"&gt;fragmentary&lt;/a&gt; &lt;a href=""&gt;explanations&lt;/a&gt;, I've struggled to find a simple and/or holistic to the field (although &lt;a href="https://openclassrooms.com/en/courses/2479876-build-your-website-with-html5-and-css3/2491630-structuring-your-page" rel="noopener noreferrer"&gt;this explanation&lt;/a&gt; is currently my favourite gentle introduction, and the &lt;a href="https://developer.mozilla.org/en-US/docs/Learn/CSS/CSS_layout/Introduction" rel="noopener noreferrer"&gt;Mozilla guide&lt;/a&gt; appears to be quite thorough). &lt;/p&gt;

&lt;p&gt;For brevity, I'm going to leave most of the further reading to you, the reader (sorry!), and instead focus on what Milligram expects. &lt;/p&gt;

&lt;p&gt;If we &lt;a href="https://developers.google.com/web/tools/chrome-devtools/open" rel="noopener noreferrer"&gt;inspect the code&lt;/a&gt; of the &lt;a href="https://milligram.io/" rel="noopener noreferrer"&gt;Milligram page&lt;/a&gt;, we'll see that within the &lt;code&gt;&amp;lt;body&amp;gt;&lt;/code&gt;, we can see the HTML of the site is structured roughly as:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;body&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;main&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"wrapper"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;header&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"header"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;section&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"container"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;section&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"container"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now, based on some of the readings in the above links, and assuming that this is the structure that Milligram is expecting, we can apply the same structure to our own report, giving us something like the following:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;report.html&lt;/code&gt;&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="cp"&gt;&amp;lt;!DOCTYPE html&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;html&lt;/span&gt; &lt;span class="na"&gt;lang=&lt;/span&gt;&lt;span class="s"&gt;"en"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;head&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;meta&lt;/span&gt; &lt;span class="na"&gt;charset=&lt;/span&gt;&lt;span class="s"&gt;"UTF-8"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="c"&gt;&amp;lt;!-- Note the addition of the viewport! --&amp;gt;&lt;/span&gt; 
    &lt;span class="nt"&gt;&amp;lt;meta&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"viewport"&lt;/span&gt; &lt;span class="na"&gt;content=&lt;/span&gt;&lt;span class="s"&gt;"width=device-width, initial-scale=1.0, minimal-ui"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;title&amp;gt;&lt;/span&gt;{{ title }}&lt;span class="nt"&gt;&amp;lt;/title&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;link&lt;/span&gt; &lt;span class="na"&gt;rel=&lt;/span&gt;&lt;span class="s"&gt;"stylesheet"&lt;/span&gt; &lt;span class="na"&gt;href=&lt;/span&gt;&lt;span class="s"&gt;"//fonts.googleapis.com/css?family=Roboto:300,300italic,700,700italic"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;link&lt;/span&gt; &lt;span class="na"&gt;rel=&lt;/span&gt;&lt;span class="s"&gt;"stylesheet"&lt;/span&gt; &lt;span class="na"&gt;href=&lt;/span&gt;&lt;span class="s"&gt;"//cdn.rawgit.com/necolas/normalize.css/master/normalize.css"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;link&lt;/span&gt; &lt;span class="na"&gt;rel=&lt;/span&gt;&lt;span class="s"&gt;"stylesheet"&lt;/span&gt; &lt;span class="na"&gt;href=&lt;/span&gt;&lt;span class="s"&gt;"//cdn.rawgit.com/milligram/milligram/master/dist/milligram.min.css"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/head&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;body&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;main&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"wrapper"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;header&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"header"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;section&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"container"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
                &lt;span class="nt"&gt;&amp;lt;h1&amp;gt;&lt;/span&gt;{{ title }}&lt;span class="nt"&gt;&amp;lt;/h1&amp;gt;&lt;/span&gt;
                &lt;span class="nt"&gt;&amp;lt;p&amp;gt;&lt;/span&gt;This report was automatically generated.&lt;span class="nt"&gt;&amp;lt;/p&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;/section&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;/header&amp;gt;&lt;/span&gt;
        {% for section in sections %}
        {{ section }}
        {% endfor %}
    &lt;span class="nt"&gt;&amp;lt;/main&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/body&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/html&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;&lt;code&gt;summary_section.html&lt;/code&gt;&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;section&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"container"&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"summary"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;h2&amp;gt;&lt;/span&gt;Quick summary&lt;span class="nt"&gt;&amp;lt;/h2&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;h3&amp;gt;&lt;/span&gt;Accuracy&lt;span class="nt"&gt;&amp;lt;/h3&amp;gt;&lt;/span&gt;
    {% for model_results in model_results_list %}
    &lt;span class="nt"&gt;&amp;lt;p&amp;gt;&amp;lt;em&amp;gt;&lt;/span&gt;{{ model_results.model_name }}&lt;span class="nt"&gt;&amp;lt;/em&amp;gt;&lt;/span&gt; analysed &lt;span class="nt"&gt;&amp;lt;em&amp;gt;&lt;/span&gt;{{ model_results.number_of_images }} image(s)&lt;span class="nt"&gt;&amp;lt;/em&amp;gt;&lt;/span&gt;, achieving an
        accuracy of &lt;span class="nt"&gt;&amp;lt;em&amp;gt;&lt;/span&gt;{{ "{:.2%}".format(model_results.accuracy) }}.&lt;span class="nt"&gt;&amp;lt;/em&amp;gt;&amp;lt;/p&amp;gt;&lt;/span&gt;
    {% endfor %}
    &lt;span class="nt"&gt;&amp;lt;h3&amp;gt;&lt;/span&gt;Trouble spots&lt;span class="nt"&gt;&amp;lt;/h3&amp;gt;&lt;/span&gt;
    {% for model_results in model_results_list %}
    &lt;span class="nt"&gt;&amp;lt;p&amp;gt;&amp;lt;em&amp;gt;&lt;/span&gt;{{ model_results.model_name }}&lt;span class="nt"&gt;&amp;lt;/em&amp;gt;&lt;/span&gt; misidentified &lt;span class="nt"&gt;&amp;lt;em&amp;gt;&lt;/span&gt;{{ model_results.number_misidentified }} image(s)&lt;span class="nt"&gt;&amp;lt;/em&amp;gt;&lt;/span&gt;.&lt;span class="nt"&gt;&amp;lt;/p&amp;gt;&lt;/span&gt;
    {% endfor %}
    &lt;span class="nt"&gt;&amp;lt;p&amp;gt;&amp;lt;em&amp;gt;&lt;/span&gt;{{ number_misidentified }}&lt;span class="nt"&gt;&amp;lt;/em&amp;gt;&lt;/span&gt; misidentified image(s) were common to all models.&lt;span class="nt"&gt;&amp;lt;/p&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/section&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;&lt;code&gt;table_section.html&lt;/code&gt;&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;section&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"container"&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"{{ model }}"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;h2&amp;gt;&lt;/span&gt;{{ model }} - Model Results&lt;span class="nt"&gt;&amp;lt;/h2&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;p&amp;gt;&lt;/span&gt;Results for each image as predicted by model &lt;span class="nt"&gt;&amp;lt;i&amp;gt;&lt;/span&gt;'{{ model }}'&lt;span class="nt"&gt;&amp;lt;/i&amp;gt;&lt;/span&gt;, as captured in file &lt;span class="nt"&gt;&amp;lt;i&amp;gt;&lt;/span&gt;'{{ dataset }}'&lt;span class="nt"&gt;&amp;lt;/i&amp;gt;&lt;/span&gt;.&lt;span class="nt"&gt;&amp;lt;/p&amp;gt;&lt;/span&gt;
    {{ table }}
&lt;span class="nt"&gt;&amp;lt;/section&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We had already structured this report to be a collection of largely independent collection of sections - we were even using this terminology! - so it's not a huge drama to add the &lt;code&gt;&amp;lt;section&amp;gt;&lt;/code&gt; tags to the system. &lt;/p&gt;

&lt;h3&gt;
  
  
  Proof that this works
&lt;/h3&gt;

&lt;p&gt;Run &lt;code&gt;autoreporting.py&lt;/code&gt; and inspect the report - try it both at full-screen and &lt;a href="https://developers.google.com/web/tools/chrome-devtools/device-mode/" rel="noopener noreferrer"&gt;simulating a mobile screen&lt;/a&gt;. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Fcjgumoiu4xixcf26st13.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Fcjgumoiu4xixcf26st13.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Progress! That's the benefit of using a well-made responsive layout.&lt;/p&gt;

&lt;h3&gt;
  
  
  GitHub status
&lt;/h3&gt;

&lt;p&gt;Oooft. That was a lot of background reading for not a huge amount of code. All the same, the project should look like &lt;a href="https://github.com/goyder/autoreporting/commit/bb86833aef81847f09cea687a47273e64341fc68" rel="noopener noreferrer"&gt;this&lt;/a&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step Eight - Making Our Tables Interactive
&lt;/h2&gt;

&lt;p&gt;Okay. So now we have our tables, and they look pretty good - the challenge is that they're not interactive. For instance, it would be wonderful to have the functionality to filter by a category, or drill down to a specific image.&lt;/p&gt;

&lt;p&gt;Now, as with exploring CSS, we have a couple of options. Certainly, we can explore the option of creating all of this ourselves - there are plenty of examples around, and they're not &lt;a href="https://www.w3schools.com/jquery/jquery_filters.asp" rel="noopener noreferrer"&gt;terribly difficult&lt;/a&gt;. But, if we're being pragmatic (or pressed by business needs!) we can probably find a pre-built package of what we need.&lt;/p&gt;

&lt;p&gt;After a bit of googling, I came across &lt;a href="https://datatables.net/" rel="noopener noreferrer"&gt;DataTables&lt;/a&gt; - this is a plugin for &lt;a href="https://jquery.com/" rel="noopener noreferrer"&gt;JQuery&lt;/a&gt;, a very common JavaScript framework. DataTables looks like it covers most of the functionality we need, and provides a wealth of &lt;a href="https://datatables.net/extensions/index" rel="noopener noreferrer"&gt;extensions&lt;/a&gt; and &lt;a href="https://datatables.net/plug-ins/index" rel="noopener noreferrer"&gt;plugins&lt;/a&gt; for any of the functionality we don't have. All in all, a promising candidate. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2F1sssls51ujht3gjulihl.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2F1sssls51ujht3gjulihl.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Implementing DataTables
&lt;/h3&gt;

&lt;p&gt;Fortunately, it turns out that implementing DataTables is relatively straight forward. From the front page of the &lt;a href="https://datatables.net/" rel="noopener noreferrer"&gt;DataTables&lt;/a&gt; site, we can see that the general principles are that we need to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;em&gt;Although not spelled out explicitly&lt;/em&gt; - DataTables is a JQuery plugin - so first we need load the Jquery &lt;code&gt;.js&lt;/code&gt; file.&lt;/li&gt;
&lt;li&gt;We'll then load the DataTables &lt;code&gt;.js&lt;/code&gt; and &lt;code&gt;.css&lt;/code&gt; files.&lt;/li&gt;
&lt;li&gt;Finally, we call the DataTables function, pointing it at the &lt;a href="https://www.w3schools.com/html/html_id.asp" rel="noopener noreferrer"&gt;HTML &lt;code&gt;id&lt;/code&gt;&lt;/a&gt; of the table we want to add the functionality to.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That's all relatively straightforward, with just some very solvable wrinkles:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Our tables don't have &lt;code&gt;id&lt;/code&gt; tags to refer to.&lt;/li&gt;
&lt;li&gt;We need a way to call the DataTables function and point it at the &lt;code&gt;id&lt;/code&gt; of the table, in a way that fits with our templating system.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Let's address these one by one.&lt;/p&gt;

&lt;h4&gt;
  
  
  Importing the relevant files
&lt;/h4&gt;

&lt;p&gt;Before we get to our wrinkles, let's hit our basics. As we imported our files from CDNs previously, we'll do the same for JQuery.&lt;/p&gt;

&lt;p&gt;Let's update the &lt;code&gt;&amp;lt;head&amp;gt;&lt;/code&gt; section of &lt;code&gt;templates/report.html&lt;/code&gt; and add the links:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;report.html&lt;/code&gt;&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="c"&gt;&amp;lt;!-- More above! --&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;head&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;meta&lt;/span&gt; &lt;span class="na"&gt;charset=&lt;/span&gt;&lt;span class="s"&gt;"UTF-8"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;meta&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"viewport"&lt;/span&gt; &lt;span class="na"&gt;content=&lt;/span&gt;&lt;span class="s"&gt;"width=device-width, initial-scale=1.0"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;title&amp;gt;&lt;/span&gt;{{ title }}&lt;span class="nt"&gt;&amp;lt;/title&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;link&lt;/span&gt; &lt;span class="na"&gt;rel=&lt;/span&gt;&lt;span class="s"&gt;"stylesheet"&lt;/span&gt; &lt;span class="na"&gt;href=&lt;/span&gt;&lt;span class="s"&gt;"//fonts.googleapis.com/css?family=Roboto:300,300italic,700,700italic"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;link&lt;/span&gt; &lt;span class="na"&gt;rel=&lt;/span&gt;&lt;span class="s"&gt;"stylesheet"&lt;/span&gt; &lt;span class="na"&gt;href=&lt;/span&gt;&lt;span class="s"&gt;"//cdn.rawgit.com/necolas/normalize.css/master/normalize.css"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;link&lt;/span&gt; &lt;span class="na"&gt;rel=&lt;/span&gt;&lt;span class="s"&gt;"stylesheet"&lt;/span&gt; &lt;span class="na"&gt;href=&lt;/span&gt;&lt;span class="s"&gt;"//cdn.rawgit.com/milligram/milligram/master/dist/milligram.min.css"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;link&lt;/span&gt; &lt;span class="na"&gt;rel=&lt;/span&gt;&lt;span class="s"&gt;"stylesheet"&lt;/span&gt; &lt;span class="na"&gt;href=&lt;/span&gt;&lt;span class="s"&gt;"//cdn.datatables.net/1.10.19/css/jquery.dataTables.min.css"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;script &lt;/span&gt;&lt;span class="na"&gt;src=&lt;/span&gt;&lt;span class="s"&gt;"https://code.jquery.com/jquery-3.3.1.slim.min.js"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;script &lt;/span&gt;&lt;span class="na"&gt;src=&lt;/span&gt;&lt;span class="s"&gt;"//cdn.datatables.net/1.10.19/js/jquery.dataTables.min.js"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/head&amp;gt;&lt;/span&gt;
&lt;span class="c"&gt;&amp;lt;!-- More below! --&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Adding &lt;code&gt;id&lt;/code&gt; tags to the tables
&lt;/h4&gt;

&lt;p&gt;We need to add an &lt;code&gt;id&lt;/code&gt; tag to the &lt;code&gt;&amp;lt;table&amp;gt;&lt;/code&gt; objects in our report. How can we do this? &lt;/p&gt;

&lt;p&gt;Well, let's work backwards from the &lt;code&gt;templates/table_section.html&lt;/code&gt; file. Within that file, we note that we insert our fully-formed HTML tables via the &lt;code&gt;{{ table }}&lt;/code&gt; insert.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;{{ table }}&lt;/code&gt; insert is generated in &lt;code&gt;autoreporting.py&lt;/code&gt; when we call the &lt;code&gt;get_results_df_as_html&lt;/code&gt; method of &lt;code&gt;ModelResults&lt;/code&gt; class. This method takes the &lt;code&gt;pandas&lt;/code&gt; DataFrame and converts it into a string of HTML using the &lt;a href="https://pandas.pydata.org/pandas-docs/stable/generated/pandas.DataFrame.to_html.html" rel="noopener noreferrer"&gt;&lt;code&gt;DataFrame.to_html&lt;/code&gt; function&lt;/a&gt;.  &lt;/p&gt;

&lt;p&gt;If we inspect the docs for that function, we see that there's an optional argument &lt;code&gt;table_id&lt;/code&gt;. Ah yeah, cool man! If we pass the model name as that argument, the HTML table will be generated with the &lt;code&gt;id&lt;/code&gt; that we want. The &lt;code&gt;ModelResults&lt;/code&gt; class already has &lt;code&gt;model_name&lt;/code&gt; as an attribute, so we can include that:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ModelResults&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;

    &lt;span class="c1"&gt;# ...
&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;get_results_df_as_html&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;
        Return the results DataFrame as an HTML object.
        :return: String of HTML.
        &lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
        &lt;span class="n"&gt;html&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;df_results&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;to_html&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;table_id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;model_name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;html&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can run &lt;code&gt;autoreporting.py&lt;/code&gt; and inspect the tables that are generated to confirm that they do indeed have the model name as an &lt;code&gt;id&lt;/code&gt;. &lt;/p&gt;

&lt;p&gt;Easy! Just a matter of tracing it back from the end result of HTML to the actual source within the code.&lt;/p&gt;

&lt;h4&gt;
  
  
  Calling the DataTables function
&lt;/h4&gt;

&lt;p&gt;We need to call the DataTables function listed above, pointing at the appropriate &lt;code&gt;id&lt;/code&gt; as we've just generated. The actual code to call the function is pretty straightforward. The question is: where do we put it? &lt;/p&gt;

&lt;p&gt;This challenge has a couple of bounding constraints on it. &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;We need to call the function to generate the DataTable using the model name - something like this:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;$(document).ready( function () {
    $('#VGG19').DataTable();
} );
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;Traditionally, JavaScript is placed either in the &lt;code&gt;&amp;lt;head&amp;gt;&lt;/code&gt; section, although this is a &lt;a href="https://stackoverflow.com/questions/1213281/does-javascript-have-to-be-in-the-head-tags" rel="noopener noreferrer"&gt;somewhat&lt;/a&gt; controversial discussion. Note that this is &lt;em&gt;tradition&lt;/em&gt;  - it will actually run anywhere.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is a challenge because of the structuring of our template. We want to place the JavaScript call for DataTables in &lt;code&gt;&amp;lt;head&amp;gt;&lt;/code&gt;, which is in &lt;code&gt;templates/report.html&lt;/code&gt;. Right now, when &lt;code&gt;report.html&lt;/code&gt; is rendered under the &lt;code&gt;main()&lt;/code&gt; function in &lt;code&gt;autoreporting.py&lt;/code&gt;, it doesn't know anything about the model names: the &lt;code&gt;render()&lt;/code&gt; call only has arguments for &lt;code&gt;title&lt;/code&gt;, the overall title of the report, and &lt;code&gt;sections&lt;/code&gt;, a list of pre-rendered strings of HTML ready to be inserted into the document. We just need to modify &lt;code&gt;autoreporting.py&lt;/code&gt; to pass in the model names and tweak &lt;code&gt;report.html&lt;/code&gt; accordingly. We tweak our files thusly:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;autoreporting.py&lt;/code&gt;&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;

    &lt;span class="c1"&gt;# ...
&lt;/span&gt;
    &lt;span class="c1"&gt;# Production and write the report to file
&lt;/span&gt;    &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;write&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;base_template&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;render&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="n"&gt;title&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;sections&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;sections&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;model_results_list&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;vgg19_results&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;mobilenet_results&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;&lt;strong&gt;&lt;code&gt;report.html&lt;/code&gt;&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;head&amp;gt;
    &amp;lt;!-- Lots of calls above... --&amp;gt;
    &amp;lt;script&amp;gt;
        {% for model_results in model_results_list %}
            $(document).ready(function() {
                $('#{{ model_results.model_name }}').DataTable();
            } );
        {% endfor %}
    &amp;lt;/script&amp;gt;
&amp;lt;/head&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Bingo-bango: when we render &lt;code&gt;report.html&lt;/code&gt;, the calls to render the DataTable functionality for each existing table is included. Nice!&lt;/p&gt;

&lt;p&gt;And now, if we run &lt;code&gt;autoreporting.py&lt;/code&gt; and inspect the output, we get something like this:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Fdjq67ud0lfhs6wxv0ao4.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Fdjq67ud0lfhs6wxv0ao4.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We can now order our tables, search for categories, filter by image names - we have some rich functionality available, with &lt;a href="https://datatables.net/extensions/index" rel="noopener noreferrer"&gt;more available via extensions&lt;/a&gt;. &lt;/p&gt;

&lt;p&gt;This is a huge advantage for a report! Imagine if you only wanted to check out the common themes of incorrect image categorisations, or rapidly narrow down on a specific image. &lt;/p&gt;

&lt;h3&gt;
  
  
  GitHub status
&lt;/h3&gt;

&lt;p&gt;Your repo should look a little something like &lt;a href="https://github.com/goyder/autoreporting/tree/bb86833aef81847f09cea687a47273e64341fc68" rel="noopener noreferrer"&gt;this&lt;/a&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step Nine - Packaging it up
&lt;/h2&gt;

&lt;p&gt;&lt;em&gt;AKA Step the Last.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;The good news: we have the functionality we want and need. We can take a &lt;code&gt;.csv&lt;/code&gt; file or two and punch out an interactive report.&lt;/p&gt;

&lt;p&gt;The bad news: we've hardcoded it to two files, &lt;code&gt;VGG19_results.csv&lt;/code&gt; and &lt;code&gt;MobileNet_results.csv&lt;/code&gt;, which limits the functionality.&lt;/p&gt;

&lt;p&gt;The final step for this exploration is therefore to turn this hard-coded script into a tool that can be called from the command-line. We want to be able to call the report and an arbitrary number of &lt;code&gt;.csv&lt;/code&gt; files and have the report spat-out. So if we called our script and specified the relevant &lt;code&gt;.csv&lt;/code&gt; files, we'd get a report successfully written to the &lt;code&gt;/outputs&lt;/code&gt; folder - looking a little like this on the command line:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;python autoreporting.py VGG19_results.csv MobileNet_results.csv
Successfully wrote &lt;span class="s2"&gt;"report.html"&lt;/span&gt; to folder &lt;span class="s2"&gt;"outputs"&lt;/span&gt;&lt;span class="nb"&gt;.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This can be accomplished by utilising command line arguments - or, to put it rather simply, the commands that follow the call to Python. (In the example above, the first argument is &lt;code&gt;autoreporting.py&lt;/code&gt;, our script. The second and third command line arguments are &lt;code&gt;VGG19_results.csv&lt;/code&gt; and &lt;code&gt;MobileNet_results.csv&lt;/code&gt;, respectively.) We have a couple of main ways we can approach this:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;We can crunch the arguments manually, using &lt;a href="https://docs.python.org/3.6/library/sys.html#sys.argv" rel="noopener noreferrer"&gt;&lt;code&gt;sys.argv&lt;/code&gt;&lt;/a&gt;. There's absolutely nothing wrong with this approach, &lt;code&gt;sys.argv&lt;/code&gt; is really quite simple to use.&lt;/li&gt;
&lt;li&gt;We can use a Parser like &lt;a href="https://docs.python.org/3.3/library/argparse.html#" rel="noopener noreferrer"&gt;&lt;code&gt;argparse&lt;/code&gt;&lt;/a&gt;, primarily to assist in generating help and error messages. &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Because I've not used &lt;code&gt;argparse&lt;/code&gt; before, I'm interested in giving it a go and testing it for these purposes. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Fma484b0jm9oxskzryoh7.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Fma484b0jm9oxskzryoh7.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Implementing &lt;code&gt;argparse&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;Most everything we want to work with in argparse is handled within the &lt;code&gt;main()&lt;/code&gt; call within &lt;code&gt;autoreporting.py&lt;/code&gt;. To make this work, we're going to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Define and parse the arguments we're interested in (specifically, filepaths to the results &lt;code&gt;.csv&lt;/code&gt; files), using &lt;code&gt;argparse&lt;/code&gt;;&lt;/li&gt;
&lt;li&gt;Convert these filepaths into &lt;code&gt;ModelResults&lt;/code&gt; objects that we can use to generate our reports;&lt;/li&gt;
&lt;li&gt;Adapt our existing code to output reports using these &lt;code&gt;ModelResults&lt;/code&gt; objects.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So, first of all, we need to make sure that &lt;code&gt;argparse&lt;/code&gt; is imported. It's been part of the standard library since Python 3.2.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;autoreporting.py&lt;/code&gt;&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;argparse&lt;/span&gt;
&lt;span class="c1"&gt;# ...
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Next, within &lt;code&gt;main()&lt;/code&gt;, we'll define the parser - we're saying how we want the command line arguments to be interpreted. This code is adapted pretty quick smart from the &lt;a href="https://docs.python.org/3.6/library/argparse.html" rel="noopener noreferrer"&gt;demo in the &lt;code&gt;argparse&lt;/code&gt; docs&lt;/a&gt;. We actually only have one argument, by how &lt;code&gt;argparse&lt;/code&gt; defines it - just the filepaths to our results &lt;code&gt;.csv&lt;/code&gt; files. The key thing to note is that we set the &lt;code&gt;nargs&lt;/code&gt; argument to &lt;code&gt;"+"&lt;/code&gt;, indicating that we can have a &lt;em&gt;undefined&lt;/em&gt; number of arguments of this kind, but we do need &lt;em&gt;at least one&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;When we call &lt;code&gt;parser.parse_args()&lt;/code&gt;, all the arguments are neatly returned as a &lt;a href="https://docs.python.org/dev/library/argparse.html#argparse.Namespace" rel="noopener noreferrer"&gt;Namespace&lt;/a&gt; object that makes the inputs very easy to access, as we'll see in the following steps.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;
&lt;span class="c1"&gt;# ...
&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;
    Entry point for the script.
    Render a template and write it to file.
    :return:
    &lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
    &lt;span class="c1"&gt;# Define and parse our arguments
&lt;/span&gt;    &lt;span class="n"&gt;parser&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;argparse&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;ArgumentParser&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;description&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Convert results .csv files into an interactive report.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;parser&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add_argument&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;results_filepaths&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;nargs&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;+&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;help&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Path(s) to results file(s) with filename(s) &lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;&amp;lt;model_name&amp;gt;_results.csv&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;args&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;parser&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parse_args&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;From &lt;code&gt;args&lt;/code&gt;, the &lt;code&gt;Namespace&lt;/code&gt; object, we can pull out the filepaths and use them to generate &lt;code&gt;ModelResults&lt;/code&gt; objects. &lt;/p&gt;

&lt;p&gt;&lt;code&gt;args.result_filepaths&lt;/code&gt; holds a list of our filepaths, which we have indicated should point at filenames in the format &lt;code&gt;&amp;lt;model_name&amp;gt;_results.csv&lt;/code&gt;. We use the &lt;code&gt;os.path&lt;/code&gt; module to manipulate this filepath, extract the model name, and generate the &lt;code&gt;ModelResults&lt;/code&gt; object, adding it into a list called &lt;code&gt;model_results&lt;/code&gt; as we go. &lt;/p&gt;

&lt;p&gt;This filename manipulation can look a little tricky, but &lt;a href="https://docs.python.org/3.6/library/os.path.html" rel="noopener noreferrer"&gt;inspect the doccies of &lt;code&gt;os.path&lt;/code&gt;&lt;/a&gt; and you'll see it's mostly clever string manipulation. &lt;code&gt;os.path&lt;/code&gt; is full of very, very useful functions that can save you a lot of time with common path manipulations, and help your code to work cross-platform!&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;    &lt;span class="c1"&gt;# Create the model_results list, which holds the relevant information
&lt;/span&gt;    &lt;span class="n"&gt;model_results&lt;/span&gt; &lt;span class="o"&gt;=&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;results_filepath&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;results_filepaths&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;results_root_name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;splitext&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;basename&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;results_filepath&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;model_name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;results_root_name&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;_results&lt;/span&gt;&lt;span class="sh"&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="n"&gt;model_results&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="nc"&gt;ModelResults&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;model_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;results_filepath&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The logic for the set intersection - how we figure out which images are common across all results files - has be changed to account for the fact that we now have an arbitrary number of &lt;code&gt;ModelResults&lt;/code&gt; objects in a list. &lt;/p&gt;

&lt;p&gt;To make this work, we quickly extract the &lt;code&gt;misidentified_images&lt;/code&gt; property of each object using a &lt;a href="https://www.programiz.com/python-programming/list-comprehension" rel="noopener noreferrer"&gt;list comprehension&lt;/a&gt;, and then calculate the intersection of sets based on this resulting list. (Note that we have to use a leading asterix (&lt;code&gt;*&lt;/code&gt;) when we call &lt;code&gt;set.intersection()&lt;/code&gt; so that each member of the list gets passed in as an individual argument).&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;    &lt;span class="c1"&gt;# Create some more content to be published as part of this analysis
&lt;/span&gt;    &lt;span class="n"&gt;title&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Model Report&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="n"&gt;misidentified_images&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;results&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;misidentified_images&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;results&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;model_results&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="n"&gt;number_misidentified&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;len&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="nf"&gt;intersection&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;misidentified_images&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Everything below this point is relatively consistent with our previous version, but now we're taking advantage of the fact that we have our &lt;code&gt;ModelResults&lt;/code&gt; objects already packed up into the &lt;code&gt;model_results&lt;/code&gt; list.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;    &lt;span class="c1"&gt;# Produce our section blocks
&lt;/span&gt;    &lt;span class="n"&gt;sections&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;list&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;sections&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;summary_section_template&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;render&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;model_results_list&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;model_results&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;number_misidentified&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;number_misidentified&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;model_result&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;model_results&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;sections&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;table_section_template&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;render&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;model_result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;model_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;dataset&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;model_result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;dataset&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;table&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;model_result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get_results_df_as_html&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c1"&gt;# Produce and write the report to file
&lt;/span&gt;    &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="nf"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;outputs/report.html&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;w&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;write&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;base_template&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;render&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="n"&gt;title&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;sections&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;sections&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;model_results_list&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;model_results&lt;/span&gt;
        &lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Successfully wrote &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;report.html&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt; to folder &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;outputs&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;.&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Oooft! With all of the explanations, this looks a little complex. However, when you compare this code to the previous commit, you'll see there's not a great deal that's actually significantly different here - we've really kept the core principles the same and just played with the packaging a bit. &lt;/p&gt;

&lt;h3&gt;
  
  
  GitHub status
&lt;/h3&gt;

&lt;p&gt;Your project should look a little &lt;a href="https://github.com/goyder/autoreporting/tree/9bf0ee2bdfc06f3ccd824d7c42599288efbfcc0b" rel="noopener noreferrer"&gt;like this&lt;/a&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  The End?
&lt;/h2&gt;

&lt;p&gt;At the very start of the &lt;a href="https://dev.to/goyder/automatic-reporting-in-python---part-1-from-planning-to-hello-world-32n1"&gt;first post&lt;/a&gt;, I indicated that the goal of this project was to create an automatic HTML reporting tool, where the outcome was a single stand-alone HTML file, with info and interactivity.&lt;/p&gt;

&lt;p&gt;Well, it's done! We've got a tool that can accept an arbitrary number of standard results files, and spit out a report that crunches them into an interactive format. &lt;/p&gt;

&lt;p&gt;Take a breather, push your chair away from your desk, and pat yourself on the back. We've done what we set out to do!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Fgpweb7hcoobs1m3w7afl.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Fgpweb7hcoobs1m3w7afl.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Does that mean we're done? That depends, really.&lt;/p&gt;

&lt;h3&gt;
  
  
  What comes next?
&lt;/h3&gt;

&lt;p&gt;At this point, we have a tool that works for a very narrow use-case, and assumes &lt;em&gt;perfect inputs&lt;/em&gt; and &lt;em&gt;perfect operation&lt;/em&gt; from the user. Now, if you're using a tool like this just for yourself, and the inputs to the tool are quite consistent, then that could in fact be perfectly satisfactory - so no more work to be done, you've got something that's fit for purpose.&lt;/p&gt;

&lt;p&gt;But of course, there are any number of ways we can work to extend and harden this tool. As I was writing this tutorial, I made notes on some of these. Running losely from simpler to more complex, here are a few notes and ideas:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Could we add a default command-line argument with &lt;code&gt;argparse&lt;/code&gt; that allows us to specify the title of the report?&lt;/li&gt;
&lt;li&gt;Our &lt;code&gt;.csv&lt;/code&gt; inputs need to be named in a perfectly consistent format. How can we restructure our inputs so that we can define the model name and not have it read from the filename?&lt;/li&gt;
&lt;li&gt;How can we make this a command-line script that can be run anywhere on our machine - not just in the folder the script is in? If our data is generated and stored elsewhere, it would certainly be more useful to be able to call &lt;code&gt;autoreport&lt;/code&gt; on the terminal, rather than trace back to where the script is stored, for instance.
&lt;/li&gt;
&lt;li&gt;In its current form, we're analysing images - can we add functionality to &lt;em&gt;show&lt;/em&gt; the images we're analysing? This would be great for generating hypotheses for &lt;em&gt;why&lt;/em&gt; a model failed.&lt;/li&gt;
&lt;li&gt;Our reports need an internet connection each time they're open. As part of the templating process, could we pull down the JavaScript and CSS files and embed them into our files?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is a tiny sliver of the possible extensions - and this is not even to mention that there's plenty of refactoring and tidying to be done across the project. The job of improving your work is never done!&lt;/p&gt;

&lt;h3&gt;
  
  
  A thankyou and a call to action
&lt;/h3&gt;

&lt;p&gt;This has been the first tutorial of this scope I've ever written. I've had a lot of fun doing so, and to paraphrase Sigur Rós, this has been a &lt;a href="https://genius.com/albums/Sigur-ros/Agtis-byrjun" rel="noopener noreferrer"&gt;good beginning&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;But I'm really keen to hear what parts of this &lt;em&gt;you&lt;/em&gt;, the tutorial-reader, enjoyed and what parts were challenging or obscure. Feel free to drop a comment or send me a message on what worked and what didn't.&lt;/p&gt;

&lt;p&gt;See you next time!&lt;/p&gt;

</description>
      <category>python</category>
      <category>git</category>
      <category>beginners</category>
      <category>planning</category>
    </item>
    <item>
      <title>Automatic Reporting in Python - Part 2: From Hello World to Real Insights</title>
      <dc:creator>goyder</dc:creator>
      <pubDate>Wed, 13 Jun 2018 14:03:46 +0000</pubDate>
      <link>https://dev.to/goyder/automatic-reporting-in-python---part-2-from-hello-world-to-real-insights-8p3</link>
      <guid>https://dev.to/goyder/automatic-reporting-in-python---part-2-from-hello-world-to-real-insights-8p3</guid>
      <description>&lt;p&gt;As outlined &lt;a href="https://dev.to/goyder/automatic-reporting-in-python---part-1-from-planning-to-hello-world-32n1"&gt;in my previous post&lt;/a&gt;, the goal of this project is to make an automatic reporting tool.&lt;/p&gt;

&lt;p&gt;In this guide, the outcome I'm shooting for is a single HTML page that allows me to interrogate and compare the outputs of machine learning models.&lt;/p&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%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Fae8kwaytszwzmznkkmmn.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%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Fae8kwaytszwzmznkkmmn.png" width="342" height="407"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;At the &lt;a href="https://dev.to/goyder/automatic-reporting-in-python---part-1-from-planning-to-hello-world-32n1"&gt;conclusion of the previous tutorial&lt;/a&gt;, we'd gotten up to the "Hello, World!" stage. Some very basic content could be fed into our very basic structure - but now it's time to add some real features.&lt;/p&gt;

&lt;h3&gt;
  
  
  A note on tutorials and design choices
&lt;/h3&gt;

&lt;p&gt;Rather than just providing a dump of code that works, I'd like to incrementally work through the development, step-by-step. &lt;/p&gt;

&lt;p&gt;Personally, I've always found this kind of tutorial more effective as:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;It gives an insight of how someone else approaches a problem&lt;/li&gt;
&lt;li&gt;It actually reflects how I code - feature by feature, commit by commit.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;When I was totally fresh to programming, I worked from a large number of tutorials. The structure of most tutorials gives you the impression that there is one and only one way to accomplish the goal that the tutorial sets out to do - and, admittedly, sometimes there is, depending on how niche and technical the tutorial is. &lt;/p&gt;

&lt;p&gt;But more often I've found that any programming job, even the very small, requires choices and trade-offs to be made in the implementation of the solution from the get-go. Should this be a function or a class? Do I import a library or roll my own? Should I be using a dictionary or a list? What kind of flow-on effects are these choices going to have?&lt;/p&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%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Fpd0quc5sgv8z625fqwg4.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%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Fpd0quc5sgv8z625fqwg4.png" width="133" height="95"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  One approach to simplify things: mark your assumptions
&lt;/h3&gt;

&lt;p&gt;Now, there's obviously no simple solution to this challenge. However, one approach to making the process of understanding easier is being clear on the assumptions underpinning your project. &lt;/p&gt;

&lt;p&gt;For instance, in this project:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;I am the only consumer of these reports, and therefore;&lt;/li&gt;
&lt;li&gt;My users (me!) will not be hostile&lt;/li&gt;
&lt;li&gt;A minimum of error-checking is required, as I can interrogate and troubleshoot myself&lt;/li&gt;
&lt;li&gt;An internet connection is available&lt;/li&gt;
&lt;li&gt;Portability is not required&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Most of these are simplifying assumptions that serve to make clear exactly how thorough or involved my solution needs to be. OF course, as soon as you start looking at providing your code to others, or in an enterprise situation, or if a higher degree of automation is required, the game can change significantly!&lt;/p&gt;

&lt;p&gt;It makes the ecosystem in which this tool will exist a bit clearer.  When I get stuck and I'm not sure where to go, I'll refer back to these assumptions and see if I've not answered my own question somewhere.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step Three - Generate some data to work with
&lt;/h2&gt;

&lt;p&gt;As mentioned in &lt;a href="https://dev.to/goyder/automatic-reporting-in-python---part-1-from-planning-to-hello-world-32n1"&gt;Part 1 of this Post&lt;/a&gt;, there are a host of use-cases for this tool that come from the machine learning space. &lt;/p&gt;

&lt;p&gt;In this case, we'll consider the following scenario:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;We have an existing, baseline model that determines whether an image contains a &lt;strong&gt;cat&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;We regularly develop new models that carry out the same task. These models may or may not be an improvement on your baseline model.&lt;/li&gt;
&lt;li&gt;Every time we develop a new model, we run it over an existing set of data and capture the outputs. In this case, the outputs are what the model believes to be in each image.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It would therefore be convenient to be able to:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Automatically review these outputs and get a status report on how the new model did.&lt;/li&gt;
&lt;li&gt;Automatically compare the performance of these models against each other. Where did one outperform the other? Are there images that both struggle with?&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;For the purposes of this study, we'll assess the performance of two models in the well respected field of detecting cats in images. &lt;a href="https://arxiv.org/abs/1409.1556" rel="noopener noreferrer"&gt;&lt;strong&gt;VGG19&lt;/strong&gt;&lt;/a&gt;, the first model, is a well respected and important image classifier. &lt;a href="https://arxiv.org/abs/1704.04861" rel="noopener noreferrer"&gt;&lt;strong&gt;MobileNet&lt;/strong&gt;&lt;/a&gt; is a significantly more lightweight architecture (running approximately six times faster than VGG19, for example) but with reduced accuracy. &lt;/p&gt;

&lt;p&gt;The use of these models to generate the data can be found under a separate repo, &lt;a href="https://github.com/goyder/dataset_generation" rel="noopener noreferrer"&gt;available here&lt;/a&gt;. While the process of generating this data is a bit beyond the scope of this discussion, the end-result is we have two datasets to play with - &lt;code&gt;VGG19_results.csv&lt;/code&gt; and &lt;code&gt;MobileNet_results.csv&lt;/code&gt; - each stored as &lt;a href="https://en.wikipedia.org/wiki/Comma-separated_values" rel="noopener noreferrer"&gt;CSV files&lt;/a&gt;. &lt;/p&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%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Fbspnb7m7jo8jqcm7pc4b.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%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Fbspnb7m7jo8jqcm7pc4b.png" width="700" height="379"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;For each image in the dataset, we have a column for the predicted ImageNet category ID (&lt;code&gt;imagenet_index&lt;/code&gt;), the text description of the category (&lt;code&gt;imagenet_category&lt;/code&gt;), and whether this corresponds to a housecat (&lt;code&gt;correct&lt;/code&gt;). &lt;/p&gt;




&lt;h2&gt;
  
  
  Step Four - Set up a structure to put things in
&lt;/h2&gt;

&lt;p&gt;Well - after our digressions - back to the reporter. When we left off, we had the reporter in a functional but featureless state:&lt;/p&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%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Fjmubbdsddqgrnm4opkpl.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%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Fjmubbdsddqgrnm4opkpl.png" width="800" height="322"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Let's make the next move by adding a basic stucture to the report that we can populate with more sections of content. First, we'll update &lt;code&gt;report.html&lt;/code&gt;. We'll make some minor tweaks, like specifying an actual &lt;code&gt;title&lt;/code&gt; point to be entered and adding a brief preamble, but the real interesting part is below that:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;report.html&lt;/code&gt;&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="cp"&gt;&amp;lt;!DOCTYPE html&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;html&lt;/span&gt; &lt;span class="na"&gt;lang=&lt;/span&gt;&lt;span class="s"&gt;"en"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;head&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;meta&lt;/span&gt; &lt;span class="na"&gt;charset=&lt;/span&gt;&lt;span class="s"&gt;"UTF-8"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;title&amp;gt;&lt;/span&gt;{{ title }}&lt;span class="nt"&gt;&amp;lt;/title&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/head&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;body&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;h1&amp;gt;&lt;/span&gt;{{ title }}&lt;span class="nt"&gt;&amp;lt;/h1&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;p&amp;gt;&lt;/span&gt;This report was automatically generated.&lt;span class="nt"&gt;&amp;lt;/p&amp;gt;&lt;/span&gt;
{% for section in sections %}
    {{ section }}
{% endfor %}
&lt;span class="nt"&gt;&amp;lt;/body&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/html&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We're limited to inserting blocks of text into our template - Jinja allows control flow structures! In this case, we'll feed in some iterable variable &lt;code&gt;sections&lt;/code&gt; - note the plural - and then we'll publish each &lt;code&gt;section&lt;/code&gt;  object within.&lt;/p&gt;

&lt;p&gt;But what are we to include in these sections? As a first run, we can generate a section for each model that displays a brief bit of information about the model output, and then displays the results. Let's create a new template, &lt;code&gt;table_section.html&lt;/code&gt;, &lt;br&gt;
that we'll use to render this information.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;table_section.html&lt;/code&gt;&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;h2&amp;gt;&lt;/span&gt;{{ model }} - Model Results&lt;span class="nt"&gt;&amp;lt;/h2&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;p&amp;gt;&lt;/span&gt;Results for each image as predicted by model &lt;span class="nt"&gt;&amp;lt;i&amp;gt;&lt;/span&gt;'{{ model }}'&lt;span class="nt"&gt;&amp;lt;/i&amp;gt;&lt;/span&gt;, as captured in file &lt;span class="nt"&gt;&amp;lt;i&amp;gt;&lt;/span&gt;'{{ dataset }}'&lt;span class="nt"&gt;&amp;lt;/i&amp;gt;&lt;/span&gt;.&lt;span class="nt"&gt;&amp;lt;/p&amp;gt;&lt;/span&gt;
{{ table }}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Nice and simple - this gives us all the structure we need.&lt;/p&gt;

&lt;p&gt;Before we get to inserting the tables into the document, let's see what this gives us if we populate it with some dummy data. We'll update &lt;code&gt;autoreporting.py&lt;/code&gt; to a) refer to the new updated terms in the updated &lt;code&gt;report.html&lt;/code&gt; template, and b) render and input some &lt;code&gt;sections&lt;/code&gt; so we can check out the new template.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;autoreporting.py&lt;/code&gt;&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;jinja2&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;FileSystemLoader&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Environment&lt;/span&gt;

&lt;span class="c1"&gt;# Configure Jinja and ready the loader
&lt;/span&gt;&lt;span class="n"&gt;env&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Environment&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;loader&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nc"&gt;FileSystemLoader&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;searchpath&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;templates&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# Assemble the templates we'll use
&lt;/span&gt;&lt;span class="n"&gt;base_template&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get_template&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;report.html&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;table_section_template&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get_template&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;table_section.html&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# Content to be published
&lt;/span&gt;&lt;span class="n"&gt;title&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Model Report&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="n"&gt;sections&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;list&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="n"&gt;sections&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;table_section_template&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;render&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;VGG19&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;dataset&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;VGG19_results.csv&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;table&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Table goes here.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="n"&gt;sections&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;table_section_template&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;render&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;MobileNet&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;dataset&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;MobileNet_results.csv&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;table&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Table goes here.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="p"&gt;))&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;
    Entry point for the script.
    Render a template and write it to file.
    :return:
    &lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
    &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="nf"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;outputs/report.html&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;w&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;write&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;base_template&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;render&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="n"&gt;title&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;sections&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;sections&lt;/span&gt;
        &lt;span class="p"&gt;))&lt;/span&gt;


&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;__name__&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;__main__&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When we run the script, the two sections we rendered with the &lt;code&gt;table_section.html&lt;/code&gt; template will be included in our report, one after the other.&lt;/p&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%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Fmum6sae6cuaoybncgmtr.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%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Fmum6sae6cuaoybncgmtr.png" width="800" height="631"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Here, we only have two sections, and they're of the same kind - but we can imagine it'd be no concern to string together more models, or generate blocks that compare performance between models, to suggest just some examples.&lt;/p&gt;

&lt;h3&gt;
  
  
  GitHub status
&lt;/h3&gt;

&lt;p&gt;At this point, your project will look something like &lt;a href="https://github.com/goyder/autoreporting/tree/9ded9bf1867ba6e8149591d34a1525589f0d785e" rel="noopener noreferrer"&gt;this&lt;/a&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step Five - Insert the tables into the document
&lt;/h2&gt;

&lt;p&gt;It's time to extract and insert our tables into the report. &lt;/p&gt;

&lt;p&gt;The basic approach I've suggested is to use &lt;a href="https://pandas.pydata.org/" rel="noopener noreferrer"&gt;&lt;em&gt;pandas&lt;/em&gt;&lt;/a&gt; to read in the &lt;code&gt;.csv&lt;/code&gt; file as a DataFrame object (the workhorse class of &lt;em&gt;pandas&lt;/em&gt;). From this DataFrame object, we can readily export it as HTML using the &lt;code&gt;to_html&lt;/code&gt; method. Our modified version of &lt;code&gt;autoreporting.py&lt;/code&gt; therefore looks as follows:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;autoreporting.py&lt;/code&gt;&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&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="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;jinja2&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;FileSystemLoader&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Environment&lt;/span&gt;

&lt;span class="c1"&gt;# Allow for very wide columns - otherwise columns are spaced and ellipse'd
&lt;/span&gt;&lt;span class="n"&gt;pd&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;set_option&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;display.max_colwidth&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;csv_to_html&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;filepath&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;
    Open a .csv file and return it in HTML format.
    :param filepath: Filepath to a .csv file to be read.
    :return: String of HTML to be published.
    &lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
    &lt;span class="n"&gt;df&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="nf"&gt;read_csv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;filepath&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;index_col&lt;/span&gt;&lt;span class="o"&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;html&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;to_html&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;html&lt;/span&gt;


&lt;span class="c1"&gt;# Configure Jinja and ready the loader
&lt;/span&gt;&lt;span class="n"&gt;env&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Environment&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;loader&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nc"&gt;FileSystemLoader&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;searchpath&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;templates&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# Assemble the templates we'll use
&lt;/span&gt;&lt;span class="n"&gt;base_template&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get_template&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;report.html&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;table_section_template&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get_template&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;table_section.html&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;
    Entry point for the script.
    Render a template and write it to file.
    :return:
    &lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;

    &lt;span class="c1"&gt;# Content to be published
&lt;/span&gt;    &lt;span class="n"&gt;title&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Model Report&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="n"&gt;sections&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;list&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;sections&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;table_section_template&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;render&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;VGG19&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;dataset&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;VGG19_results.csv&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;table&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nf"&gt;csv_to_html&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;datasets/VGG19_results.csv&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="n"&gt;sections&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;table_section_template&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;render&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;MobileNet&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;dataset&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;MobileNet_results.csv&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;table&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nf"&gt;csv_to_html&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;datasets/MobileNet_results.csv&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;))&lt;/span&gt;

    &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="nf"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;outputs/report.html&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;w&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;write&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;base_template&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;render&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="n"&gt;title&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="n"&gt;sections&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;sections&lt;/span&gt;
            &lt;span class="p"&gt;))&lt;/span&gt;


&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;__name__&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;__main__&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The main thing that jumps out at me here is that this is starting to get fairly involved for a script. To keep things on the straight and narrow, I'm applying the following rough flow for ordering:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Imports to start&lt;/li&gt;
&lt;li&gt;Run any configuration code required&lt;/li&gt;
&lt;li&gt;Define any functions required&lt;/li&gt;
&lt;li&gt;Run any standalone code&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;main()&lt;/code&gt; function and call to run it at the end of the script. &lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This raises the question: what should be standalone and what should go in &lt;code&gt;main()&lt;/code&gt;? As a rule of thumb, code that we only want executed when we explicitly run the script goes in &lt;code&gt;main()&lt;/code&gt;; code (like classes, definitions, objects) that we might like to use elsewhere go in the body, available for import elsewhere. You can find a good many explanations of this functionality around, but I find &lt;a href="https://stackoverflow.com/a/22493194" rel="noopener noreferrer"&gt;this&lt;/a&gt; to be a thorough discussion of the concept.&lt;/p&gt;

&lt;p&gt;Some other minor points on the above:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Note the call to widen the &lt;code&gt;max_colwidth&lt;/code&gt; setting via &lt;code&gt;set_option&lt;/code&gt; for &lt;code&gt;pandas&lt;/code&gt; at the start of the script. Without this, &lt;code&gt;pandas&lt;/code&gt; will silently apply some funny formatting if any column exceeds fifty characters. Increasing the &lt;code&gt;max_colwidth&lt;/code&gt; is a trick that has only been learnt through painful experience.&lt;/li&gt;
&lt;li&gt;There's repetition as we call &lt;code&gt;table_section_template.render()&lt;/code&gt; - note that the filename of the same file is entered manually twice, violating the principle of &lt;a href="https://en.wikipedia.org/wiki/Don%27t_repeat_yourself" rel="noopener noreferrer"&gt;Don't Repeat Yourself&lt;/a&gt;. Would it be worthwhile to tweak this so the filename is automatically split out from the filepath?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;With this implemented, we can re-run the script, and voila - tables!&lt;/p&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%2Fthepracticaldev.s3.amazonaws.com%2Fi%2F8w06zeswd5vcs1z1zdvg.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%2Fthepracticaldev.s3.amazonaws.com%2Fi%2F8w06zeswd5vcs1z1zdvg.png" width="800" height="732"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  GitHub status
&lt;/h3&gt;

&lt;p&gt;If you're playing at home, your project will resemble &lt;a href="https://github.com/goyder/autoreporting/tree/f64e986b0775df694b3969a7271b9b1c9814b613" rel="noopener noreferrer"&gt;this&lt;/a&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step Six - Include a summary and comparison
&lt;/h2&gt;

&lt;p&gt;Well, we have the raw data displayed, but we haven't really answered our key question: which model performs better? Using our 'section' based approach, let's develop a new 'summary' section. &lt;/p&gt;

&lt;p&gt;Two questions that often come to mind when comparing models is:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;How accurate is each model? &lt;/li&gt;
&lt;li&gt;Are there patterns in how the models are inaccurate - in this instance, are there images that &lt;em&gt;all&lt;/em&gt; the models get wrong? &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This moves beyond simply &lt;em&gt;displaying&lt;/em&gt; the raw data: instead, we're starting to draw inferences and make summaries, marking a significant increasing in the complexity of the code. I find this situation tends to induce a kind of &lt;a href="https://en.wikipedia.org/wiki/Analysis_paralysis" rel="noopener noreferrer"&gt;analysis paralysis&lt;/a&gt; if I'm not careful: I can spend too long thinking about how best to approach the problem to ensure the solution is neat and elegant. Do I dive straight into the code? Do I write a new template? How do I know if the code as it exists now is under or overcooked?&lt;/p&gt;

&lt;p&gt;One way to focus this process is to very clearly articulate our end-goal, allowing us to focus on figuring out the most effective way there. So, before diving straight back into the Python script, I spent five minutes scribbling down how I'd like this section to read, and settled on some explicit phrases that I was happy with. After some scribbling, I ended up with this target:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;Summary block prototype&lt;/code&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;h2&gt;
  
  
  Quick Summary
&lt;/h2&gt;
&lt;h3&gt;
  
  
  Accuracy
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Model 1&lt;/strong&gt; analysed &lt;strong&gt;100&lt;/strong&gt; images, achieving an accuracy of &lt;strong&gt;97.2%&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Model 2&lt;/strong&gt; analysed &lt;strong&gt;100&lt;/strong&gt; images, achieving an accuracy of &lt;strong&gt;94.2%&lt;/strong&gt;.&lt;/p&gt;
&lt;h3&gt;
  
  
  Trouble spots
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Model 1&lt;/strong&gt; misidentified &lt;strong&gt;10 images&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Model 2&lt;/strong&gt; misidentified &lt;strong&gt;12 images&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;8 images&lt;/strong&gt; misidentified images were common to all.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This seemed straightforward, and &lt;em&gt;implies&lt;/em&gt; a neat translation into &lt;code&gt;Jinja&lt;/code&gt; template structuring - it makes the next step feel obvious. So with this target clearly in mind, I could easily produce the template: &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;summary_section.html&lt;/code&gt;&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;h2&amp;gt;&lt;/span&gt;Quick summary&lt;span class="nt"&gt;&amp;lt;/h2&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;h3&amp;gt;&lt;/span&gt;Accuracy&lt;span class="nt"&gt;&amp;lt;/h3&amp;gt;&lt;/span&gt;
{% for model_results in model_results_list %}
&lt;span class="nt"&gt;&amp;lt;p&amp;gt;&amp;lt;em&amp;gt;&lt;/span&gt;{{ model_results.model_name }}&lt;span class="nt"&gt;&amp;lt;/em&amp;gt;&lt;/span&gt; analysed &lt;span class="nt"&gt;&amp;lt;em&amp;gt;&lt;/span&gt;{{ model_results.number_of_images }} image(s)&lt;span class="nt"&gt;&amp;lt;/em&amp;gt;&lt;/span&gt;, achieving an accuracy of &lt;span class="nt"&gt;&amp;lt;em&amp;gt;&lt;/span&gt;{{ "{:.2%}".format(model_results.accuracy) }}.&lt;span class="nt"&gt;&amp;lt;/em&amp;gt;&amp;lt;/p&amp;gt;&lt;/span&gt;
{% endfor %}
&lt;span class="nt"&gt;&amp;lt;h3&amp;gt;&lt;/span&gt;Trouble spots&lt;span class="nt"&gt;&amp;lt;/h3&amp;gt;&lt;/span&gt;
{% for model_results in model_results_list %}
&lt;span class="nt"&gt;&amp;lt;p&amp;gt;&amp;lt;em&amp;gt;&lt;/span&gt;{{ model_results.model_name }}&lt;span class="nt"&gt;&amp;lt;/em&amp;gt;&lt;/span&gt; misidentified &lt;span class="nt"&gt;&amp;lt;em&amp;gt;&lt;/span&gt;{{ model_results.number_misidentified }} image(s)&lt;span class="nt"&gt;&amp;lt;/em&amp;gt;&lt;/span&gt;.&lt;span class="nt"&gt;&amp;lt;/p&amp;gt;&lt;/span&gt;
{% endfor %}
&lt;span class="nt"&gt;&amp;lt;em&amp;gt;&lt;/span&gt;{{ number_misidentified }}&lt;span class="nt"&gt;&amp;lt;/em&amp;gt;&lt;/span&gt; misidentified image(s) were common to all models.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This template assumes some that I'll produce a &lt;em&gt;list&lt;/em&gt; of some objects &lt;code&gt;model_results&lt;/code&gt;, which in turn contains properties like &lt;code&gt;model_name&lt;/code&gt;, &lt;code&gt;number_of_images&lt;/code&gt;, and &lt;code&gt;accuracy&lt;/code&gt;. I'll also need some value &lt;code&gt;number_misidentified&lt;/code&gt;, referring to the number of images misidentifed across all models: this doesn't quite mesh into a theoretical, singular &lt;code&gt;model_results&lt;/code&gt; object, so it stands alone. (For now.)&lt;/p&gt;

&lt;p&gt;This seems plausible enough, and implies some useful information I can take back into the code, giving me a good structure to start with. If, as I start to work back into the code, I find that these early and rough requirements don't quite translate well or make things too difficult, I won't hesitate to feed design decisions &lt;em&gt;forward&lt;/em&gt; and tweak them. However, in this instance, this looks to be fairly straightforward and I can't imagine any significant issues.&lt;/p&gt;

&lt;p&gt;With this in mind, I worked back into &lt;code&gt;autoreporting.py&lt;/code&gt; and started making some changes. By this point I feel &lt;code&gt;autoreporting.py&lt;/code&gt; is getting a bit long to be posting every time, so I'll work by exception from here on out: you can find this relevant commit &lt;a href="https://github.com/goyder/autoreporting/blob/4f79514660bcc34a44c01be3076f757f1371f098/autoreporting.py" rel="noopener noreferrer"&gt;here&lt;/a&gt;. &lt;/p&gt;

&lt;p&gt;Of perhaps greatest importance is the &lt;code&gt;model_results&lt;/code&gt; object. I spent a little bit of time debating whether to implement this as a class, dictionary, or named tuple: in the end, I landed on a class. The primary reason for this was that I was ended up bundling the &lt;em&gt;data&lt;/em&gt; and the &lt;em&gt;methods&lt;/em&gt; in one: and when you have data and methods combined, a class is a good approach. &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;autoreporting.py - ModelResults class&lt;/code&gt;&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# ...
&lt;/span&gt;
&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ModelResults&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;
    Class to store the results of a model run and associated data.
    &lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;__init__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;model_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;filepath&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;
        :param model_name: Name of model.
        :param filepath: Filepath to results .csv.
        &lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;model_name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;model_name&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;filepath&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;filepath&lt;/span&gt;

        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;dataset&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;filepath&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="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;df_results&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;csv_to_df&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;filepath&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# Filesystem access
&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;number_of_images&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;df_results&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;accuracy&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;_calculate_accuracy&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;misidentified_images&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;_get_misidentified_images&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;number_misidentified&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;misidentified_images&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c1"&gt;# Class methods not shown... 
&lt;/span&gt;
&lt;span class="c1"&gt;# ...
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now, with this &lt;code&gt;ModelResults&lt;/code&gt; class, we can call in our results files only once and have our relevant information readily available, solving the DRY problem mentioned back in Step Five.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;autoreporting.py - main() function&lt;/code&gt;&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# ...
&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;
    Entry point for the script.
    Render a template and write it to file.
    :return:
    &lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
    &lt;span class="c1"&gt;# Content to be published
&lt;/span&gt;    &lt;span class="n"&gt;title&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Model Report&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="n"&gt;vgg19_results&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;ModelResults&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;VGG19&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;datasets/VGG19_results.csv&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;mobilenet_results&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;ModelResults&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;MobileNet&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;datasets/MobileNet_results.csv&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;number_misidentified&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;vgg19_results&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;misidentified_images&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt; &lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;mobilenet_results&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;misidentified_images&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;

    &lt;span class="c1"&gt;# Produce our section blocks
&lt;/span&gt;    &lt;span class="n"&gt;sections&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;list&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;sections&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;summary_section_template&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;render&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;model_results_list&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;vgg19_results&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;mobilenet_results&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
        &lt;span class="n"&gt;number_misidentified&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;number_misidentified&lt;/span&gt;
    &lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="n"&gt;sections&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;table_section_template&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;render&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;vgg19_results&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;model_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;dataset&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;vgg19_results&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;dataset&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;table&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;vgg19_results&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get_results_df_as_html&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;sections&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;table_section_template&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;render&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;mobilenet_results&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;model_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;dataset&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;mobilenet_results&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;dataset&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;table&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;mobilenet_results&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get_results_df_as_html&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c1"&gt;# Produce and write the report to file
&lt;/span&gt;    &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="nf"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;outputs/report.html&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;w&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;write&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;base_template&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;render&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="n"&gt;title&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;sections&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;sections&lt;/span&gt;
        &lt;span class="p"&gt;))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now, we'll run our revised &lt;code&gt;autoreporting.py&lt;/code&gt;, and:&lt;/p&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%2Fthepracticaldev.s3.amazonaws.com%2Fi%2F8ypcum4wkmd5wjcrymku.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%2Fthepracticaldev.s3.amazonaws.com%2Fi%2F8ypcum4wkmd5wjcrymku.png" width="800" height="832"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;A neat summary is what we get!&lt;/p&gt;

&lt;p&gt;From this output, we can see:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Both models achieve approximately the same level of accuracy. This is surprising in and off itself - I would have expected VGG19 to be far more accurate.&lt;/li&gt;
&lt;li&gt;We have a modest amount of overlap in the misidentified images - about 2/3s are common between the models. This suggests that those images are particularly challenging in some way. It may be worthwhile digging into these and examining whether these images are flawed or unsuitable in some way.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Or, to summarise the above dot points: we're actually getting useful insights from the report. Nice!&lt;/p&gt;

&lt;h3&gt;
  
  
  GitHub status
&lt;/h3&gt;

&lt;p&gt;At this point, the project will probably look like &lt;a href="https://github.com/goyder/autoreporting/tree/4f79514660bcc34a44c01be3076f757f1371f098" rel="noopener noreferrer"&gt;this&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Next steps
&lt;/h2&gt;

&lt;p&gt;We have a functioning report that produces useful information! That's a good start. However, there are many things to look at next, such as:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The datasets that we want to run the report are currently hard-coded into the script. How can we turn this into a more useful, flexible tool?&lt;/li&gt;
&lt;li&gt;It's hard to dig into the tables in any meaningful fashion - the output is very static. Can we improve the model such that we can filter and tweak the data?&lt;/li&gt;
&lt;li&gt;The report looks &lt;em&gt;exceedingly&lt;/em&gt; dull. How can we spruce it up?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;There's always more work to be done.&lt;/p&gt;

</description>
      <category>python</category>
      <category>git</category>
      <category>beginners</category>
      <category>planning</category>
    </item>
    <item>
      <title>Automatic Reporting in Python - Part 1: From Planning to Hello World</title>
      <dc:creator>goyder</dc:creator>
      <pubDate>Sat, 26 May 2018 09:49:52 +0000</pubDate>
      <link>https://dev.to/goyder/automatic-reporting-in-python---part-1-from-planning-to-hello-world-32n1</link>
      <guid>https://dev.to/goyder/automatic-reporting-in-python---part-1-from-planning-to-hello-world-32n1</guid>
      <description>&lt;p&gt;I'd like to document and step through the execution of a simple concept in Python: creating an automatic HTML reporting tool.&lt;/p&gt;

&lt;p&gt;The outcome would be a single stand-alone HTML file. An HTML file is a great reporting tool: even stripped of a back-end server, you can package up a good volume of info in a page and have handy-dandy interactivity.&lt;/p&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%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Ft3ikmh861byddzvvczmd.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%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Ft3ikmh861byddzvvczmd.png" width="543" height="600"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now, to be fair: this is not a terribly exciting or sexy project. All the same, automatically producing reports is a fantastic trick to have up your sleeve, especially in business environments. &lt;a href="https://automatetheboringstuff.com/" rel="noopener noreferrer"&gt;Automate the boring stuff,&lt;/a&gt; as they say.&lt;/p&gt;

&lt;p&gt;Personally, I mainly intend to use this to automate reporting of model performance in the machine learning space, but you could of course use this for any context! &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Naturally, tools like this already exist&lt;/strong&gt;. But I want to make my own, for the experience and flexibility it offers me.&lt;/p&gt;

&lt;h3&gt;
  
  
  How is this guide presented?
&lt;/h3&gt;

&lt;p&gt;Rather than just providing a dump of code that works, I'd like to incrementally work through the development, step-by-step. &lt;a href="https://www.youtube.com/watch?v=l1_bp8YKUPU" rel="noopener noreferrer"&gt;You'll see what I see - code what I code!&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Personally, I've always found this kind of tutorial more effective as:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;It gives an insight of how someone else approaches a problem&lt;/li&gt;
&lt;li&gt;It actually reflects how I code - feature by feature, commit by commit.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Who is this for?
&lt;/h3&gt;

&lt;p&gt;A user "knee-deep" in Python is assumed. I'll not run over standard syntax and the like, but I'll be sure to stop and explain anything particularly tricky. Wherever possible, I'll refer back to cleverer and clearer explanations.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step Zero - Stop for a minute
&lt;/h2&gt;

&lt;p&gt;I'm actually a chemical engineer by background. While I certainly gained and applied a lot of useful and/or esoteric knowledge from this background, by far the most useful bit of advice I ever learnt was probably also the most simple:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;When approaching a new problem, draw a picture.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;By "draw a picture", I mean to externalise the problem. Sketch it out and get thoughts and ideas out on paper.&lt;/p&gt;

&lt;p&gt;Before tackling this (relatively small-scale) project, I sat down and spent 10-15 minutes sketching out how I thought I might approach it, what parts would move, what parts would stay static, and a (very) rough structure of the project. &lt;/p&gt;

&lt;p&gt;My own nigh-incomprehensible sketch is as follows.&lt;/p&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%2Fthepracticaldev.s3.amazonaws.com%2Fi%2F5mpuuejc6kmc2wbj0z0q.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%2Fthepracticaldev.s3.amazonaws.com%2Fi%2F5mpuuejc6kmc2wbj0z0q.png" width="560" height="641"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Some key learnings from early planning
&lt;/h3&gt;

&lt;p&gt;Based on my early "planning", some things came clear:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;An &lt;a href="https://en.wikipedia.org/wiki/Web_template_system" rel="noopener noreferrer"&gt;HTML templating system&lt;/a&gt; makes a lot of sense. These systems are designed to input dynamic content into static template: this sounds exactly like what we're after. In Python, &lt;a href="http://jinja.pocoo.org/" rel="noopener noreferrer"&gt;Jinja&lt;/a&gt; is a common choice, and may be familiar to anyone who has played with &lt;a href="http://flask.pocoo.org/" rel="noopener noreferrer"&gt;Flask&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;In the machine-learning space, I'm constantly finding new things I might want to look out for, or new ways to compare models, or new ways to analyse a dataset - so both the &lt;em&gt;content of a report&lt;/em&gt; and the &lt;em&gt;features I can pack into any report&lt;/em&gt; should be flexible.&lt;/li&gt;
&lt;li&gt;A lot of the data I work with is in .csv format, so having tools to work with that and generate interrogatable HTML blocks from it would be neat.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Step One - Lay a foundation
&lt;/h2&gt;

&lt;p&gt;For pretty much any size project bigger than tinkering in the command line for a couple of minutes, I'm fond of setting up a Github repo and a consistent environment. &lt;/p&gt;

&lt;p&gt;This, I think, will be a pretty small-scale project, so I'm not terribly concerned with laying down a &lt;a href="http://docs.python-guide.org/en/latest/writing/structure/" rel="noopener noreferrer"&gt;full project structure&lt;/a&gt;, but it's worth being comfortable with this for further reference.&lt;/p&gt;

&lt;p&gt;This:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Encourages good habits in keeping the project clean&lt;/li&gt;
&lt;li&gt;Helps me jump from my Mac laptop to my Linux desktop (or any other environment, for that matter) with a minimum of fuss&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A few quick goals to start us off:&lt;/p&gt;

&lt;h3&gt;
  
  
  Set up a virtual environment
&lt;/h3&gt;

&lt;p&gt;We want to create an isolated instance of Python for this project - or "virtual environment" - rather than using whatever general installation is present on this machine. This isolates our dependencies and makes it easier to move the project around. I'm working in Pycharm, which gives you the option to create new virtual environment from the start.&lt;/p&gt;

&lt;p&gt;To make this transferable, I'll keep a &lt;code&gt;requirements.txt&lt;/code&gt; file in the root of my project directory which details what packages are required in the environment.&lt;/p&gt;

&lt;p&gt;There are &lt;a href="https://docs.python.org/3/tutorial/venv.html" rel="noopener noreferrer"&gt;many&lt;/a&gt;, &lt;a href="https://realpython.com/python-virtual-environments-a-primer/" rel="noopener noreferrer"&gt;many&lt;/a&gt;, &lt;a href="http://docs.python-guide.org/en/latest/dev/virtualenvs/" rel="noopener noreferrer"&gt;many&lt;/a&gt; guides on this topic, and they're good to refer to for more specific details. &lt;/p&gt;

&lt;h3&gt;
  
  
  Set up a Git/Github repo
&lt;/h3&gt;

&lt;p&gt;To implement version tracking and allow ready transport to and fro from Github, we'll initialise a Git repo locally (in the project folder) and create a new repo in Github. The local Git repo can then pushed to Github. &lt;/p&gt;

&lt;p&gt;A succint guide can be found &lt;a href="https://help.github.com/articles/adding-an-existing-project-to-github-using-the-command-line/" rel="noopener noreferrer"&gt;on Github.com&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;As a matter of course of working in a Git/Github repo, we'll add the following to the root project directory:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A &lt;code&gt;readme.md&lt;/code&gt; file that briefly describes the project, and&lt;/li&gt;
&lt;li&gt;A &lt;code&gt;.gitignore&lt;/code&gt; file that indicates which files and folders should not be tracked by Git and pushed to Github. This might include automatically generated files (from the IDE, or from the virtual environment, for instance), sensitive files and config data, or anything else you've created locally that doesn't need to be shared with the world. For me, this looks like:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# .gitignore

# Don't add the virtual environment, IDE, and Jupyter notebook info
venv
.idea
.ipynb_checkpoints
Write-up.ipynb
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Github status
&lt;/h3&gt;

&lt;p&gt;At this point, the skeleton of your project should look a bit like &lt;a href="https://github.com/goyder/autoreporting/tree/c1f1ba68cae351d0de851f0f4831b0f2caa0a407" rel="noopener noreferrer"&gt;this&lt;/a&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step Two - "Hello, World" templating
&lt;/h2&gt;

&lt;p&gt;At this point, we have a very simple project structure that we can start populating with code and templates. To carry out a bit of a smoke-test and give me somewhere to build off of, I'd like to create the simplest possible report.&lt;/p&gt;

&lt;p&gt;This first implementation should input no information, and output a simple "Hello, world!" HTML page.&lt;/p&gt;

&lt;h3&gt;
  
  
  A brief primer on templating
&lt;/h3&gt;

&lt;p&gt;This project is built on the concept of templating and &lt;a href="https://en.wikipedia.org/wiki/Template_processor" rel="noopener noreferrer"&gt;template processors&lt;/a&gt;. This is an enormous but very crucial field in web publishing.&lt;/p&gt;

&lt;p&gt;In a crude explanation: we separate the &lt;em&gt;structure&lt;/em&gt; of an HTML page and the &lt;em&gt;content&lt;/em&gt; that is to be published. When it comes time to generate a new webpage (or in this context, a report!) we can input the content into a standardised structure and get a new webpage out. &lt;/p&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%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Fze46l15yyxhmsrp722xf.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%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Fze46l15yyxhmsrp722xf.jpg" width="370" height="640"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Templating is well and truly ubiquitous now, but if you'd like an example of life without consistent manual or automatic templating, you can find many unironically charming examples in a &lt;a href="http://www.oocities.org/" rel="noopener noreferrer"&gt;Geocities archive&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Building 'hello, world' functionality
&lt;/h3&gt;

&lt;p&gt;Building on this distinction between &lt;em&gt;structure&lt;/em&gt; and &lt;em&gt;content&lt;/em&gt;, we can create very simple examples for both to test the system.&lt;/p&gt;

&lt;h4&gt;
  
  
  Structure
&lt;/h4&gt;

&lt;p&gt;Our structure, in this case, is a near-empty HTML page with locations for data to be input. We'll call this a "template", and we'll store this and others under a &lt;code&gt;template&lt;/code&gt; folder in our root project directory. We'll create a new HTML file under this folder - &lt;code&gt;{project_folder}/template/report.html&lt;/code&gt;. The contents of this file are as follows:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="cp"&gt;&amp;lt;!DOCTYPE html&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;html&lt;/span&gt; &lt;span class="na"&gt;lang=&lt;/span&gt;&lt;span class="s"&gt;"en"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;head&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;meta&lt;/span&gt; &lt;span class="na"&gt;charset=&lt;/span&gt;&lt;span class="s"&gt;"UTF-8"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;title&amp;gt;&lt;/span&gt;{{ content }}&lt;span class="nt"&gt;&amp;lt;/title&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/head&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;body&amp;gt;&lt;/span&gt;
{{ content }}
&lt;span class="nt"&gt;&amp;lt;/body&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/html&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Note the elements here within curly braces (in this case &lt;em&gt;double&lt;/em&gt; curly braces - &lt;code&gt;{{&lt;/code&gt; and &lt;code&gt;}}&lt;/code&gt;). This is where dynamic content will be entered. &lt;/p&gt;

&lt;p&gt;Template processors such as Jinja are very powerful and allow many features to be present in templates beyond simple insertion. Take a look at the first example on the &lt;a href="http://jinja.pocoo.org/docs/2.10/" rel="noopener noreferrer"&gt;Jinja homepage&lt;/a&gt; and see if you can make sense of it.&lt;/p&gt;

&lt;h4&gt;
  
  
  Content
&lt;/h4&gt;

&lt;p&gt;Our content, for now, can be as simple as &lt;code&gt;hello, world!&lt;/code&gt;. &lt;/p&gt;

&lt;p&gt;We'll create a new file in the root of the project directory called &lt;code&gt;{project_folder}/autoreporting.py&lt;/code&gt; and hardcode our 'content' to begin with.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;content&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Hello, world!&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Functionality
&lt;/h4&gt;

&lt;p&gt;We have structure and content - now we just need to get them combined using Jinja.&lt;/p&gt;

&lt;p&gt;Firstly, create an &lt;code&gt;outputs&lt;/code&gt; directory and add it to your &lt;code&gt;.gitignore&lt;/code&gt; file. We'll dump what we create in this folder, but there's no reason to share whatever ends up in here in your repo.&lt;/p&gt;

&lt;p&gt;Jinja requires an &lt;code&gt;Environment&lt;/code&gt; object to store and define things like the config and loader - i.e. where we will pull our templates from. In this case, we're running off the local filesystem, so a &lt;code&gt;FileSystemLoader&lt;/code&gt; specified to search in the &lt;code&gt;templates&lt;/code&gt; directory will do us just fine. &lt;/p&gt;

&lt;p&gt;With our &lt;code&gt;env&lt;/code&gt; instance, we can render our basic template and write this to file.&lt;/p&gt;

&lt;p&gt;Our &lt;code&gt;autoreporting.py&lt;/code&gt; script now looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;jinja2&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;FileSystemLoader&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Environment&lt;/span&gt;

&lt;span class="c1"&gt;# Content to be published
&lt;/span&gt;&lt;span class="n"&gt;content&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Hello, world!&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;

&lt;span class="c1"&gt;# Configure Jinja and ready the template
&lt;/span&gt;&lt;span class="n"&gt;env&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Environment&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;loader&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nc"&gt;FileSystemLoader&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;searchpath&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;templates&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;template&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get_template&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;report.html&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;
    Entry point for the script.
    Render a template and write it to file.
    :return:
    &lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
    &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="nf"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;outputs/report.html&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;w&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;write&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;template&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;render&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;content&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;content&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;


&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;__name__&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;__main__&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Note the argument to &lt;code&gt;template.render&lt;/code&gt;, &lt;code&gt;content=content&lt;/code&gt;. This links the template and our code. The first &lt;code&gt;content&lt;/code&gt; refers to the &lt;code&gt;{{ content }}&lt;/code&gt; in the template, while the second is the string defined in the script. We define this relationship and the string is fed into the template to make a substitution.&lt;/p&gt;

&lt;h3&gt;
  
  
  Results so far
&lt;/h3&gt;

&lt;p&gt;If you run this script and all goes well, a new &lt;code&gt;report.html&lt;/code&gt; file will be written under &lt;code&gt;outputs/&lt;/code&gt;. Open this file and behold what you have created!&lt;/p&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%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Ftwx3hgldja5dxwjket0e.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%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Ftwx3hgldja5dxwjket0e.png" width="800" height="322"&gt;&lt;/a&gt;&lt;br&gt;
This certainly meets the criteria of "very simple, but functional."&lt;/p&gt;

&lt;p&gt;If you inspect the HTML of the page, you'll note that the &lt;code&gt;{{ content }}&lt;/code&gt; tags in the template were neatly replaced by the &lt;code&gt;Hello, world!&lt;/code&gt; string, which is the crux of this affair.&lt;/p&gt;

&lt;h2&gt;
  
  
  Github status
&lt;/h2&gt;

&lt;p&gt;You've developed a new feature, so it's a good point to commit and push to Github. &lt;/p&gt;

&lt;p&gt;Your repo should probably look a bit like &lt;a href="https://github.com/goyder/autoreporting/tree/271790cb0e6ed8d2e07b585fa43d03b1e1fa5b1b" rel="noopener noreferrer"&gt;this&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Next Steps
&lt;/h2&gt;

&lt;p&gt;The reporter walks and works, but we certainly don't have any sort of meaningful input or flexibility at this point. Our next steps will be to explore how we can read in data and report on it, and start working with JavaScript and CSS.&lt;/p&gt;

</description>
      <category>python</category>
      <category>git</category>
      <category>beginners</category>
      <category>planning</category>
    </item>
  </channel>
</rss>
