<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>Forem: Aly Sivji</title>
    <description>The latest articles on Forem by Aly Sivji (@alysivji).</description>
    <link>https://forem.com/alysivji</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%2F25172%2Fcd548863-b63f-4f90-b097-1f2396ab97d6.jpeg</url>
      <title>Forem: Aly Sivji</title>
      <link>https://forem.com/alysivji</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/alysivji"/>
    <language>en</language>
    <item>
      <title>Testing 101: Introduction to Testing</title>
      <dc:creator>Aly Sivji</dc:creator>
      <pubDate>Mon, 22 Oct 2018 18:43:24 +0000</pubDate>
      <link>https://forem.com/alysivji/testing-101-introduction-to-testing-5hdk</link>
      <guid>https://forem.com/alysivji/testing-101-introduction-to-testing-5hdk</guid>
      <description>&lt;p&gt;&lt;em&gt;This &lt;a href="https://alysivji.github.io/testing-101-introduction-to-testing.html" rel="noopener noreferrer"&gt;post&lt;/a&gt; was originally published on &lt;a href="https://alysivji.github.io/" rel="noopener noreferrer"&gt;Siv Scripts&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Programming is writing code to solve problems. Software Engineering is the practice of using a &lt;strong&gt;structured process&lt;/strong&gt; to solve problems. As engineers, we want to have a codebase we can change, extend, and refactor as required. Tests ensure our program works as intended and that changes to the codebase do not break existing functionality.&lt;/p&gt;

&lt;p&gt;At my last job, I worked with a Senior Engineer to build out a microservices-based backend to replace our existing Django monolith. It was a greenfield project and we were encouraged to try new things. I was reading &lt;a href="https://pragprog.com/book/bopytest/python-testing-with-pytest" rel="noopener noreferrer"&gt;Python Testing with pytest&lt;/a&gt; and convinced the Senior Engineer to let me bring &lt;a href="http://pytest.org/" rel="noopener noreferrer"&gt;pytest&lt;/a&gt; into our project. This was fortuitous as it forced me to take the lead in writing the initial set of tests we used as a template for all of our services.&lt;/p&gt;

&lt;p&gt;This experience reinforced the principles highlighted in &lt;a href="https://en.wikipedia.org/wiki/The_Pragmatic_Programmer" rel="noopener noreferrer"&gt;The Pragmatic Programmer&lt;/a&gt;. It's about being pragmatic in &lt;strong&gt;what&lt;/strong&gt; we test, &lt;strong&gt;how&lt;/strong&gt; we test, and &lt;strong&gt;when&lt;/strong&gt; we test; we should leverage tools and techniques that allow us to test our code as efficiently as possible. Testing needs to be easy and free of barriers; once testing feels like a chore, programmers won't do it... and this is how software quality slips.&lt;/p&gt;

&lt;p&gt;We dread going into the code because either there are no tests or the tests that exist are so brittle that we're forced to rewrite tests as we write code. This is not what Software Engineering is about. Test should enable refactoring, not hamper our ability to make changes to the codebase. We should spend our time writing business logic, not wrestling with tests.&lt;/p&gt;

&lt;p&gt;Testing is folklore in the sense that best practices and techniques are passed down from programmer to programmer while working on projects as part of a team. If you are new to the industry and are trying to grok testing, it's hard to figure out how to get started. It feels like there is a lot of conflicting advice out there, and that's because there is. Testing is opinionated, more-so than any other software engineering discipline. Folks are always arguing about what to test, how to test, and &lt;strong&gt;especially&lt;/strong&gt; when to test.&lt;/p&gt;

&lt;p&gt;This is the first in a series of posts that details my thought process for how I go about adding tests to a codebase. In this post, I provide a broad introduction to the world of testing so we can have a common vocabulary for future posts.&lt;/p&gt;




&lt;h4&gt;
  
  
  Table of Contents
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;What is Testing&lt;/li&gt;
&lt;li&gt;Benefits of Testing&lt;/li&gt;
&lt;li&gt;Black Box vs White Box&lt;/li&gt;
&lt;li&gt;Test Pyramid&lt;/li&gt;
&lt;li&gt;Structuring Tests&lt;/li&gt;
&lt;li&gt;What to Test&lt;/li&gt;
&lt;li&gt;When to Write Tests&lt;/li&gt;
&lt;li&gt;Conclusion&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;a&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What is Testing
&lt;/h2&gt;

&lt;p&gt;When we write code, we need to run it to ensure that it is doing what we expect it to. Tests are a contract with our code: given a value, we expect a certain result to be returned.&lt;/p&gt;

&lt;p&gt;Running tests can be thought of as a feedback mechanism that informs us if our program works as intended:&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%2Falysivji.github.io%2Fimages%2F30-39%2Ffeedback_loop.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%2Falysivji.github.io%2Fimages%2F30-39%2Ffeedback_loop.jpg" alt="Tests provide a feedback mechanism" width="789" height="493"&gt;&lt;/a&gt;
  &lt;/p&gt;



&lt;p&gt;While passing tests &lt;a href="http://wiki.c2.com/?TestsCantProveTheAbsenceOfBugs" rel="noopener noreferrer"&gt;cannot prove the absence bugs&lt;/a&gt;, they do inform us that our code is working in the manner defined by the test. In contrast, a failing test indicates that something is not right. We need to understand why our test failed so we can modify code and/or tests, as required.&lt;/p&gt;
&lt;h3&gt;
  
  
  Properties of Tests
&lt;/h3&gt;
&lt;h4&gt;
  
  
  1. Fast
&lt;/h4&gt;

&lt;p&gt;Tests give us confidence that our code is working as intended. A slower feedback loop hampers development as it takes us longer to find out if our change was correct. If our workflow is plagued by slow tests, we won't be running them as often. This will lead to problems down the line.&lt;/p&gt;

&lt;p&gt;&lt;a&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h4&gt;
  
  
  2. Deterministic
&lt;/h4&gt;

&lt;p&gt;Tests should be &lt;a href="https://en.wikipedia.org/wiki/Deterministic_system" rel="noopener noreferrer"&gt;deterministic&lt;/a&gt;, i.e. the same input will always result in the same output. If tests are non-deterministic, we have to find a way to account for random behavior inside of our tests.&lt;/p&gt;

&lt;p&gt;While there is definitely non-deterministic code in production (i.e. Machine Learning and AI), we should try to make all our non-probabilistic code as deterministic as possible. There is no point of doing additional work unless our program requires it.&lt;/p&gt;

&lt;p&gt;&lt;a&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h4&gt;
  
  
  3. Automated
&lt;/h4&gt;

&lt;p&gt;We can confirm our program works by running it. This could be manually running a command in the &lt;a href="https://en.wikipedia.org/wiki/Read%E2%80%93eval%E2%80%93print_loop" rel="noopener noreferrer"&gt;REPL&lt;/a&gt; or refreshing a webpage; in both cases, we are looking to see if our program does what it is supposed to do. While manual testing is fine for small projects, it becomes unmanageable as our project grows in complexity.&lt;/p&gt;

&lt;p&gt;By automating our test suite, we can quickly verify our program works on-demand. Some developers even have their tests triggered to run on file save.&lt;/p&gt;
&lt;h3&gt;
  
  
  Formal Definition
&lt;/h3&gt;

&lt;p&gt;Let's go over some definitions so we have a common vocabulary going forward.&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%2Falysivji.github.io%2Fimages%2F30-39%2Fsystem_under_test.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%2Falysivji.github.io%2Fimages%2F30-39%2Fsystem_under_test.jpg" alt="Diagram of a system under test" width="489" height="388"&gt;&lt;/a&gt;
&lt;/p&gt;

&lt;p&gt;A &lt;strong&gt;System Under Test (SUT)&lt;/strong&gt; is the entity that is currently being tested. This could be a line of code, a method, or an entire program.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Acceptance Criteria&lt;/strong&gt; refers to the check we perform that allows us to accept output from the system. The specificity and range of acceptance criteria depends on what we are testing: medical device and aerospace require tests to be specific as there is a lot less room for error.&lt;/p&gt;

&lt;p&gt;If Amazon makes a bad recommendation, it's not the end of the world. If IBM's Watson suggests the wrong surgery, it can be life-threatening.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Testing&lt;/strong&gt; refers to the process of entering Inputs into our System Under Test and validating Outputs against our Acceptance Criteria:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;If output is okay, our test passes.&lt;/li&gt;
&lt;li&gt;If output is not okay, our test fails and we have to debug.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Hopefully the test failure provides enough contextual information for us to find out where to look.&lt;/p&gt;




&lt;p&gt;&lt;a&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Benefits of Testing
&lt;/h2&gt;

&lt;p&gt;A well-thought-out testing strategy paired with thorough test cases provides the following benefits:&lt;/p&gt;

&lt;h4&gt;
  
  
  Modify Code with Confidence
&lt;/h4&gt;

&lt;p&gt;If a program does anything of interest, it has interactions between functions, classes, and modules. This means a single line change can break our program in unexpected ways. Tests give us confidence in our code. By running our tests after we modify our code, we can confirm our changes did not break existing functionality as defined by our tests.&lt;/p&gt;

&lt;p&gt;In contrast, modifying a code base without tests is a challenge. There is no way of knowing if things are working as intended. We are programming by the seat of our pants, which is quite a risky proposition.&lt;/p&gt;

&lt;h4&gt;
  
  
  Identify Bugs Early
&lt;/h4&gt;

&lt;p&gt;Bugs cost money. How much depends on when you find them.&lt;/p&gt;

&lt;p&gt;Fixing bugs gets more expensive the further you are in the &lt;a href="https://en.wikipedia.org/wiki/Systems_development_life_cycle" rel="noopener noreferrer"&gt;Software Development Life Cycle&lt;/a&gt; (SDLC). &lt;a href="http://blog.celerity.com/the-true-cost-of-a-software-bug" rel="noopener noreferrer"&gt;True Cost of a Software Bug&lt;/a&gt; digs into this issue.&lt;/p&gt;

&lt;h4&gt;
  
  
  Improve System Design
&lt;/h4&gt;

&lt;p&gt;This one is a bit controversial, but I think writing code with tests in mind improves system design. A thorough test suite shows that the developer has actually thought about the problem in some depth. Writing tests &lt;a href="https://en.wikipedia.org/wiki/Eating_your_own_dog_food" rel="noopener noreferrer"&gt;forces you to use your own API&lt;/a&gt;; this hopefully results in a better interface.&lt;/p&gt;

&lt;p&gt;All projects have time constraints and it's quite easy to get into the habit of taking shortcuts that increase coupling between modules leading to complex interdependencies. We have to be cognizant of solving problems with &lt;a href="https://en.wikipedia.org/wiki/Spaghetti_code" rel="noopener noreferrer"&gt;spaghetti code&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Knowing we have to test our code forces us to write modular code. If something is clunky to test, there might be a better interface we can implement. Taking the time to write tests forces mindfulness upon us; we take a deep breath before looking at the problem from the perspective of a user.&lt;/p&gt;

&lt;p&gt;Once you write testable code by using patterns like &lt;a href="https://en.wikipedia.org/wiki/Dependency_injection" rel="noopener noreferrer"&gt;dependency injection&lt;/a&gt;, you'll see how adding structure makes it easier to verify our code is doing what we expect it to.&lt;/p&gt;




&lt;p&gt;&lt;a&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Black Box vs White Box
&lt;/h2&gt;

&lt;p&gt;Tests can be broadly classified into two broad categories: black box testing and white box testing.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Black Box Testing&lt;/strong&gt; refers to testing techniques in which the tester cannot see the inner workings of the item being tested.&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%2Falysivji.github.io%2Fimages%2F30-39%2Fblack_box_testing.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%2Falysivji.github.io%2Fimages%2F30-39%2Fblack_box_testing.jpg" alt="Picture showing input and output going into a blackbox" width="800" height="338"&gt;&lt;/a&gt;
&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;White Box Testing&lt;/strong&gt; is the technique in which the tester can see the inner workings of the item being tested.&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%2Falysivji.github.io%2Fimages%2F30-39%2Fwhite_box_testing.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%2Falysivji.github.io%2Fimages%2F30-39%2Fwhite_box_testing.jpg" alt="Picture showing input and output going into a transparent box where we can see how information flows" width="800" height="338"&gt;&lt;/a&gt;
&lt;/p&gt;

&lt;p&gt;As developers, we perform white box testing. We wrote the code inside of the box and know how to test it thoroughly. This is not to say that there is not a need for black box testing, we should still have somebody perform testing at a higher level; proximity to the code can lead to blind spots in our tests.&lt;/p&gt;




&lt;p&gt;&lt;a&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Test Pyramid
&lt;/h2&gt;

&lt;p&gt;The Automated Test Pyramid provides guidance on how to structure our testing strategy. It says we should write lots of fast and cheap unit tests and a small number of slow and expensive end-to-end tests.&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%2Falysivji.github.io%2Fimages%2F30-39%2Ftest_pyramid.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%2Falysivji.github.io%2Fimages%2F30-39%2Ftest_pyramid.png" alt="Picture showing automated testing pyramid" width="800" height="472"&gt;&lt;/a&gt;
&lt;/p&gt;

&lt;p&gt;The Test Pyramid is not a hard and fast rule, but it provides a good place to start thinking about a testing strategy. A good rule of thumb is to write as many tests at each level as you need to have confidence in your system. We should be writing tests as we write code, iterating towards a testing strategy that works for the project we are working on.&lt;/p&gt;

&lt;h3&gt;
  
  
  Unit Tests
&lt;/h3&gt;

&lt;p&gt;Unit tests are low-level tests that focus on testing a specific part of our system. They are cheap to write and fast to run. Test failures should provide enough contextual information to pinpoint the source of the error. These tests are typically written by developers during the Implementation phase of the &lt;a href="https://en.wikipedia.org/wiki/Systems_development_life_cycle" rel="noopener noreferrer"&gt;Software Development Life Cycle&lt;/a&gt; (SDLC).&lt;/p&gt;

&lt;p&gt;Unit tests should be independent and isolated; interacting with external components increases both the scope of our tests and the time it takes for tests to run. As we will see in a future post, replacing dependencies with &lt;a href="https://martinfowler.com/bliki/TestDouble.html" rel="noopener noreferrer"&gt;test doubles&lt;/a&gt; results in deterministic tests that are quick to run.&lt;/p&gt;

&lt;p&gt;How big should our unit test be? Like everything else in programming, it depends on what we are trying to do. Thinking in terms of a &lt;a href="https://www.agileconnection.com/article/3-keys-mastering-test-driven-development" rel="noopener noreferrer"&gt;unit of behavior&lt;/a&gt; allows us to write tests around logical blocks of code.&lt;/p&gt;

&lt;p&gt;The Test Pyramid recommends having a lot of unit tests in our test suite. These tests give us confidence that our program works as expected. Writing new code or modifying existing code might require us to rewrite some of our tests. This is standard practice, our test suite grows with our code base.&lt;/p&gt;

&lt;p&gt;Try to be cognizant of our test suite growing in complexity. Remember, &lt;strong&gt;code that tests our production code is also production code&lt;/strong&gt;. Take the time to refactor your tests to ensure they are efficient and effective.&lt;/p&gt;

&lt;h4&gt;
  
  
  Unit Test Example
&lt;/h4&gt;

&lt;p&gt;Suppose we have the following function that takes a list of words and returns the most common word and the number of occurrences of that word:&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;find_top_word&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;words&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="c1"&gt;# Return most common word &amp;amp; occurrences
&lt;/span&gt;    &lt;span class="n"&gt;word_counter&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Counter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;words&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;word_counter&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;most_common&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We can test this function by creating a list, running the &lt;code&gt;find_top_word&lt;/code&gt; function over that list and comparing the results of the function to the value we expect:&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;test_find_top_word&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="n"&gt;words&lt;/span&gt; &lt;span class="o"&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;foo&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;bar&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;bat&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;baz&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;foo&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;baz&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;foo&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;find_top_word&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;words&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;foo&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If we ever wanted to change the implementation of &lt;code&gt;find_top_words&lt;/code&gt;, we can do it without fear. Our test ensures that the functionality of &lt;code&gt;find_top_word&lt;/code&gt; cannot change without causing a test failure.&lt;/p&gt;

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

&lt;p&gt;Every complex application has internal and external components working together to do something interesting. In contrast to units tests which focus on individual components, integration tests combine various parts of the system and test them together as a group. Integration testing can also refer to testing at service boundaries of our application, i.e. when it goes out to the database, file system, or external API.&lt;/p&gt;

&lt;p&gt;These tests are typically written by developers, but they don't have to be. By definition, integration tests are larger in scope and take longer to run than unit tests. This means that test failures require some investigation: we know that one of the components in our test is not working, but the failure's exact location needs to be found. This is in contrast to unit tests which are smaller in scope and indicate exactly where things have failed.&lt;/p&gt;

&lt;p&gt;We should try to run integration tests in a production-like environment; this minimizes the chance that tests fail due to differences in configuration.&lt;/p&gt;

&lt;h4&gt;
  
  
  Integration Test Example
&lt;/h4&gt;

&lt;p&gt;Suppose we have the following function that takes in a URL and a tuple of &lt;code&gt;(word, occurrences)&lt;/code&gt;. Our function creates a records and saves it to the database:&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;save_to_db&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;top_word&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;record&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;TopWord&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;record&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;url&lt;/span&gt;
    &lt;span class="n"&gt;record&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;word&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;top_word&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;record&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;num_occurrences&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;top_word&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

    &lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;session&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;record&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;session&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;commit&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;record&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We test this function by passing in known information; the function should save the information we entered into the database. Our test code pulls the newly saved record from the database and confirms its fields match the input we passed in.&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;test_save_to_db&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="n"&gt;url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;http://test_url.com&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="n"&gt;most_common_word_details&lt;/span&gt; &lt;span class="o"&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;Python&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;42&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;word&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;save_to_db&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;most_common_word_details&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;inserted_record&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;TopWord&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;query&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;word&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="n"&gt;inserted_record&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;http://test_url.com&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="n"&gt;inserted_record&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;word&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Python&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="n"&gt;inserted_record&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;num_occurrences&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;42&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Notice how this is the kind of testing we do manually to confirm things are working as expected. Automating this test saves us from having to repeatedly check this functionality each time we make a change to the code.&lt;/p&gt;

&lt;h3&gt;
  
  
  End-to-End
&lt;/h3&gt;

&lt;p&gt;End-to-end tests check to see if the system meets our defined business requirements. A common test is to trace a path through the system in the same manner a user would experience. For example, we can test a new user workflow: simulate creating an account, "clicking" the link in the activate email, logging-in for the first time, and interacting with our web application's tutorial modal pop-up.&lt;/p&gt;

&lt;p&gt;We can conduct end-to-end tests through our user interface (UI) by leveraging a browser automation tool like &lt;a href="https://selenium-python.readthedocs.io/" rel="noopener noreferrer"&gt;Selenium&lt;/a&gt;. This creates a dependency between our UI and our tests, which makes our tests brittle: a change to the front-end requires us to change tests. This is not sustainable as either our front-end will become static or our tests will not be run.&lt;/p&gt;

&lt;p&gt;A better solution is to test the subcutaneous layer, i.e. the layer just below our user interface. For a web application, this would be testing the REST API, both sending in JSON and getting JSON out.&lt;/p&gt;

&lt;p&gt;Our &lt;a href="https://martinfowler.com/bliki/SubcutaneousTest.html" rel="noopener noreferrer"&gt;subcutaneous tests&lt;/a&gt; are our contracts with our front-end; they can be used by our front-end developers as a specification of the REST API. Tools, like &lt;a href="https://github.com/meqaio/swagger_meqa" rel="noopener noreferrer"&gt;swagger-meqa&lt;/a&gt;, that are built on top of the OpenAPI Specification can help us automate this process. We could also full-featured tools like &lt;a href="https://www.getpostman.com/" rel="noopener noreferrer"&gt;Postman&lt;/a&gt; to test, debug, and validate our API.&lt;/p&gt;

&lt;p&gt;End-to-end tests are considered black box as we do not need to know anything about the implementation in order to conduct testing. This also means that test failures provide no indication of what went wrong; we would need to use logs to help us trace the error and diagnose system failure.&lt;/p&gt;

&lt;h4&gt;
  
  
  End-to-End Test Example
&lt;/h4&gt;

&lt;p&gt;Here we are using the &lt;a href="http://flask.pocoo.org/docs/1.0/api/#flask.Flask.test_client" rel="noopener noreferrer"&gt;Flask Test client&lt;/a&gt; to run subcutaneous testing on our REST API. There are a lot of things happening behind the scene and the result we get back (HTTP status code) lets us know that the test either passed or failed.&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;test_end_to_end&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="n"&gt;client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;test_client&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="n"&gt;body&lt;/span&gt; &lt;span class="o"&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;url&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;https://www.python.org&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/top-word&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;body&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;status_code&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;HTTPStatus&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;OK&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Resources
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;Martin Fowler Wiki: &lt;a href="https://martinfowler.com/bliki/TestPyramid.html" rel="noopener noreferrer"&gt;TestPyramid&lt;/a&gt; | &lt;a href="https://martinfowler.com/bliki/UnitTest.html" rel="noopener noreferrer"&gt;UnitTest&lt;/a&gt; | &lt;a href="https://martinfowler.com/bliki/ComponentTest.html" rel="noopener noreferrer"&gt;IntegrationTest&lt;/a&gt; | &lt;a href="https://martinfowler.com/bliki/BroadStackTest.html" rel="noopener noreferrer"&gt;EndToEndTest&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Google Testing Blog: &lt;a href="https://testing.googleblog.com/2015/04/just-say-no-to-more-end-to-end-tests.html" rel="noopener noreferrer"&gt;Just Say No to More End-to-End Tests&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Automation Panda: &lt;a href="https://automationpanda.com/2018/08/01/the-testing-pyramid/" rel="noopener noreferrer"&gt;The Testing Pyramid&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;a&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Structuring Tests
&lt;/h2&gt;

&lt;p&gt;Each test case can be separated into the following phases:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;setting up the system under test (SUT) to the environment required by the test case (pre-conditions)&lt;/li&gt;
&lt;li&gt;performing the action we want to test on SUT&lt;/li&gt;
&lt;li&gt;verifying if the expected outcome occurred (post-conditions)&lt;/li&gt;
&lt;li&gt;tearing down SUT and putting the environment back to the state we found it in&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;There are two widely used frameworks for structuring tests: Arrange-Act-Assert and Given-When-Then.&lt;/p&gt;

&lt;h3&gt;
  
  
  Arrange-Act-Assert (AAA)
&lt;/h3&gt;

&lt;p&gt;The AAA pattern is abstraction for separating the different part of our tests:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Arrange&lt;/strong&gt; all necessary pre-conditions&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Act&lt;/strong&gt; on the SUT&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Assert&lt;/strong&gt; that our post-conditions are met&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  Arrange-Act-Assert Example
&lt;/h4&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;test_find_top_word&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="c1"&gt;# Arrange
&lt;/span&gt;    &lt;span class="n"&gt;words&lt;/span&gt; &lt;span class="o"&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;foo&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;bar&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;bat&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;baz&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;foo&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;baz&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;foo&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

    &lt;span class="c1"&gt;# Act
&lt;/span&gt;    &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;find_top_word&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;words&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c1"&gt;# Assert
&lt;/span&gt;    &lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;foo&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The clear separation between the phases allows us to see if our test method is trying to test too many different things at once. Arrange-Act-Assert is the pattern I use when writing tests.&lt;/p&gt;

&lt;h3&gt;
  
  
  Given-When-Then (GWT)
&lt;/h3&gt;

&lt;p&gt;GWT provides a useful abstraction for separating the different phases of our test:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Given&lt;/strong&gt; a set of pre-conditions&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;When&lt;/strong&gt; we perform an action on the SUT&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Then&lt;/strong&gt; our post-conditions should be as follows&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;GWT is widely used in &lt;a href="https://en.wikipedia.org/wiki/Behavior-driven_development" rel="noopener noreferrer"&gt;Behavior Driven Development&lt;/a&gt; (BDD).&lt;/p&gt;

&lt;h4&gt;
  
  
  Given-When-Then Example
&lt;/h4&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;test_find_top_word&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="c1"&gt;# Given a list of word
&lt;/span&gt;    &lt;span class="n"&gt;words&lt;/span&gt; &lt;span class="o"&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;foo&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;bar&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;bat&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;baz&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;foo&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;baz&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;foo&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

    &lt;span class="c1"&gt;# When we run the function over the list
&lt;/span&gt;    &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;find_top_word&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;words&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c1"&gt;# Then we should see `foo` occurring 3 times
&lt;/span&gt;    &lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;foo&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Resources
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;Gerard Meszaros: &lt;a href="http://xunitpatterns.com/Four%20Phase%20Test.html" rel="noopener noreferrer"&gt;Four-Phase Test&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Martin Fowler Wiki: &lt;a href="https://martinfowler.com/bliki/GivenWhenThen.html" rel="noopener noreferrer"&gt;GivenWhenThen&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;C2 Wiki: &lt;a href="http://wiki.c2.com/?ArrangeActAssert" rel="noopener noreferrer"&gt;Arrange Act Assert&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;James Cooke: &lt;a href="https://jamescooke.info/arrange-act-assert-pattern-for-python-developers.html" rel="noopener noreferrer"&gt;Arrange Act Assert Pattern for Python Developers&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Automation Panda: &lt;a href="https://www.youtube.com/watch?v=EtIAbfCrsFI" rel="noopener noreferrer"&gt;Behavior-Driven Python&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;a&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What to Test
&lt;/h2&gt;

&lt;p&gt;In order to prove that our program is correct, we have to test it against every conceivable combination of input values. This type of exhaustive testing is not practical so we need to employ testing strategies that allow us to select test cases where errors are most likely to error.&lt;/p&gt;

&lt;p&gt;Seasoned developers can balance writing code to solve business problems with writing tests to ensure correctness and prevent &lt;a href="https://en.wikipedia.org/wiki/Regression_testing" rel="noopener noreferrer"&gt;regression&lt;/a&gt;. Finding this balance and knowing what to test can feel more like an art than a science. Fortunately, there are a few rules of thumb we can follow to make sure our testing is thorough.&lt;/p&gt;

&lt;h4&gt;
  
  
  Functional Requirements
&lt;/h4&gt;

&lt;p&gt;We want to make sure that all relevant requirements have been implemented. Our test cases should be detailed enough to check business requirements. There is no point building something if doesn't it meet the criteria you set forth.&lt;/p&gt;

&lt;h4&gt;
  
  
  Basis Path Testing
&lt;/h4&gt;

&lt;p&gt;We have to test each statement at least once. If the statement has a conditional (&lt;code&gt;if&lt;/code&gt; or &lt;code&gt;while&lt;/code&gt;), we have to vary our testing to make sure we test all branches of the conditional. For example, if we have the following code:&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;if&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;18&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="c1"&gt;# statement1
&lt;/span&gt;&lt;span class="k"&gt;elif&lt;/span&gt; &lt;span class="mi"&gt;18&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="mi"&gt;35&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="c1"&gt;# statement2
&lt;/span&gt;&lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="c1"&gt;# statement3
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To make sure we hit all branches of the above conditional, we need to write the following tests:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;code&gt;x &amp;lt; 18&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;18 &amp;lt;= x &amp;lt;= 35&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;x &amp;gt; 35&lt;/code&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h4&gt;
  
  
  Equivalence Partitioning
&lt;/h4&gt;

&lt;p&gt;Two test cases that result in the same output are said to be equivalent. We only require one of the test cases in order to cover that class of errors.&lt;/p&gt;

&lt;h4&gt;
  
  
  Boundary Analysis
&lt;/h4&gt;

&lt;p&gt;&lt;em&gt;"There are 2 hard problems in Computer Science: cache invalidation, naming things, and off-by-1 errors."&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;This is one of the &lt;a href="https://martinfowler.com/bliki/TwoHardThings.html" rel="noopener noreferrer"&gt;oldest jokes in programming&lt;/a&gt;, but there is a lot of truth behind it, we often confuse if we need a &lt;code&gt;&amp;lt;&lt;/code&gt; or a &lt;code&gt;&amp;lt;=&lt;/code&gt;. This is why we should always test the boundary conditions. Given the following example:&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;if&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;18&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="c1"&gt;# statement1
&lt;/span&gt;&lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="c1"&gt;# statement2
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To ensure we thoroughly test the boundary conditions of the code snippet above, we would to have test cases for &lt;code&gt;x=17&lt;/code&gt;, &lt;code&gt;x=18&lt;/code&gt;, and &lt;code&gt;x=19&lt;/code&gt;. Be aware that writing test cases becomes more complicated if our boundary has compound conditionals.&lt;/p&gt;

&lt;p&gt;This is a great &lt;a href="https://medium.freecodecamp.org/a-beginners-guide-to-testing-implement-these-quick-checks-to-test-your-code-d50027ad5eed" rel="noopener noreferrer"&gt;guide on testing boundary conditions&lt;/a&gt;.&lt;/p&gt;

&lt;h4&gt;
  
  
  Classes of Bad Data
&lt;/h4&gt;

&lt;p&gt;This refers to any of the the following cases:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Too little data (or no data)&lt;/li&gt;
&lt;li&gt;Too much data&lt;/li&gt;
&lt;li&gt;Invalid data&lt;/li&gt;
&lt;li&gt;Wrong size of data&lt;/li&gt;
&lt;li&gt;Uninitialized data&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  Data Flow Testing
&lt;/h4&gt;

&lt;p&gt;Focuses on tracing the control flow of the program with a focus on exploring the sequence of events related to the status of data objects. For example, we get an error if we try to access a variable that has been deleted. We can use Data Flow testing to come up with additional test cases for variables that have not be tested by other tests.&lt;/p&gt;

&lt;h4&gt;
  
  
  Error Guessing
&lt;/h4&gt;

&lt;p&gt;Past experience provides insights into parts of our code base that can lead to errors. Keeping a record of previous errors can improve the likelihood that you will not make that same mistake again in the future.&lt;/p&gt;

&lt;h4&gt;
  
  
  Recap
&lt;/h4&gt;

&lt;p&gt;Figuring out what to test and doing it efficiently is what I mean when I say &lt;a href="https://alysivji.github.io/tag/art-of-developer-testing.html" rel="noopener noreferrer"&gt;&lt;em&gt;Art of Developer Testing&lt;/em&gt;&lt;/a&gt;. The only way to get better at testing is by writing tests, coming up come up better testing strategies, and learning about different testing techniques. Just like in software development, the more you know about something, the better you will become at it.&lt;/p&gt;




&lt;p&gt;&lt;a&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  When to Write Tests
&lt;/h2&gt;

&lt;p&gt;While there is a lot of interesting discussion about when to write tests, I feel it takes away from the point of testing. It doesn't matter &lt;strong&gt;when&lt;/strong&gt; you write tests, it just matters &lt;strong&gt;that&lt;/strong&gt; you write tests.&lt;/p&gt;

&lt;p&gt;If you are interested in exploring this topic, I recommend the following links:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;RubyOnRails creator David Heinemeier Hansson (DHH) &lt;a href="https://www.youtube.com/watch?v=9LfmrkyP81M" rel="noopener noreferrer"&gt;criticizing TDD at RailsConf 2014&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://martinfowler.com/articles/is-tdd-dead/" rel="noopener noreferrer"&gt;Is TDD Dead? discussion&lt;/a&gt; with DHH, Martin Fowler, and Kent Beck&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.tedmyoung.com/looking-at-tdd-an-academic-survey/" rel="noopener noreferrer"&gt;TDD: A Academic Survey&lt;/a&gt; by Ted M. Young&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;a&gt;&lt;/a&gt;&lt;/p&gt;

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

&lt;p&gt;In this post, we got a broad introduction to the world of testing. Now that we are all on the same page, we can explore testing in more depth in future posts.&lt;/p&gt;

&lt;h4&gt;
  
  
  Additional Resources
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://www.amazon.com/Code-Complete-Practical-Handbook-Construction/dp/0735619670" rel="noopener noreferrer"&gt;Code Complete&lt;/a&gt; Chapter 22: Developer Testing&lt;/li&gt;
&lt;li&gt;Code Simplicity: &lt;a href="https://www.codesimplicity.com/post/the-philosophy-of-testing/" rel="noopener noreferrer"&gt;The Philosophy of Testing&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Katy Huff: &lt;a href="https://katyhuff.github.io/python-testing/" rel="noopener noreferrer"&gt;Python Testing and Continuous Integration&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href="https://testandcode.com/" rel="noopener noreferrer"&gt;Test and Code Podcast&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>testing</category>
      <category>beginners</category>
      <category>programming</category>
      <category>python</category>
    </item>
    <item>
      <title>Finding Common Security Issues in Python Code with Bandit</title>
      <dc:creator>Aly Sivji</dc:creator>
      <pubDate>Thu, 20 Sep 2018 11:55:47 +0000</pubDate>
      <link>https://forem.com/alysivji/finding-common-security-issues-in-python-code-with-bandit-47of</link>
      <guid>https://forem.com/alysivji/finding-common-security-issues-in-python-code-with-bandit-47of</guid>
      <description>&lt;p&gt;&lt;em&gt;This &lt;a href="https://alysivji.github.io/find-security-issues-in-python.html"&gt;post&lt;/a&gt; was originally published on &lt;a href="https://alysivji.github.io/"&gt;Siv Scripts&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/PyCQA/bandit"&gt;Bandit&lt;/a&gt; is a static analysis tool that can find common security issues in Python code. The command line utility scans &lt;code&gt;.py&lt;/code&gt; files and generates a report detailing issues by confidence and severity.&lt;/p&gt;

&lt;p&gt;I ran Bandit on a few of my repositories and found I was using &lt;code&gt;md5&lt;/code&gt; to hash passwords in one of my side projects. It was a hack with a &lt;code&gt;TODO fix&lt;/code&gt; comment, but I had forgotten about it. Thanks to Bandit, I have changed my password hashing algorithm to &lt;code&gt;bcrypt2&lt;/code&gt;. Also learned about &lt;a href="https://passlib.readthedocs.io/en/stable/lib/passlib.hash.bcrypt.html"&gt;PassLib&lt;/a&gt; as I was researching &lt;a href="https://crackstation.net/hashing-security.htm"&gt;how to salt and hash passwords&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The Rackspace blog has a post on &lt;a href="https://developer.rackspace.com/blog/getting-started-with-bandit/"&gt;Getting Started with Bandit&lt;/a&gt; that I recommend checking out.&lt;/p&gt;

</description>
      <category>python</category>
      <category>security</category>
    </item>
    <item>
      <title>Implementing a Plugin System in Python</title>
      <dc:creator>Aly Sivji</dc:creator>
      <pubDate>Mon, 10 Sep 2018 18:12:26 +0000</pubDate>
      <link>https://forem.com/alysivji/implementing-a-plugin-system-in-python-19mp</link>
      <guid>https://forem.com/alysivji/implementing-a-plugin-system-in-python-19mp</guid>
      <description>&lt;p&gt;&lt;em&gt;This &lt;a href="https://alysivji.github.io/simple-plugin-system.html" rel="noopener noreferrer"&gt;post&lt;/a&gt; was originally published on &lt;a href="https://alysivji.github.io/" rel="noopener noreferrer"&gt;Siv Scripts&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;In July, I released my &lt;a href="https://github.com/alysivji/falcon-apispec" rel="noopener noreferrer"&gt;first open source project&lt;/a&gt;. It's an &lt;a href="https://github.com/marshmallow-code/apispec" rel="noopener noreferrer"&gt;apispec&lt;/a&gt; plugin that generates &lt;a href="https://swagger.io/docs/specification/about/" rel="noopener noreferrer"&gt;OpenAPI Specification&lt;/a&gt; (aka Swagger docs) for &lt;a href="https://github.com/falconry/falcon" rel="noopener noreferrer"&gt;Falcon web applications&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Apispec's design made it easy to extend core functionality for a specific use case. I extended the &lt;code&gt;apispec.BasePlugin&lt;/code&gt; class, overrode a couple of methods, and I was done. Had to dig into apispec internals to figure out how things were wired together, but it was easy to build upon what was already there&lt;/p&gt;

&lt;p&gt;This got me thinking about how to implement a plugin system in one of my own applications. Creating a plugin architecture? It sounded hard. An &lt;em&gt;advanced&lt;/em&gt; computer science concept, if you will.&lt;/p&gt;

&lt;p&gt;I mulled over a few different implementations based on software I had previously used. I realized this wasn't a difficult problem. Like everything else in programming, once we deconstruct the problem into smaller chunks, we can reason about implementation details clearly.&lt;/p&gt;

&lt;p&gt;We assume things are more difficult than they appear. This is especially true for problems we have not seen before. Take a step back. Breathe. Break the problem down into smaller pieces. You got 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%2Falysivji.github.io%2Fimages%2F30-39%2F35_you_got_this.jpg" 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%2Falysivji.github.io%2Fimages%2F30-39%2F35_you_got_this.jpg" alt="Motivational You Got This poster with dog"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In this &lt;a href="https://alysivji.github.io/category/quick-hits.html" rel="noopener noreferrer"&gt;Quick Hit&lt;/a&gt;, we will walk through the implementation of a simple plugin system.&lt;/p&gt;




&lt;h2&gt;
  
  
  Background
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;A plugin is a software component that adds a specific feature to an existing computer program. When a program supports plug-ins, it enables customization (&lt;a href="https://en.wikipedia.org/wiki/Plug-in_(computing)" rel="noopener noreferrer"&gt;Wikipedia&lt;/a&gt;)&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;There are many benefits to building apps with a plugin framework:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;3rd party developers can create and extend upon your app&lt;/li&gt;
&lt;li&gt;new features are easier to developer&lt;/li&gt;
&lt;li&gt;your application becomes smaller and easier to understand&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Sample Application Flow
&lt;/h3&gt;

&lt;p&gt;We have a program that starts, does a few things, and exits.&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%2Falysivji.github.io%2Fimages%2F30-39%2F35_program-workflow.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%2Falysivji.github.io%2Fimages%2F30-39%2F35_program-workflow.png" alt="Program Flow"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Plugin Architecture Workflow
&lt;/h3&gt;

&lt;p&gt;We refactored our Business Logic into a Plugin Framework that can run registered plugins. The plugins will need to meet the specifications defined by our framework in order to run.&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%2Falysivji.github.io%2Fimages%2F30-39%2F35_program-workflow-plugin-system.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%2Falysivji.github.io%2Fimages%2F30-39%2F35_program-workflow-plugin-system.png" alt="Program flow with Plugin"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Toy Example
&lt;/h2&gt;

&lt;p&gt;Let's implement a toy example where we have a plugin framework to print to the console. Our project will have the following structure:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="c"&gt;.
&lt;/span&gt;&lt;span class="gp"&gt;├── internal.py  #&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;internal business logic
&lt;span class="gp"&gt;├── external.py  #&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;contains user-created plugins
&lt;span class="gp"&gt;└── main.py      #&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;initialize and run application
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Internal
&lt;/h3&gt;

&lt;p&gt;This module contains the application class and an internal plugin.&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;# internal.py
&lt;/span&gt;
&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;InternalPrinter&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Internal business logic&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;process&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="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;Internal Hello&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;


&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;MyApplication&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;First attempt at a plugin system&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="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;plugins&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;list&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;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;internal_modules&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nc"&gt;InternalPrinter&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;_plugins&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;plugins&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;run&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="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;Starting program&lt;/span&gt;&lt;span class="sh"&gt;"&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;-&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;79&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="n"&gt;modules_to_execute&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;internal_modules&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;_plugins&lt;/span&gt;
        &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;module&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;modules_to_execute&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;process&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;-&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;79&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;Program done&lt;/span&gt;&lt;span class="sh"&gt;"&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  External
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# external.py
&lt;/span&gt;
&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;HelloWorldPrinter&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;process&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="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;Hello World&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;


&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;AlohaWorldPrinter&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;process&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="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;Aloha World&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;h3&gt;
  
  
  Main
&lt;/h3&gt;

&lt;p&gt;In this module, we run instances of our application with the external plugins we want to enable.&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;# main.py
&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;internal&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;MyApplication&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;external&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;HelloWorldPrinter&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;AlohaWorldPrinter&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="c1"&gt;# Run with one plugin
&lt;/span&gt;    &lt;span class="n"&gt;app&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;MyApplication&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;plugins&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nc"&gt;HelloWorldPrinter&lt;/span&gt;&lt;span class="p"&gt;()])&lt;/span&gt;
    &lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;run&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="c1"&gt;# Run with another plugin
&lt;/span&gt;    &lt;span class="n"&gt;app&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;MyApplication&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;plugins&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nc"&gt;AlohaWorldPrinter&lt;/span&gt;&lt;span class="p"&gt;()])&lt;/span&gt;
    &lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;run&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="c1"&gt;# Run with both plugins
&lt;/span&gt;    &lt;span class="n"&gt;app&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;MyApplication&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;plugins&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nc"&gt;HelloWorldPrinter&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="nc"&gt;AlohaWorldPrinter&lt;/span&gt;&lt;span class="p"&gt;()])&lt;/span&gt;
    &lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;run&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

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

&lt;/div&gt;



&lt;h3&gt;
  
  
  Discussion
&lt;/h3&gt;

&lt;p&gt;The Application's plugin framework works for both internal and external plugins. We define internal plugins in the application code. External plugins are initialized and passed into the application at runtime.&lt;/p&gt;

&lt;p&gt;Each plugin inherits from the base Python object and has a &lt;code&gt;process()&lt;/code&gt; method. Nothing complex, want to keep this example as simple as possible.&lt;/p&gt;

&lt;p&gt;We can run our plugins by calling the application's &lt;code&gt;run()&lt;/code&gt; method. This method loops over all the plugins and calls each instance's &lt;code&gt;process()&lt;/code&gt; function. As we see from the output above, the plugins are executed in the same order as the list.&lt;/p&gt;

&lt;h3&gt;
  
  
  Running Toy Application
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;python main.py
&lt;span class="go"&gt;Starting program
-------------------------------------------------------------------------------
Internal Hello
Hello World
-------------------------------------------------------------------------------
Program done

Starting program
-------------------------------------------------------------------------------
Internal Hello
Aloha World
-------------------------------------------------------------------------------
Program done

Starting program
-------------------------------------------------------------------------------
Internal Hello
Hello World
Aloha World
-------------------------------------------------------------------------------
Program done
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Real World Example
&lt;/h3&gt;

&lt;p&gt;This pattern can be used in conjunction with the &lt;a href="https://en.wikipedia.org/wiki/Adapter_pattern" rel="noopener noreferrer"&gt;Adapter pattern&lt;/a&gt; to simplify application development.&lt;/p&gt;

&lt;p&gt;Let's say we have a large number of external clients we want to interface with. Each API is different, but the tasks we need to perform for each client are the same.&lt;/p&gt;

&lt;p&gt;One possible implementation of this is to write an adapter around each of the client APIs, resulting in a common interface. Next, we can leverage the plugin framework to solve our business problem, and then we can use plugins to make it work for all of our clients.&lt;/p&gt;

&lt;p&gt;This is a very high level description of the solution. I leave implementation as an exercise to the reader.&lt;/p&gt;




&lt;h4&gt;
  
  
  Additional Resources
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="http://martyalchin.com/2008/jan/10/simple-plugin-framework/" rel="noopener noreferrer"&gt;A Simple Plugin Framework&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Plugin libraries

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/pytest-dev/pluggy/" rel="noopener noreferrer"&gt;pluggy&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/tibonihoo/yapsy" rel="noopener noreferrer"&gt;yapsy&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/ironfroggy/straight.plugin" rel="noopener noreferrer"&gt;straight.plugin&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

</description>
      <category>python</category>
      <category>showdev</category>
      <category>tutorial</category>
      <category>beginners</category>
    </item>
    <item>
      <title>PHP + MySQL using Docker Compose</title>
      <dc:creator>Aly Sivji</dc:creator>
      <pubDate>Tue, 27 Feb 2018 02:04:20 +0000</pubDate>
      <link>https://forem.com/alysivji/php--mysql-using-docker-compose-387d</link>
      <guid>https://forem.com/alysivji/php--mysql-using-docker-compose-387d</guid>
      <description>&lt;p&gt;&lt;em&gt;This &lt;a href="https://alysivji.github.io/php-mysql-docker-containers.html"&gt;post&lt;/a&gt; was originally published on &lt;a href="https://alysivji.github.io/"&gt;Siv Scripts&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Being a Software Engineer isn't just about being effective in a specific programming language, it's about being able to solve any given problem using the tools at hand.&lt;/p&gt;

&lt;p&gt;This week at work I have to extend the functionality of a WordPress plug-in so it can fit into our microservices-based backend architecture. This means learning enough PHP to write some production-grade code.&lt;/p&gt;

&lt;p&gt;I don't want to install PHP on my local machine so this is the perfect use case for &lt;a href="https://www.docker.com/"&gt;Docker&lt;/a&gt;! In this &lt;a href="https://alysivji.github.io/category/quick-hits.html"&gt;Quick Hit&lt;/a&gt;, I will describe how to create a containerized PHP + MySQL development environment using Docker Compose.&lt;/p&gt;

&lt;p&gt;The &lt;a href="https://en.wikipedia.org/wiki/LAMP_(software_bundle)"&gt;LAMP Stack&lt;/a&gt; is back!&lt;/p&gt;




&lt;h3&gt;
  
  
  Instructions
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;We'll start by creating a folder for this project:&lt;br&gt;
&lt;br&gt;
&lt;code&gt;mkdir lamp-stack &amp;amp;&amp;amp; cd lamp-stack&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Create another subdirectory, &lt;code&gt;/php&lt;/code&gt; which contains the following &lt;code&gt;index.php&lt;/code&gt; file:&lt;br&gt;
&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="c"&gt;&amp;lt;!-- ./php/index.php --&amp;gt;&lt;/span&gt;

&lt;span class="nt"&gt;&amp;lt;html&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;title&amp;gt;&lt;/span&gt;Hello World&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="cp"&gt;&amp;lt;?php&lt;/span&gt;
            &lt;span class="k"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Hello, World!"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="cp"&gt;?&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;ul&gt;
&lt;li&gt;Populate &lt;code&gt;docker-compose.yml&lt;/code&gt; with the following configuration:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;# ./docker-compose.yml&lt;/span&gt;

&lt;span class="na"&gt;version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;3'&lt;/span&gt;

&lt;span class="na"&gt;services&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;db&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;mysql:5.7&lt;/span&gt;
    &lt;span class="na"&gt;environment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;MYSQL_ROOT_PASSWORD&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;my_secret_pw_shh&lt;/span&gt;
      &lt;span class="na"&gt;MYSQL_DATABASE&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;test_db&lt;/span&gt;
      &lt;span class="na"&gt;MYSQL_USER&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;devuser&lt;/span&gt;
      &lt;span class="na"&gt;MYSQL_PASSWORD&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;devpass&lt;/span&gt;
    &lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;9906:3306"&lt;/span&gt;
  &lt;span class="na"&gt;web&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;php:7.2.2-apache&lt;/span&gt;
    &lt;span class="na"&gt;container_name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;php_web&lt;/span&gt;
    &lt;span class="na"&gt;depends_on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;db&lt;/span&gt;
    &lt;span class="na"&gt;volumes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;./php/:/var/www/html/&lt;/span&gt;
    &lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;8100:80"&lt;/span&gt;
    &lt;span class="na"&gt;stdin_open&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="no"&gt;true&lt;/span&gt;
    &lt;span class="na"&gt;tty&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="no"&gt;true&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;Our directory structure should look as follows:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;tree
&lt;span class="c"&gt;.
&lt;/span&gt;&lt;span class="go"&gt;├── docker-compose.yml
└── php
    └── index.php
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;Execute &lt;code&gt;docker-compose up -d&lt;/code&gt; in the terminal and load &lt;a href="http://localhost:8100/"&gt;http://localhost:8100/&lt;/a&gt; in your browser.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Time to learn some &lt;a href="https://www.w3schools.com/php/default.asp"&gt;P&lt;/a&gt;&lt;a href="https://www.tutorialspoint.com/php/index.htm"&gt;H&lt;/a&gt;&lt;a href="http://php.net/manual/en/tutorial.php"&gt;P&lt;/a&gt;!&lt;/p&gt;

&lt;h5&gt;
  
  
  Notes
&lt;/h5&gt;

&lt;ul&gt;
&lt;li&gt;We use &lt;a href="https://docs.docker.com/config/containers/container-networking/"&gt;port-forwarding&lt;/a&gt; to connect to the inside of containers from our local machine.

&lt;ul&gt;
&lt;li&gt;webserver: &lt;code&gt;http://localhost:8100&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;db: &lt;code&gt;mysql://devuser:devpasslocalhost:9906/test_db&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;li&gt;Our local directory, &lt;code&gt;./php&lt;/code&gt;, is &lt;a href="https://docs.docker.com/storage/volumes/"&gt;mounted&lt;/a&gt; inside of the webserver container as &lt;code&gt;/var/www/html/&lt;/code&gt;

&lt;ul&gt;
&lt;li&gt;The files within in our local folder will be served when we access the website inside of the container&lt;/li&gt;
&lt;/ul&gt;


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

</description>
      <category>php</category>
      <category>mysql</category>
      <category>docker</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Data Science Workflows Using Docker Containers</title>
      <dc:creator>Aly Sivji</dc:creator>
      <pubDate>Mon, 27 Nov 2017 18:22:32 +0000</pubDate>
      <link>https://forem.com/alysivji/data-science-workflows-using-docker-containers-bcn</link>
      <guid>https://forem.com/alysivji/data-science-workflows-using-docker-containers-bcn</guid>
      <description>&lt;p&gt;I gave a talk last month on how to incorporate Docker into Data Science projects.&lt;/p&gt;

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

&lt;p&gt;Slides are available at &lt;a href="http://bit.ly/docker-for-data-science"&gt;http://bit.ly/docker-for-data-science&lt;/a&gt;&lt;br&gt;
Link to my blog: &lt;a href="https://alysivji.github.io"&gt;https://alysivji.github.io&lt;/a&gt;&lt;/p&gt;

</description>
      <category>docker</category>
      <category>tutorial</category>
      <category>python</category>
      <category>datascience</category>
    </item>
    <item>
      <title>Interactive, Web-Based Dashboards in Python</title>
      <dc:creator>Aly Sivji</dc:creator>
      <pubDate>Thu, 16 Nov 2017 00:18:11 +0000</pubDate>
      <link>https://forem.com/alysivji/interactive-web-based-dashboards-in-python-5hf</link>
      <guid>https://forem.com/alysivji/interactive-web-based-dashboards-in-python-5hf</guid>
      <description>&lt;p&gt;&lt;em&gt;This &lt;a href="https://alysivji.github.io/reactive-dashboards-with-dash.html" rel="noopener noreferrer"&gt;post&lt;/a&gt; was originally published on &lt;a href="https://alysivji.github.io/" rel="noopener noreferrer"&gt;Siv Scripts&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;h4&gt;
  
  
  Summary
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;Explore Plotly's new Dash library&lt;/li&gt;
&lt;li&gt;Discuss how to structure Dash apps using MVC&lt;/li&gt;
&lt;li&gt;Build interactive dashboard to display historical soocer results&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;




&lt;p&gt;I spent a good portion of 2014-15 learning JavaScript to create interactive, web-based dashboards for a work project. I wrapped &lt;a href="https://d3js.org/" rel="noopener noreferrer"&gt;D3.js&lt;/a&gt; with &lt;a href="https://angularjs.org/" rel="noopener noreferrer"&gt;Angular directives&lt;/a&gt; to create modular components that were used to visualize data.&lt;/p&gt;

&lt;p&gt;Data Analysis is not one of JavaScript's strengths; most of my code was trying to cobble together &lt;code&gt;DataFrame&lt;/code&gt;-esque operations with JSON data. I missed R. I missed Python. I even missed MATLAB.&lt;/p&gt;

&lt;p&gt;When I found &lt;a href="https://medium.com/@plotlygraphs/introducing-dash-5ecf7191b503" rel="noopener noreferrer"&gt;Dash&lt;/a&gt; a couple of months ago, I was blown away.&lt;/p&gt;

&lt;p&gt;With Dash, we can create interactive, web-based dashboards with &lt;strong&gt;pure Python&lt;/strong&gt;. All the front-end work, all that dreaded JavaScript, that's not our problem anymore.&lt;/p&gt;

&lt;p&gt;How easy is Dash to use? In around an hour and with &amp;lt;100 lines of code, I created a &lt;a href="https://github.com/alysivji/talks/blob/master/data-science-workflows-using-docker-containers/workflow3-data-driven-app/plot_timeseries.py" rel="noopener noreferrer"&gt;dashboard to display live streaming data&lt;/a&gt; for my &lt;a href="http://bit.ly/siv-docker-for-data-science-chipy-talk" rel="noopener noreferrer"&gt;Data Science Workflows using Docker Containers&lt;/a&gt; talk.&lt;/p&gt;

&lt;p&gt;Dash is a powerful library that simplifies the development of data-driven applications. Dash enables Data Scentists to become &lt;a href="http://www.laurencegellert.com/2012/08/what-is-a-full-stack-developer/" rel="noopener noreferrer"&gt;Full Stack Developers&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;In this post we will explore Dash, discuss how the Model-View-Controller pattern can be used to structure Dash applications, and build a dashboard to display historical soccer results.&lt;/p&gt;




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

&lt;blockquote&gt;
&lt;p&gt;&lt;a href="https://medium.com/@plotlygraphs/introducing-dash-5ecf7191b503" rel="noopener noreferrer"&gt;Dash is a Open Source Python library for creating reactive, Web-based applications&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Dash apps consist of a Flask server that communicates with front-end React components using JSON packets over HTTP requests.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;What does this mean?&lt;/em&gt; We can run a Flask app to create a web page with a dashboard. Interaction in the browser can call code to re-render certain parts of our page.&lt;/p&gt;

&lt;p&gt;We use the provided Python interface to design our application layout and to enable interaction between components. User interaction triggers Python functions; these functions can perform any action before returning a result back to the specified component.&lt;/p&gt;

&lt;p&gt;Dash applications are written in Python. &lt;strong&gt;No HTML or JavaScript is necessary.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;We are also able to plug into React's &lt;a href="https://github.com/brillout/awesome-react-components" rel="noopener noreferrer"&gt;extensive ecosystem&lt;/a&gt; through an &lt;a href="https://github.com/plotly/dash-components-archetype" rel="noopener noreferrer"&gt;included toolset&lt;/a&gt; that packages React components into Dash-useable components.&lt;/p&gt;




&lt;h2&gt;
  
  
  Dash Application Design: MVC Pattern
&lt;/h2&gt;

&lt;p&gt;As I worked my way through the &lt;a href="https://plot.ly/dash/" rel="noopener noreferrer"&gt;documentation&lt;/a&gt;, I kept noticing that every Dash application could be divided into the following components:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Data Manipulation&lt;/strong&gt; - Perform operations to read / transform data for display&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Dashboard Layout&lt;/strong&gt; - Visually render data into output representation&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Interaction Between Components&lt;/strong&gt; - Convert user input to commmands for data manipulation + render&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That's the Model-View-Controller (MVC) Pattern's music! (Note: I covered MVC in a &lt;a href="https://alysivji.github.io/flask-part2-building-a-flask-web-application.html#model-view-controller" rel="noopener noreferrer"&gt;previous post&lt;/a&gt;)&lt;/p&gt;

&lt;p&gt;When designing a Dash application, we should stucture our code into three sections:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Data Manipulation (Model)&lt;/li&gt;
&lt;li&gt;Dashboard Layout (View)&lt;/li&gt;
&lt;li&gt;Interaction Between Components (Controller)&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;I created the &lt;a href="https://gist.github.com/alysivji/e85a04f3a9d84f6ce98c56f05858ecfb" rel="noopener noreferrer"&gt;following template&lt;/a&gt; to help us get started:&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;# standard library
&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;

&lt;span class="c1"&gt;# dash libs
&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;dash&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;dash.dependencies&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Input&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Output&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;dash_core_components&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;dcc&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;dash_html_components&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;html&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;plotly.figure_factory&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;ff&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;plotly.graph_objs&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;go&lt;/span&gt;

&lt;span class="c1"&gt;# pydata stack
&lt;/span&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;sqlalchemy&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;create_engine&lt;/span&gt;

&lt;span class="c1"&gt;# set params
&lt;/span&gt;&lt;span class="n"&gt;conn&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;create_engine&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;environ&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;DB_URI&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;


&lt;span class="c1"&gt;###########################
# Data Manipulation / Model
###########################
&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;fetch_data&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;q&lt;/span&gt;&lt;span class="p"&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_sql&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;sql&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;q&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;con&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;conn&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;df&lt;/span&gt;


&lt;span class="c1"&gt;#########################
# Dashboard Layout / View
#########################
&lt;/span&gt;
&lt;span class="c1"&gt;# Set up Dashboard and create layout
&lt;/span&gt;&lt;span class="n"&gt;app&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;dash&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Dash&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;css&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append_css&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;external_url&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;https://codepen.io/chriddyp/pen/bWLwgP.css&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;

&lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;layout&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;html&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Div&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;

    &lt;span class="c1"&gt;# Page Header
&lt;/span&gt;    &lt;span class="n"&gt;html&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Div&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;
        &lt;span class="n"&gt;html&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;H1&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Project Header&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="p"&gt;])&lt;/span&gt;


&lt;span class="c1"&gt;#############################################
# Interaction Between Components / Controller
#############################################
&lt;/span&gt;
&lt;span class="c1"&gt;# Template
&lt;/span&gt;&lt;span class="nd"&gt;@app.callback&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nc"&gt;Output&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;component_id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;selector-id&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;component_property&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;figure&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="nc"&gt;Input&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;component_id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;input-selector-id&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;component_property&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;value&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="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;ctrl_func&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;input_selection&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;


&lt;span class="c1"&gt;# start Flask server
&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="n"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;run_server&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;debug&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;host&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;0.0.0.0&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;port&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;8050&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Historical Matchup Dashboard
&lt;/h2&gt;

&lt;p&gt;In this section, we will create a full-featured Dash application that can be used to view historicial soccer data.&lt;/p&gt;

&lt;p&gt;We will use the following process to create / modify Dash applications:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Create/Update Layout&lt;/strong&gt; - Figure out where components will be placed&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Map Interactions with Other Components&lt;/strong&gt; - Specify interaction in callback decorators&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Wire in Data Model&lt;/strong&gt; - Data manipulation to link interaction and render&lt;/li&gt;
&lt;/ol&gt;




&lt;h3&gt;
  
  
  Setting Up Environment and Installing Dependencies
&lt;/h3&gt;

&lt;p&gt;There are &lt;a href="https://plot.ly/dash/installation" rel="noopener noreferrer"&gt;installation instructions&lt;/a&gt; in the Dash Documentation. Alternatively, we can create a &lt;code&gt;virtualenv&lt;/code&gt; and &lt;code&gt;pip install&lt;/code&gt;  the &lt;a href="https://raw.githubusercontent.com/alysivji/historical-results-dashboard/master/requirements.txt" rel="noopener noreferrer"&gt;requirements file&lt;/a&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="go"&gt;mkdir historical-results-dashboard &amp;amp;&amp;amp; cd historical-results-dashboard
mkvirtualenv dash-app
wget https://raw.githubusercontent.com/alysivji/historical-results-dashboard/master/requirements.txt
pip install -r requirements.txt
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Data is stored in an SQLite database:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="go"&gt;wget https://github.com/alysivji/historical-results-dashboard/blob/master/soccer-stats.db?raw=true soccer-stats.db
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Download the Dash application template file from above:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="go"&gt;wget https://gist.githubusercontent.com/alysivji/e85a04f3a9d84f6ce98c56f05858ecfb/raw/d7bfeb84e2c825cfb5d4feee15982c763651e72e/dash_app_template.py app.py
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h3&gt;
  
  
  Dashboard Layout (View)
&lt;/h3&gt;

&lt;p&gt;Our app will look as follows:&lt;br&gt;&lt;br&gt;
&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Falysivji.github.io%2Fimages%2F11-20%2F11_dash_app_layout.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%2Falysivji.github.io%2Fimages%2F11-20%2F11_dash_app_layout.png" alt="Dash App Layout"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Users will be able to select Division, Season, and Team via Dropdown components. Selection will trigger actions to update tables (Results + Win/Loss/Draw/Points Summary) and a graph (Points Accumulated vs Time)&lt;/p&gt;

&lt;p&gt;We begin by translating the layout from above into Dash components (both &lt;a href="https://github.com/plotly/dash-core-components" rel="noopener noreferrer"&gt;core&lt;/a&gt; + &lt;a href="https://github.com/plotly/dash-html-components" rel="noopener noreferrer"&gt;HTML&lt;/a&gt; components will be required):&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;#########################
# Dashboard Layout / View
#########################
&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;generate_table&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;dataframe&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;max_rows&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="sh"&gt;'''&lt;/span&gt;&lt;span class="s"&gt;Given dataframe, return template generated using Dash components
    &lt;/span&gt;&lt;span class="sh"&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="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Table&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="c1"&gt;# Header
&lt;/span&gt;        &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;html&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Tr&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="n"&gt;html&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Th&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;col&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;col&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;dataframe&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;columns&lt;/span&gt;&lt;span class="p"&gt;])]&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt;

        &lt;span class="c1"&gt;# Body
&lt;/span&gt;        &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;html&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Tr&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;
            &lt;span class="n"&gt;html&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Td&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;dataframe&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;iloc&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="n"&gt;col&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;col&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;dataframe&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;columns&lt;/span&gt;
        &lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;min&lt;/span&gt;&lt;span class="p"&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;dataframe&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;max_rows&lt;/span&gt;&lt;span class="p"&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;onLoad_division_options&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="sh"&gt;'''&lt;/span&gt;&lt;span class="s"&gt;Actions to perform upon initial page load&lt;/span&gt;&lt;span class="sh"&gt;'''&lt;/span&gt;

    &lt;span class="n"&gt;division_options&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&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;label&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;division&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;value&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;division&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;division&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;get_divisions&lt;/span&gt;&lt;span class="p"&gt;()]&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;division_options&lt;/span&gt;


&lt;span class="c1"&gt;# Set up Dashboard and create layout
&lt;/span&gt;&lt;span class="n"&gt;app&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;dash&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Dash&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;css&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append_css&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;external_url&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;https://codepen.io/chriddyp/pen/bWLwgP.css&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;

&lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;layout&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;html&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Div&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;

    &lt;span class="c1"&gt;# Page Header
&lt;/span&gt;    &lt;span class="n"&gt;html&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Div&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;
        &lt;span class="n"&gt;html&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;H1&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Soccer Results Viewer&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;# Dropdown Grid
&lt;/span&gt;    &lt;span class="n"&gt;html&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Div&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;
        &lt;span class="n"&gt;html&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Div&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;
            &lt;span class="c1"&gt;# Select Division Dropdown
&lt;/span&gt;            &lt;span class="n"&gt;html&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Div&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;
                &lt;span class="n"&gt;html&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Div&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Select Division&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;className&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;three columns&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
                &lt;span class="n"&gt;html&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Div&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;dcc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Dropdown&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;division-selector&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                                      &lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nf"&gt;onLoad_division_options&lt;/span&gt;&lt;span class="p"&gt;()),&lt;/span&gt;
                         &lt;span class="n"&gt;className&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;nine columns&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;# Select Season Dropdown
&lt;/span&gt;            &lt;span class="n"&gt;html&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Div&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;
                &lt;span class="n"&gt;html&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Div&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Select Season&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;className&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;three columns&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
                &lt;span class="n"&gt;html&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Div&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;dcc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Dropdown&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;season-selector&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
                         &lt;span class="n"&gt;className&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;nine columns&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;# Select Team Dropdown
&lt;/span&gt;            &lt;span class="n"&gt;html&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Div&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;
                &lt;span class="n"&gt;html&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Div&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Select Team&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;className&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;three columns&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
                &lt;span class="n"&gt;html&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Div&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;dcc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Dropdown&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;team-selector&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
                         &lt;span class="n"&gt;className&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;nine columns&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="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;className&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;six columns&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;

        &lt;span class="c1"&gt;# Empty
&lt;/span&gt;        &lt;span class="n"&gt;html&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Div&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;className&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;six columns&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;className&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;twleve columns&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;

    &lt;span class="c1"&gt;# Match Results Grid
&lt;/span&gt;    &lt;span class="n"&gt;html&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Div&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;

        &lt;span class="c1"&gt;# Match Results Table
&lt;/span&gt;        &lt;span class="n"&gt;html&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Div&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;html&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Table&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;match-results&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
            &lt;span class="n"&gt;className&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;six columns&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;
        &lt;span class="p"&gt;),&lt;/span&gt;

        &lt;span class="c1"&gt;# Season Summary Table and Graph
&lt;/span&gt;        &lt;span class="n"&gt;html&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Div&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;
            &lt;span class="c1"&gt;# summary table
&lt;/span&gt;            &lt;span class="n"&gt;dcc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Graph&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;season-summary&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;

            &lt;span class="c1"&gt;# graph
&lt;/span&gt;            &lt;span class="n"&gt;dcc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Graph&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;season-graph&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="c1"&gt;# style={},
&lt;/span&gt;
        &lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;className&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;six columns&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="p"&gt;])&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h5&gt;
  
  
  Notes:
&lt;/h5&gt;

&lt;ul&gt;
&lt;li&gt;We used HTML &lt;code&gt;&amp;lt;DIV&amp;gt;&lt;/code&gt; elements and the &lt;a href="https://codepen.io/chriddyp/pen/bWLwgP" rel="noopener noreferrer"&gt;Dash Style Guide&lt;/a&gt; to design the layout&lt;/li&gt;
&lt;li&gt;Tables can be rendered two different ways: &lt;a href="https://community.plot.ly/t/display-tables-in-dash/4707/13" rel="noopener noreferrer"&gt;Native HTML or Plotly Table&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;We just wireframed components in this section, data will be populated via Model and Controller&lt;/li&gt;
&lt;/ul&gt;




&lt;h3&gt;
  
  
  Interaction Between Components (Controller)
&lt;/h3&gt;

&lt;p&gt;Once we create our layout, we will need to map out the interaction between the various components. We do this using the provided &lt;code&gt;app.callback()&lt;/code&gt; decorator.&lt;/p&gt;

&lt;p&gt;The parameters we pass into the decorator are:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Output component + property we want to update&lt;/li&gt;
&lt;li&gt;list of all the Input components + properties that can be used to trigger the function&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Our code looks as follows:&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;#############################################
# Interaction Between Components / Controller
#############################################
&lt;/span&gt;
&lt;span class="c1"&gt;# Load Seasons in Dropdown
&lt;/span&gt;&lt;span class="nd"&gt;@app.callback&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nc"&gt;Output&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;component_id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;season-selector&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;component_property&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;options&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="nc"&gt;Input&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;component_id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;division-selector&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;component_property&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;value&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="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;populate_season_selector&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;division&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;seasons&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;get_seasons&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;division&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&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;label&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;season&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;value&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;season&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;season&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;seasons&lt;/span&gt;
    &lt;span class="p"&gt;]&lt;/span&gt;


&lt;span class="c1"&gt;# Load Teams into dropdown
&lt;/span&gt;&lt;span class="nd"&gt;@app.callback&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nc"&gt;Output&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;component_id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;team-selector&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;component_property&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;options&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="nc"&gt;Input&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;component_id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;division-selector&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;component_property&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;value&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="nc"&gt;Input&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;component_id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;season-selector&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;component_property&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;value&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="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;populate_team_selector&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;division&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;season&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;teams&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;get_teams&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;division&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;season&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&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;label&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;team&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;value&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;team&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;team&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;teams&lt;/span&gt;
    &lt;span class="p"&gt;]&lt;/span&gt;


&lt;span class="c1"&gt;# Load Match results
&lt;/span&gt;&lt;span class="nd"&gt;@app.callback&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nc"&gt;Output&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;component_id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;match-results&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;component_property&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;children&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="nc"&gt;Input&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;component_id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;division-selector&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;component_property&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;value&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="nc"&gt;Input&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;component_id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;season-selector&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;component_property&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;value&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="nc"&gt;Input&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;component_id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;team-selector&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;component_property&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;value&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="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;load_match_results&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;division&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;season&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;team&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;results&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;get_match_results&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;division&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;season&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;team&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;generate_table&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;max_rows&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;50&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;


&lt;span class="c1"&gt;# Update Season Summary Table
&lt;/span&gt;&lt;span class="nd"&gt;@app.callback&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nc"&gt;Output&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;component_id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;season-summary&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;component_property&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;figure&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="nc"&gt;Input&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;component_id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;division-selector&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;component_property&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;value&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="nc"&gt;Input&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;component_id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;season-selector&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;component_property&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;value&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="nc"&gt;Input&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;component_id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;team-selector&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;component_property&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;value&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="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;load_season_summary&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;division&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;season&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;team&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;results&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;get_match_results&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;division&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;season&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;team&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="p"&gt;[]&lt;/span&gt;
    &lt;span class="k"&gt;if&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;results&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;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;summary&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;calculate_season_summary&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;table&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ff&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create_table&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;summary&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;table&lt;/span&gt;


&lt;span class="c1"&gt;# Update Season Point Graph
&lt;/span&gt;&lt;span class="nd"&gt;@app.callback&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nc"&gt;Output&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;component_id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;season-graph&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;component_property&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;figure&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="nc"&gt;Input&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;component_id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;division-selector&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;component_property&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;value&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="nc"&gt;Input&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;component_id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;season-selector&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;component_property&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;value&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="nc"&gt;Input&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;component_id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;team-selector&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;component_property&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;value&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="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;load_season_points_graph&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;division&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;season&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;team&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;results&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;get_match_results&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;division&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;season&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;team&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;figure&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
    &lt;span class="k"&gt;if&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;results&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;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;figure&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;draw_season_points_graph&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="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;figure&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h5&gt;
  
  
  Notes:
&lt;/h5&gt;

&lt;ul&gt;
&lt;li&gt;Each &lt;code&gt;app.callback()&lt;/code&gt; decorator can be bound to a single Output &lt;code&gt;(component, property)&lt;/code&gt; pair

&lt;ul&gt;
&lt;li&gt;We will need to create additional functions to change multiple Output components&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;We could add Data Manipulation code in this section, but separating the app into components makes it easier to work with&lt;/li&gt;

&lt;/ul&gt;




&lt;h3&gt;
  
  
  Data Manipulation (Model)
&lt;/h3&gt;

&lt;p&gt;We finish the dashboard by wiring our Model into both the View and the Controller:&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;###########################
# Data Manipulation / Model
###########################
&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;fetch_data&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;q&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;result&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_sql&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;sql&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;q&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;con&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;conn&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;result&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;get_divisions&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="sh"&gt;'''&lt;/span&gt;&lt;span class="s"&gt;Returns the list of divisions that are stored in the database&lt;/span&gt;&lt;span class="sh"&gt;'''&lt;/span&gt;

    &lt;span class="n"&gt;division_query&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;'''&lt;/span&gt;&lt;span class="s"&gt;
        SELECT DISTINCT division
        FROM results
        &lt;/span&gt;&lt;span class="sh"&gt;'''&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;divisions&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;fetch_data&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;division_query&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;divisions&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;divisions&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;division&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;sort_values&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ascending&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;divisions&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;get_seasons&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;division&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="sh"&gt;'''&lt;/span&gt;&lt;span class="s"&gt;Returns the seasons of the datbase store&lt;/span&gt;&lt;span class="sh"&gt;'''&lt;/span&gt;

    &lt;span class="n"&gt;seasons_query&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;'''&lt;/span&gt;&lt;span class="s"&gt;
        SELECT DISTINCT season
        FROM results
        WHERE division=&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;division&lt;/span&gt;&lt;span class="si"&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;seasons&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;fetch_data&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;seasons_query&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;seasons&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;seasons&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;season&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;sort_values&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ascending&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;False&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;seasons&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;get_teams&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;division&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;season&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="sh"&gt;'''&lt;/span&gt;&lt;span class="s"&gt;Returns all teams playing in the division in the season&lt;/span&gt;&lt;span class="sh"&gt;'''&lt;/span&gt;

    &lt;span class="n"&gt;teams_query&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;'''&lt;/span&gt;&lt;span class="s"&gt;
        SELECT DISTINCT team
        FROM results
        WHERE division=&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;division&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;
        AND season=&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;season&lt;/span&gt;&lt;span class="si"&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;teams&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;fetch_data&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;teams_query&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;teams&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;teams&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;team&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;sort_values&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ascending&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;teams&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;get_match_results&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;division&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;season&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;team&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="sh"&gt;'''&lt;/span&gt;&lt;span class="s"&gt;Returns match results for the selected prompts&lt;/span&gt;&lt;span class="sh"&gt;'''&lt;/span&gt;

    &lt;span class="n"&gt;results_query&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;'''&lt;/span&gt;&lt;span class="s"&gt;
        SELECT date, team, opponent, goals, goals_opp, result, points
        FROM results
        WHERE division=&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;division&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;
        AND season=&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;season&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;
        AND team=&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;team&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;
        ORDER BY date ASC
        &lt;/span&gt;&lt;span class="sh"&gt;'''&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;match_results&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;fetch_data&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;results_query&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;match_results&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;calculate_season_summary&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;record&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;results&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;groupby&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;by&lt;/span&gt;&lt;span class="o"&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;result&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;team&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;count&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;summary&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="nc"&gt;DataFrame&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="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="n"&gt;record&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="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;L&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;record&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;L&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;D&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;record&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;D&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;Points&lt;/span&gt;&lt;span class="sh"&gt;'&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="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;points&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;sum&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="n"&gt;columns&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="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="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;D&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;L&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;Points&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
        &lt;span class="n"&gt;index&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;results&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;team&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;unique&lt;/span&gt;&lt;span class="p"&gt;(),&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;summary&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;draw_season_points_graph&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;dates&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;results&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;date&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="n"&gt;points&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;results&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;points&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;cumsum&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="n"&gt;figure&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;go&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Figure&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;
            &lt;span class="n"&gt;go&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Scatter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;dates&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;points&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;mode&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;lines+markers&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;layout&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;go&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Layout&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="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Points Accumulation&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;showlegend&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;False&lt;/span&gt;
        &lt;span class="p"&gt;)&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;figure&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h5&gt;
  
  
  Notes:
&lt;/h5&gt;

&lt;ul&gt;
&lt;li&gt;Data is queried from the database each time there is an interaction

&lt;ul&gt;
&lt;li&gt;We can &lt;a href="https://plot.ly/dash/performance" rel="noopener noreferrer"&gt;improve performance through memoization&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;We used f-strings when creating SQL queries. Code is Python 3.6+&lt;/li&gt;

&lt;/ul&gt;




&lt;h3&gt;
  
  
  Run Application
&lt;/h3&gt;

&lt;p&gt;Let's run &lt;code&gt;app.py&lt;/code&gt; to make sure everything works.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;DB_URI&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;sqlite:///soccer-stats.db
&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;python app.py
&lt;span class="go"&gt;* Running on http://0.0.0.0:8050/ (Press CTRL+C to quit)
* Restarting with stat
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Open a web browser...&lt;br&gt;&lt;br&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%2Flpaqrtxoayai0k3fs8xj.gif" 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%2Flpaqrtxoayai0k3fs8xj.gif" alt="Soccer Results Dashboard"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;And we're good to go!&lt;/p&gt;




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

&lt;p&gt;Dash is a Python library that simplifies data-driven web app development. It combines Python's powerful data ecosystem with one of JavaScript's most popular front-end libraries (&lt;a href="https://facebook.github.io/react/" rel="noopener noreferrer"&gt;React&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;In a future post, I will walk through the &lt;a href="https://github.com/plotly/dash-components-archetype" rel="noopener noreferrer"&gt;process of converting a React component&lt;/a&gt; from &lt;a href="https://www.npmjs.com/" rel="noopener noreferrer"&gt;npm&lt;/a&gt; into a Dash-useable component. Stay tuned.&lt;/p&gt;




&lt;h4&gt;
  
  
  Additional Resources
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://medium.com/@plotlygraphs/introducing-dash-5ecf7191b503" rel="noopener noreferrer"&gt;Dash Announcement Letter&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://plot.ly/dash/" rel="noopener noreferrer"&gt;Dash Documentation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;SciPy talk: &lt;a href="https://www.youtube.com/watch?v=sea2K4AuPOk" rel="noopener noreferrer"&gt;Dash - A New Framework for Building User Interfaces for Technical Computing&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href="https://codepen.io/chriddyp/pen/bWLwgP" rel="noopener noreferrer"&gt;Dash Style Guide&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://plot.ly/dash/gallery" rel="noopener noreferrer"&gt;Dash Gallery&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://plot.ly/python/" rel="noopener noreferrer"&gt;Plotly Documentation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/Acrotrend/awesome-dash" rel="noopener noreferrer"&gt;awesome-dash&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>python</category>
      <category>programming</category>
      <category>webdev</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>First Month as a Professional Developer</title>
      <dc:creator>Aly Sivji</dc:creator>
      <pubDate>Mon, 06 Nov 2017 06:04:55 +0000</pubDate>
      <link>https://forem.com/alysivji/first-month-as-a-professional-developer-169</link>
      <guid>https://forem.com/alysivji/first-month-as-a-professional-developer-169</guid>
      <description>&lt;p&gt;&lt;em&gt;This post was originally published on &lt;a href="http://bit.ly/siv-blog"&gt;Siv Scripts&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;The best laid schemes o' mice an' men&lt;/em&gt;&lt;br&gt;
&lt;em&gt;Gang aft agley&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;I started a new job as a Mathematician / Software Engineer at a Chicago-based healthcare startup in October. It's my first &lt;em&gt;proper&lt;/em&gt; development job and I could not be more excited.&lt;/p&gt;

&lt;p&gt;I get paid to write code. It's pretty awesome.&lt;/p&gt;

&lt;p&gt;Had always thought I would work in data, maybe transition into a data engineer role. But here we are. I'm working on a team of ~10 developers building a &lt;a href="https://www.healthit.gov/policy-researchers-implementers/clinical-decision-support-cds"&gt;clinical decision support&lt;/a&gt; web application.&lt;/p&gt;

&lt;p&gt;Guess this change isn't entirely out of left field; I had started to dig deeper into software engineering best practices over the past few months. Became fascinated by the different methodologies and frameworks that facilitate the production of good code. As a self-taught developer, I desired formality. There's only so much I could absorb by reading about other people's experiences and working on open source projects after work.&lt;/p&gt;

&lt;p&gt;I needed to get my hands dirty and get some real world practice writing code as part of a team. Thought it would be good to experience the process-based workflow of a structured Agile/Scrum development environment; to understand the pain of working (&lt;a href="https://www.amazon.com/Working-Effectively-Legacy-Michael-Feathers/dp/0131177052"&gt;effectively&lt;/a&gt;) with legacy code; and to have no choice but to write tests for the code I ship. And, preferrably, have those &lt;a href="https://www.amazon.com/Test-Driven-Development-Kent-Beck/dp/0321146530"&gt;tests be written first&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;It's only been a month, but I think I made the right decision. In one of my first commits, I made a change and took time to refactor code to increase readability ala &lt;a href="https://alysivji.github.io/review-code-complete.html"&gt;Code Complete&lt;/a&gt;. Got a shoutout during code review. That felt awesome! Software Craftsmanship FTW!&lt;/p&gt;

&lt;p&gt;I will say that I find myself constantly studying after work and on weekends so that I'm not &lt;em&gt;completely&lt;/em&gt; useless, but I wouldn't want it any other way. Coding at the edge of my abilities will increase my skills exponentially.&lt;/p&gt;

&lt;p&gt;While I eventually plan on going back to data, let's hold off thinking about when. Right now I'm enjoying life and want to stay on &lt;a href="http://knowyourmeme.com/memes/mr-bones-wild-ride"&gt;Mr. Bones' Wild Ride&lt;/a&gt;.&lt;/p&gt;




&lt;h3&gt;
  
  
  Mastering Development Tools
&lt;/h3&gt;

&lt;p&gt;The main reason I took a backend developer role was to level up my dev chops. This means many different things such as writing clean code, being able to identify and correctly apply design patterns, recognizing anti-patterns (code smells), and, of course, learning the tools of the trade.&lt;/p&gt;

&lt;p&gt;Over the past couple of weeks, I've started transitioning away from the friendly confines of VSCode and iTerm2 into the magical world of &lt;a href="http://www.vim.org/"&gt;Vim&lt;/a&gt; and &lt;a href="https://github.com/tmux/tmux/wiki"&gt;tmux&lt;/a&gt;. Even though I'm fairly proficient with a graphical IDE, I thought it was worth investing in tools that enable me to work as fast as I think.&lt;/p&gt;

&lt;p&gt;By switching to a terminal-only development environment, I hope to reduce the context switching that occurs everytime I reach for my mouse or trackpad. As an added bonus, I can SSH into any machine and feel at home. No more having to set up &lt;a href="https://en.wikipedia.org/wiki/Rsync"&gt;rsync&lt;/a&gt; or use &lt;a href="https://www.nano-editor.org/"&gt;nano&lt;/a&gt; as a crutch.&lt;/p&gt;

&lt;p&gt;&lt;a href="http://www.commitstrip.com/en/2016/12/22/terminal-forever/"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--3Hoj_qYF--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://alysivji.github.io/images/11-20/18_commit_strip_terminal_forever.jpg" alt="Commit Strip - Terminal Forever" width="650"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  How I'm Learning Vim
&lt;/h4&gt;

&lt;ol&gt;
&lt;li&gt;Worked my way through &lt;code&gt;$ vimtutor&lt;/code&gt;. It's a rite of passage.&lt;/li&gt;
&lt;li&gt;Enabled Vim keybindings in all my text editors (VSCode &amp;amp; Sublime) to get comfortable using the &lt;code&gt;h j k l&lt;/code&gt; movement keys. Being in a familiar environment where I could get work done without having to Google every command did help flatten the learning curve.&lt;/li&gt;
&lt;li&gt;Once I was able to make the cursor go where I wanted, I watched &lt;a href="https://www.youtube.com/watch?v=_NUO4JEtkDw"&gt;Learning Vim in a Week&lt;/a&gt;. Key takeaways:

&lt;ul&gt;
&lt;li&gt;Set up a basic &lt;code&gt;.vimrc&lt;/code&gt; file and throw it in Verison Control

&lt;ul&gt;
&lt;li&gt;My &lt;a href="https://github.com/alysivji/dotfiles"&gt;dotfile repo&lt;/a&gt; - Warning: Use at your own risk ðŸ˜œ&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Remap [CapsLock] to [Esc]

&lt;ul&gt;
&lt;li&gt;I went a &lt;a href="https://twitter.com/CaiusSivjus/status/924671391256981504"&gt;step further&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Use plugins for development, with included recommendations

&lt;ul&gt;
&lt;li&gt;I recommend the &lt;a href="https://github.com/junegunn/vim-plug"&gt;vim-plug&lt;/a&gt; plugin manager&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Last weekend, I installed tmux on my home machine so I could start getting familiar with how it works. My &lt;code&gt;.tmux.conf&lt;/code&gt; is very basic, but all the pieces are in place to enable me to learn at my own pace. I just have to make an effort to learn a couple of new things every week. Like everything else in life, it's about putting in the reps.&lt;/p&gt;

&lt;p&gt;You're only as good as your tools so it's worth making an investment into optimizing your workflow. We're developers and we should always be tinkering with the software we use on a daily basis. It doesn't have to be Vim and I'm not suggesting everybody go out and learn it. But do take some time to learn your tools and the plugin ecosystem of your IDE.&lt;/p&gt;

&lt;p&gt;Shoutout to the folks who responded to my &lt;a href="https://twitter.com/CaiusSivjus/status/924619886529253376"&gt;tweet&lt;/a&gt; with helpful suggestions. &lt;a href="http://knowyourmeme.com/memes/events/kevin-durant-mvp-speech"&gt;You Da Real MVPs&lt;/a&gt;!&lt;/p&gt;

&lt;h4&gt;
  
  
  Additional Resources
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://vimawesome.com/"&gt;List of Vim Plugins&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="http://www.hamvocke.com/blog/a-quick-and-easy-guide-to-tmux/"&gt;A Quick and Easy Guide to tmux&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="http://www.hamvocke.com/blog/a-guide-to-customizing-your-tmux-conf/"&gt;Making tmux Pretty and Usable - A Guide to Customizing your tmux.conf terminal&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://blog.bugsnag.com/tmux-and-vim/"&gt;tmux and Vim - even better together&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>career</category>
      <category>vim</category>
      <category>workflow</category>
      <category>terminal</category>
    </item>
    <item>
      <title>Terminal Tricks: Directory Bookmarks</title>
      <dc:creator>Aly Sivji</dc:creator>
      <pubDate>Sat, 07 Oct 2017 17:05:48 +0000</pubDate>
      <link>https://forem.com/alysivji/terminal-tricks-directory-bookmarks-329</link>
      <guid>https://forem.com/alysivji/terminal-tricks-directory-bookmarks-329</guid>
      <description>&lt;p&gt;&lt;em&gt;This post was originally published on &lt;a href="https://alysivji.github.io/" rel="noopener noreferrer"&gt;Siv Scripts&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Even though my development folders are well organized, I still have to feel my way around the filesystem when I'm looking for a project directory. It's like trying to find a lightswitch in a dark room: a &lt;code&gt;cd&lt;/code&gt; here, an &lt;code&gt;ls&lt;/code&gt; there, maybe a &lt;code&gt;find&lt;/code&gt; when I'm stuck.&lt;/p&gt;

&lt;p&gt;I always get to where I want to go, but not without some frustration along the way. Plus, all the seconds spent navigating directories starts to add up.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;em&gt;Wait a minute. I use bookmarks in my browser, so why am I not using them in my shell?&lt;/em&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;A quick Google search led me to &lt;a href="https://github.com/huyng/bashmarks" rel="noopener noreferrer"&gt;Bashmarks&lt;/a&gt;. Absolute game changer. I can move around the filesystem with ease.&lt;/p&gt;

&lt;p&gt;Feel a bit like Dr. Who.&lt;br&gt;&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Falysivji.github.io%2Fimages%2F11-20%2F17_tardis_meme.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%2Falysivji.github.io%2Fimages%2F11-20%2F17_tardis_meme.jpg" alt="Teleport like a boss" width="400" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In this &lt;a href="https://alysivji.github.io/category/quick-hits.html" rel="noopener noreferrer"&gt;Quick Hit&lt;/a&gt;, we will explore Bashmarks, walk through the installation process, and get a feel of the most commonly used commands.&lt;/p&gt;


&lt;h2&gt;
  
  
  Bashmarks
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;a href="https://github.com/huyng/bashmarks" rel="noopener noreferrer"&gt;Bashmarks is a [bash] shell script that allows you to save and jump to commonly used directories&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;It supports tab completion (!!!) and has a very simple interface with only 5 commands to memorize.&lt;/p&gt;

&lt;p&gt;Use another shell? Not a problem. There are ports of bashmarks for &lt;a href="https://github.com/techwizrd/fishmarks" rel="noopener noreferrer"&gt;fish&lt;/a&gt; and &lt;a href="https://github.com/jocelynmallon/zshmarks" rel="noopener noreferrer"&gt;zsh&lt;/a&gt;.&lt;/p&gt;
&lt;h3&gt;
  
  
  Installation
&lt;/h3&gt;

&lt;p&gt;Download the files into a temporary directory and install using &lt;a href="https://www.gnu.org/software/make/manual/make.html" rel="noopener noreferrer"&gt;GNU make&lt;/a&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="go"&gt;mkdir temp &amp;amp;&amp;amp; cd temp
git clone git://github.com/huyng/bashmarks.git
cd bashmarks
make install
&lt;/span&gt;&lt;span class="gp"&gt;echo "source ~/.local/bin/bashmarks.sh" &amp;gt;&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; ~/.bash_profile
&lt;span class="go"&gt;source ~/.bash_profile
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And we're good to go!&lt;/p&gt;

&lt;h3&gt;
  
  
  Commands
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;s &amp;lt;bookmark_name&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;- Saves the current directory as &lt;span class="s2"&gt;"bookmark_name"&lt;/span&gt;
&lt;span class="gp"&gt;g &amp;lt;bookmark_name&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;- Goes &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;cd&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; to the directory associated with &lt;span class="s2"&gt;"bookmark_name"&lt;/span&gt;
&lt;span class="gp"&gt;p &amp;lt;bookmark_name&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;- Prints the directory associated with &lt;span class="s2"&gt;"bookmark_name"&lt;/span&gt;
&lt;span class="gp"&gt;d &amp;lt;bookmark_name&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;- Deletes the bookmark
&lt;span class="go"&gt;l                 - Lists all available bookmarks
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We can save bookmarks using the &lt;code&gt;s [bookmark_name]&lt;/code&gt; command:&lt;br&gt;&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Falysivji.github.io%2Fimages%2F11-20%2F17_bashmarks_save.gif" 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%2Falysivji.github.io%2Fimages%2F11-20%2F17_bashmarks_save.gif" alt="Create bashmark" width="562" height="194"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;List all bashmarks with &lt;code&gt;l&lt;/code&gt;:&lt;br&gt;&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Falysivji.github.io%2Fimages%2F11-20%2F17_bashmarks_list.gif" 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%2Falysivji.github.io%2Fimages%2F11-20%2F17_bashmarks_list.gif" alt="List bashmarks" width="562" height="194"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Navigate to project directory using &lt;code&gt;g [bookmark_name]&lt;/code&gt;:&lt;br&gt;&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Falysivji.github.io%2Fimages%2F11-20%2F17_bashmarks_jump.gif" 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%2Falysivji.github.io%2Fimages%2F11-20%2F17_bashmarks_jump.gif" alt="Jump to bashmark" width="562" height="194"&gt;&lt;/a&gt;&lt;/p&gt;




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

&lt;p&gt;Bashmarks is a tool that increases developer productivity.&lt;/p&gt;

&lt;p&gt;Do you have an interesting terminal workflow or trick to share? Please comment below!&lt;/p&gt;

</description>
      <category>shell</category>
      <category>productivity</category>
      <category>terminal</category>
      <category>devtips</category>
    </item>
    <item>
      <title>Book Review: Code Complete</title>
      <dc:creator>Aly Sivji</dc:creator>
      <pubDate>Fri, 22 Sep 2017 16:10:57 +0000</pubDate>
      <link>https://forem.com/alysivji/book-review-code-complete</link>
      <guid>https://forem.com/alysivji/book-review-code-complete</guid>
      <description>&lt;p&gt;&lt;em&gt;This post was originally published on &lt;a href="https://alysivji.github.io/"&gt;Siv Scripts&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;2017 is the year I became deliberate about my approach to programming. Like most newbie developers, I hacked together spaghetti code around chunks of answers I found on StackOverflow &lt;em&gt;(Praise Be)&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;I didn't care how I solved the problem, only that it was solved enough to cross off my todo list.&lt;/p&gt;

&lt;p&gt;By ignoring recommended software development practices such as coding standards, version control, and testing, I ended up making things harder for myself in the long run.&lt;/p&gt;

&lt;p&gt;I worried about introducing bugs everytime I touched the codebase. I couldn't reuse code because my functions and modules were &lt;a href="https://en.wikipedia.org/wiki/Coupling_(computer_programming)"&gt;tightly coupled&lt;/a&gt;. It was difficult to wrap my brain around the complexities of large projects with many moving parts.&lt;/p&gt;

&lt;p&gt;Then I discovered &lt;em&gt;Code Complete&lt;/em&gt;. Steve McConnell changed the way I thought about code. He made me realize that the process of doing things methodically is a lot more important than simply solving the problem at hand.&lt;/p&gt;

&lt;p&gt;If we break down tasks into manageable components, we can build anything we can imagine.&lt;/p&gt;

&lt;p&gt;Go read &lt;em&gt;Code Complete&lt;/em&gt;. It will change your life the way it changed mine. Doesn't matter if you're a beginner or a seasoned professional, you'll become a better developer just by reading this book.&lt;/p&gt;

&lt;p&gt;In this post, I will summarize the themes of &lt;em&gt;Code Complete&lt;/em&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  Themes
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Reduce Complexity
&lt;/h3&gt;

&lt;blockquote&gt;
&lt;p&gt;A primary goal of software design and construction is conquering complexity. The motivation behind many programming practices is to reduce a program’s complexity, and reducing complexity is arguably the most important key to being an effective programmer.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;As we increase the number of things we keep track of, we increase our chances of introducing a bug in our program. If our code is readable, we are more likely to find logic errors.&lt;/p&gt;

&lt;p&gt;Two ways to reduce complexity:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Use helper functions&lt;/li&gt;
&lt;li&gt;Refactor conditional tests into readable boolean expressions&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;What's easier to read:&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;# okay
&lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;employee&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;all_employees&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;employee&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;active&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;employee&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# better
&lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;employee&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;all_employees&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;currently_employed&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;employee&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;active&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;currently_employed&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;employee&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h3&gt;
  
  
  Process Matters
&lt;/h3&gt;

&lt;blockquote&gt;
&lt;p&gt;Observing large processes and small processes means pausing to pay attention to how you create software. It’s time well spent. Saying that “code is what matters; you have to focus on how good the code is, not some abstract process is shortsighted and ignores mountains of experimental and practical evidence to the contrary. Software development is a creative exercise. If you don’t understand the creative process, you’re not getting the most out of the primary tool you use to create software–your brain. A bad process wastes your brain cycles. A good process leverages them to maximum advantage.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;It's about doing things in a way we can best guarantee success. Sure we can create a product that works, but it only works now. What if requirements change? How are we going to handle those changes?&lt;/p&gt;

&lt;p&gt;We are dealing with a living, breathing product. We need to leverage processes that help us handle changes as they arise.&lt;/p&gt;

&lt;p&gt;One of the reasons I love &lt;em&gt;Code Complete&lt;/em&gt; is that it introduced me to the concepts of TDD (Test Driven Development). After years of struggling with regression errors, I finally understood why testing is a large component of proper development methodologies. Tests make our lives easier in the long run.&lt;/p&gt;




&lt;h3&gt;
  
  
  Reability
&lt;/h3&gt;

&lt;blockquote&gt;
&lt;p&gt;You should go to the effort of writing good code, which you can do once, rather than the effort of reading bad code, which you’d have to do again and again.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Code is read by humans far more than it is written. Your interpreter or compiler doesn't care if it's clean or messy so write for your audience, i.e. you in six month's time.&lt;/p&gt;

&lt;p&gt;Both of these blocks of code produce the same output, but which one makes more sense?&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;# bad
&lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;my_list&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
  &lt;span class="k"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;

&lt;span class="c1"&gt;# good
&lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;employee&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;company_roster&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
  &lt;span class="k"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;employee&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h3&gt;
  
  
  Program into Your Language, Not in It
&lt;/h3&gt;

&lt;blockquote&gt;
&lt;p&gt;Don’t limit your programming thinking only to the concepts that are supported automatically by your language. The best programmers think of what they want to do, and then they assess how to accomplish their objectives with the programming tools at their disposal.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This is a topic I explored when I dug into &lt;a href="https://alysivji.github.io/managing-resources-with-context-managers-pythonic.html"&gt;Context Managers&lt;/a&gt;: doing things a certain way in one language doesn't mean that we should follow the same pattern in another language. When I came into Python from C# and JavaScript, I brought all my habits with me. Instead of looking for a Pythonic solution, I looked for the Python syntax to do things the way I've always done them.&lt;/p&gt;

&lt;p&gt;Always try to use your language of choice idiomatically.&lt;/p&gt;




&lt;h3&gt;
  
  
  Conventions for Clarity
&lt;/h3&gt;

&lt;blockquote&gt;
&lt;p&gt;The specific way in which ... a question is answered is less important than that it be answered consistently each time. Conventions save programmers the trouble of answering the same questions, making the same arbitrary decisions–again and again.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Programming is hard enough. Use style guides and logical variable names to make it easy for yourself and your team. Doing things in a standard way builds competency. Look for best practices and incorporate them into your process.&lt;/p&gt;




&lt;h3&gt;
  
  
  Program in Terms of the Problem Domain
&lt;/h3&gt;

&lt;blockquote&gt;
&lt;p&gt;Another specific method of dealing with complexity is to work at the highest possible level of abstraction. One way of working at a high level of abstraction is to work in terms of the programming problem rather than the computer science solution.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Keep business logic separate from the underlying implementation. Separate out parts of the project with helper functions, classes, or a REST API.&lt;/p&gt;

&lt;p&gt;The &lt;em&gt;This Developer's Life&lt;/em&gt; podcast featured an &lt;a href="http://thisdeveloperslife.com/post/1-0-6-abstraction"&gt;abstraction episode&lt;/a&gt; which discussed how we primarily concern ourselves with the layer we are currently working in. We might need to peel a layer or two from the onion when we encounter problems, but that's more on a need to know basis.&lt;/p&gt;




&lt;h3&gt;
  
  
  Watch for Warning Signs
&lt;/h3&gt;

&lt;blockquote&gt;
&lt;p&gt;Warning signs in programming alert you to the possibility of problems, but they’re usually not as blatant as a road sign that says “Watch for falling rocks.”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;a href="https://en.wikipedia.org/wiki/Code_smell"&gt;Code smells&lt;/a&gt;. Look out for patterns and symptoms that indicate a deeper problem. I'll dig into this more once I read &lt;a href="https://twitter.com/unclebobmartin"&gt;Uncle Bob&lt;/a&gt;'s &lt;a href="https://www.amazon.com/Clean-Code-Handbook-Software-Craftsmanship/dp/0132350882"&gt;Clean Code&lt;/a&gt;.&lt;/p&gt;




&lt;h3&gt;
  
  
  Iterate, Repeatedly, Again and Again
&lt;/h3&gt;

&lt;blockquote&gt;
&lt;p&gt;The more you iterate in each development activity, the better the product of that activity will be.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Iterating to a final product is a great way to build software since we can be sure that early versions of the feature are implemented in a manner that will be used.&lt;/p&gt;

&lt;p&gt;This is also a good approach to life. Iterate your processes, optimize your workflow as you figure things out.&lt;/p&gt;




&lt;h3&gt;
  
  
  Don't be Religious about Software
&lt;/h3&gt;

&lt;blockquote&gt;
&lt;p&gt;Dogmatic methodologies and high-quality software development don’t mix. Fill your intellectual toolbox with programming alternatives, and improve your skill at choosing the right tool for the job.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Always use the best tool for the job at hand. This might require us to learn a new language, tool, technique, or framework. That's okay. As programmers, we are meant to solve problems. Sometimes this requires us to piece together a solution from many different components.&lt;/p&gt;




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

&lt;p&gt;The first few chapters of &lt;em&gt;Code Complete&lt;/em&gt; totally changed the way I look at software development. I've started to approach programming like a craftsman learning a trade. I am but a simple apprentice learning from the masters who have come before me.&lt;/p&gt;

&lt;p&gt;I need to stay humble and realize that I don't know everything, but that I have the ability within me to figure out how to solve problems. Stand on the shoulders of giants. Use design patterns, algorithms, techniques, frameworks, or whatever else makes life easy. Just remember to approach development pragmatically.&lt;/p&gt;




&lt;h5&gt;
  
  
  References
&lt;/h5&gt;

&lt;p&gt;McConnell, S. (2004) Code Complete: A Practical Handbook of Software Construction, Second Edition. Microsoft Press. [ISBN: 0790145196705]&lt;/p&gt;

</description>
      <category>bestpractices</category>
      <category>programming</category>
      <category>books</category>
      <category>review</category>
    </item>
    <item>
      <title>Ultimate Solution to Python Virtual Environments: pyenv + virtualenvwrapper</title>
      <dc:creator>Aly Sivji</dc:creator>
      <pubDate>Tue, 29 Aug 2017 18:18:41 +0000</pubDate>
      <link>https://forem.com/alysivji/ultimate-solution-to-python-virtual-environments-pyenv--virtualenvwrapper</link>
      <guid>https://forem.com/alysivji/ultimate-solution-to-python-virtual-environments-pyenv--virtualenvwrapper</guid>
      <description>&lt;p&gt;(This post was originally published on &lt;a href="https://alysivji.github.io/" rel="noopener noreferrer"&gt;Siv Scripts&lt;/a&gt;)&lt;/p&gt;

&lt;p&gt;I love using the Command Line; it's my favourite part of programming. It wasn't always this way. I used to worry that the commands I entered would destory the delicate equilibrium where everything &lt;em&gt;just worked&lt;/em&gt;. Would this be the &lt;code&gt;pip install&lt;/code&gt; that ended it all?&lt;/p&gt;

&lt;p&gt;Enter virtual environments.&lt;/p&gt;

&lt;p&gt;A &lt;strong&gt;virtual environment&lt;/strong&gt; (venv) is a tool that allows us to keep project dependencies isolated. If Project A requires pandas version 0.17 and Project B requires pandas version 0.21, we can create a venv for each project and keep workflows independent. More info in the &lt;a href="https://docs.python.org/3/library/venv.html#venv-def" rel="noopener noreferrer"&gt;Python documentation&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;It's been around a year since I started using virtual environments in my projects, but I had yet to find the perfect dev workflow.&lt;/p&gt;

&lt;p&gt;Tried &lt;a href="https://www.continuum.io/what-is-anaconda" rel="noopener noreferrer"&gt;Anaconda&lt;/a&gt;; it was too bloated for non-Data Science work.&lt;/p&gt;

&lt;p&gt;Next came &lt;a href="https://virtualenv.pypa.io/en/stable/" rel="noopener noreferrer"&gt;virtualenv&lt;/a&gt; + &lt;a href="http://virtualenvwrapper.readthedocs.io/en/latest/" rel="noopener noreferrer"&gt;virtualevnwrapper&lt;/a&gt;. Did everything I wanted, but I had to manually manage Python installations when setting up environments for different versions.&lt;/p&gt;

&lt;p&gt;Like everyone who learns Docker, I went thru a &lt;a href="https://alysivji.github.io/containerized-development-environments.html" rel="noopener noreferrer"&gt;containerize all the things phase&lt;/a&gt;. The non-existent battery life on my Mac told me this wasn't the most feasible solution.&lt;/p&gt;

&lt;p&gt;Then I found pyenv...&lt;br&gt;&lt;br&gt;
&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Falysivji.github.io%2Fimages%2F11-20%2F13_penv_just_right_meme.jpg" 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%2Falysivji.github.io%2Fimages%2F11-20%2F13_penv_just_right_meme.jpg" alt="Just Right"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In this post, I will describe how to install pyenv and pyenv-virtualenvwrapper. I will also walk through the steps required to setup and use a new virtual environment.&lt;/p&gt;


&lt;h2&gt;
  
  
  pyenv
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;a href="https://github.com/pyenv/pyenv#simple-python-version-management-pyenv" rel="noopener noreferrer"&gt;pyenv lets you easily switch between multiple versions of Python&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3&gt;
  
  
  Background
&lt;/h3&gt;

&lt;p&gt;Anytime we run a command, the OS goes looking for the executible within the list of directories in the &lt;code&gt;$PATH&lt;/code&gt; environment variable.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="nv"&gt;$PATH&lt;/span&gt;
&lt;span class="go"&gt;/Users/alysivji/.local/bin:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin:/opt/X11/bin:/usr/local/git/bin:/Users/alysivji/bin
&lt;/span&gt;&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;which python
&lt;span class="go"&gt;/usr/bin/python
&lt;/span&gt;&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;which python3
&lt;span class="go"&gt;/usr/local/bin/python3
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We see both the default Python and Python3.6 installations are located in the &lt;code&gt;$PATH&lt;/code&gt; var.&lt;/p&gt;

&lt;p&gt;When we install pyenv, we add an additional directory of &lt;a href="https://en.wikipedia.org/wiki/Shim_(computing)" rel="noopener noreferrer"&gt;shims&lt;/a&gt; to the front of our &lt;code&gt;$PATH&lt;/code&gt;. Each shim intercepts commands (&lt;code&gt;python&lt;/code&gt;, &lt;code&gt;pip&lt;/code&gt;, etc) and redirects them to the appropriate location.&lt;/p&gt;

&lt;h3&gt;
  
  
  Installation
&lt;/h3&gt;

&lt;p&gt;Check the project's Github for &lt;a href="https://github.com/pyenv/pyenv#installation" rel="noopener noreferrer"&gt;detailed installation instructions&lt;/a&gt;. These instructions assume you have macOS + &lt;a href="https://brew.sh/" rel="noopener noreferrer"&gt;Homebrew&lt;/a&gt; installed.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="go"&gt;brew update
brew install pyenv
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Add the following line to &lt;code&gt;~/.bash_profile&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;eval "$&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;pyenv init -&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;"
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Let's make sure our installation worked:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;source&lt;/span&gt; ~/.bash_profile
&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="nv"&gt;$PATH&lt;/span&gt;
&lt;span class="go"&gt;/Users/alysivji/.pyenv/shims:/Users/alysivji/.local/bin:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin:/opt/X11/bin:/usr/local/git/bin:/Users/alysivji/bin
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Looks good! The shims at the front of our &lt;code&gt;$PATH&lt;/code&gt; variable will intercept and redirect all python-related commands.&lt;/p&gt;

&lt;h3&gt;
  
  
  Commands
&lt;/h3&gt;

&lt;p&gt;We use &lt;code&gt;pyenv install --list&lt;/code&gt; to get a list of all Python versions available for installation. Let's install a few different versions of Python so we can see how pyenv works.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="go"&gt;pyenv install 2.7.13
pyenv install pypy-5.7.1
pyenv install 3.6.2
pyenv install 3.7-dev
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Set the Python we want as our default version:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;pyenv global 3.6.2
&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;pyenv versions
&lt;span class="go"&gt;  system
  2.7.13
* 3.6.2 (set by /Users/alysivji/.pyenv/version)
  3.7-dev
  pypy-5.7.1
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;How does the shim know which version of Python to use? It goes down the &lt;a href="https://github.com/pyenv/pyenv#choosing-the-python-version" rel="noopener noreferrer"&gt;hierarchy&lt;/a&gt; until it finds what it's looking for.&lt;/p&gt;

&lt;p&gt;Activate different versions of Python using the &lt;code&gt;pyenv shell [version]&lt;/code&gt; command.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;which python
&lt;span class="go"&gt;/Users/alysivji/.pyenv/shims/python
&lt;/span&gt;&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;python
&lt;span class="go"&gt;Python 3.6.2 (default, Aug 24 2017, 00:00:01)
[GCC 4.2.1 Compatible Apple LLVM 8.0.0 (clang-800.0.42.1)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
&lt;/span&gt;&lt;span class="gp"&gt;&amp;gt;&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;exit&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;
&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;pyenv shell 2.7.13
&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;which python
&lt;span class="go"&gt;/Users/alysivji/.pyenv/shims/python
&lt;/span&gt;&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;python
&lt;span class="go"&gt;Python 2.7.13 (default, Aug 24 2017, 00:14:24)
[GCC 4.2.1 Compatible Apple LLVM 8.0.0 (clang-800.0.42.1)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
&lt;/span&gt;&lt;span class="gp"&gt;&amp;gt;&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Notice how the pyenv shims take care of everything for us. Next, we'll learn how to create a &lt;code&gt;venv&lt;/code&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  pyenv-virtualenvwrapper
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;a href="https://github.com/pyenv/pyenv-virtualenvwrapper" rel="noopener noreferrer"&gt;pyenv-virtualenvwrapper is a pyenv plugin ... to manage your virtualenvs with virtualenvwrapper&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Installation
&lt;/h3&gt;

&lt;p&gt;Detailed &lt;a href="https://github.com/pyenv/pyenv-virtualenvwrapper" rel="noopener noreferrer"&gt;instructions&lt;/a&gt; are on Github. macOS + &lt;a href="https://brew.sh/" rel="noopener noreferrer"&gt;Homebrew&lt;/a&gt; users can follow along:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="go"&gt;brew install pyenv-virtualenvwrapper
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Append to &lt;code&gt;~/.bash_profile&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="go"&gt;export PYENV_VIRTUALENVWRAPPER_PREFER_PYVENV="true"
&lt;/span&gt;&lt;span class="gp"&gt;export WORKON_HOME=$&lt;/span&gt;HOME/.virtualenvs
&lt;span class="go"&gt;pyenv virtualenvwrapper_lazy
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Reload the shell:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="go"&gt;source ~/.bash_profile
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Commands
&lt;/h3&gt;

&lt;p&gt;Anytime we install a new version of Python, we will need to install virtualenvwrapper. This is done with either the &lt;code&gt;pyenv virtualenvwrapper&lt;/code&gt; or &lt;code&gt;pyenv virtualenvwrapper_lazy&lt;/code&gt; (&lt;strong&gt;preferred&lt;/strong&gt;) command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;pyenv shell 3.6.2
&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;pyenv virtualenvwrapper_lazy
&lt;span class="go"&gt;Collecting virtualenvwrapper
Collecting stevedore (from virtualenvwrapper)
  Using cached stevedore-1.25.0-py2.py3-none-any.whl
Collecting virtualenv (from virtualenvwrapper)
  Using cached virtualenv-15.1.0-py2.py3-none-any.whl
Collecting virtualenv-clone (from virtualenvwrapper)
  Using cached virtualenv-clone-0.2.6.tar.gz
&lt;/span&gt;&lt;span class="gp"&gt;Collecting pbr!=2.1.0,&amp;gt;&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;2.0.0 &lt;span class="o"&gt;(&lt;/span&gt;from stevedore-&amp;gt;virtualenvwrapper&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="go"&gt;  Using cached pbr-3.1.1-py2.py3-none-any.whl
&lt;/span&gt;&lt;span class="gp"&gt;Collecting six&amp;gt;&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;1.9.0 &lt;span class="o"&gt;(&lt;/span&gt;from stevedore-&amp;gt;virtualenvwrapper&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="go"&gt;  Using cached six-1.10.0-py2.py3-none-any.whl
Installing collected packages: pbr, six, stevedore, virtualenv, virtualenv-clone, virtualenvwrapper
  Running setup.py install for virtualenv-clone ... done
Successfully installed pbr-3.1.1 six-1.10.0 stevedore-1.25.0 virtualenv-15.1.0 virtualenv-clone-0.2.6 virtualenvwrapper-4.7.2
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now we can use the &lt;a href="http://virtualenvwrapper.readthedocs.io/en/latest/command_ref.html" rel="noopener noreferrer"&gt;virtualenvwrapper shell commands&lt;/a&gt; to manage our environment:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;mkvirtualenv venv_test
&lt;span class="go"&gt;Using base prefix '/Users/alysivji/.pyenv/versions/3.6.2'
New python executable in /Users/alysivji/.virtualenvs/venv_test/bin/python3.6
Also creating executable in /Users/alysivji/.virtualenvs/venv_test/bin/python
Installing setuptools, pip, wheel...done.
virtualenvwrapper.user_scripts creating /Users/alysivji/.virtualenvs/venv_test/bin/predeactivate
virtualenvwrapper.user_scripts creating /Users/alysivji/.virtualenvs/venv_test/bin/postdeactivate
virtualenvwrapper.user_scripts creating /Users/alysivji/.virtualenvs/venv_test/bin/preactivate
virtualenvwrapper.user_scripts creating /Users/alysivji/.virtualenvs/venv_test/bin/postactivate
virtualenvwrapper.user_scripts creating /Users/alysivji/.virtualenvs/venv_test/bin/get_env_details
&lt;/span&gt;&lt;span class="gp"&gt;(venv_test) $&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;which python
&lt;span class="go"&gt;/Users/alysivji/.virtualenvs/venv_test/bin/python
&lt;/span&gt;&lt;span class="gp"&gt;(venv_test) $&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;python
&lt;span class="go"&gt;Python 3.6.2 (default, Aug 24 2017, 00:00:01)
[GCC 4.2.1 Compatible Apple LLVM 8.0.0 (clang-800.0.42.1)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
&lt;/span&gt;&lt;span class="gp"&gt;&amp;gt;&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We can also switch between virtual environments using the &lt;code&gt;deactivate&lt;/code&gt; and &lt;code&gt;workon&lt;/code&gt; commands:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;(venv_test) $&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;deactivate
&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;workon venv_test
&lt;span class="gp"&gt;(venv_test) $&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;python
&lt;span class="go"&gt;Python 3.6.2 (default, Aug 24 2017, 00:00:01)
[GCC 4.2.1 Compatible Apple LLVM 8.0.0 (clang-800.0.42.1)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
&lt;/span&gt;&lt;span class="gp"&gt;&amp;gt;&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's pretty much it. We can create a virtual environment for any version of Python in two commands.&lt;/p&gt;

&lt;h4&gt;
  
  
  Notes
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;virtualenvwrapper_lazy&lt;/code&gt; &lt;a href="https://www.reddit.com/r/Python/comments/11e773/tip_virtualenvwrapper_has_a_lazy_version_you_can/" rel="noopener noreferrer"&gt;decreases shell load time&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;




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

&lt;p&gt;We should always look for ways to optimize our development workflows. There are tools out there to help us manage complexity and make our life easy. Find these tools. Use them. It'll make you more productive in the long run.&lt;/p&gt;




&lt;h5&gt;
  
  
  Additional Resources
&lt;/h5&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://realpython.com/blog/python/python-virtual-environments-a-primer/" rel="noopener noreferrer"&gt;Real Python Primer on Virtual Environments&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="http://virtualenvwrapper.readthedocs.io/en/latest/command_ref.html" rel="noopener noreferrer"&gt;virtualenvwrapper Command List&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/pyenv/pyenv/blob/master/COMMANDS.md" rel="noopener noreferrer"&gt;pyenv Command Reference&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/pyenv/pyenv-virtualenv" rel="noopener noreferrer"&gt;pyenv-virtualenv&lt;/a&gt; - Alternative virtual environment manager for pyenv&lt;/li&gt;
&lt;li&gt;&lt;a href="https://direnv.net/" rel="noopener noreferrer"&gt;direnv&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>python</category>
      <category>tutorial</category>
      <category>productivity</category>
      <category>terminal</category>
    </item>
  </channel>
</rss>
