<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom"><title>Christopher C. Wells</title><link href="https://www.chris-wells.net/" rel="alternate"></link><link href="https://www.chris-wells.net/feeds/all.atom.xml" rel="self"></link><id>https://www.chris-wells.net/</id><updated>2025-07-19T07:45:00-07:00</updated><entry><title>Reading the 2025 Hugo Finalists</title><link href="https://www.chris-wells.net/articles/2025/04/18/reading-2025-hugo-finalists" rel="alternate"></link><published>2025-04-18T08:30:00-07:00</published><updated>2025-07-19T07:45:00-07:00</updated><author><name>Christopher Charbonneau Wells</name></author><id>tag:www.chris-wells.net,2025-04-18:/articles/2025/04/18/reading-2025-hugo-finalists</id><summary type="html">
&lt;p&gt;Having finished reading through the past &lt;a class="reference external" href="https://www.chris-wells.net/articles/2025/04/17/71-years-hugo"&gt;71 years of Hugo award winners&lt;/a&gt; and with the &lt;a class="reference external" href="https://seattlein2025.org/wsfs/hugo-awards/2025-hugo-award-finalists/"&gt;2025 Hugo Award finalists&lt;/a&gt; announced, I'm planning to spend the next few months reading some of the 2025 finalists&lt;/p&gt;
&lt;p&gt;I won't be writing &amp;quot;reviews&amp;quot; of the novels by any means. I will simply be capturing my opinions about each one so I can think about them as a whole during voting. I hope to keep this up year-to-year as I am a somewhat slow reader and I can't even imagine trying to follow &lt;em&gt;more&lt;/em&gt; novels over the course of the year in order to actually nominate something.&lt;/p&gt;
</summary><content type="html">&lt;span class="target" id="top"&gt;&lt;/span&gt;
&lt;p&gt;Having finished reading through the past &lt;a class="reference external" href="https://www.chris-wells.net/articles/2025/04/17/71-years-hugo"&gt;71 years of Hugo award winners&lt;/a&gt; and with the &lt;a class="reference external" href="https://seattlein2025.org/wsfs/hugo-awards/2025-hugo-award-finalists/"&gt;2025 Hugo Award finalists&lt;/a&gt; announced, I'm planning to spend the next few months reading some of the 2025 finalists&lt;/p&gt;
&lt;p&gt;I won't be writing &amp;quot;reviews&amp;quot; of the novels by any means. I will simply be capturing my opinions about each one so I can think about them as a whole during voting. I hope to keep this up year-to-year as I am a somewhat slow reader and I can't even imagine trying to follow &lt;em&gt;more&lt;/em&gt; novels over the course of the year in order to actually nominate something.&lt;/p&gt;

&lt;div class="section" id="best-novel"&gt;
&lt;h2&gt;Best Novel&lt;/h2&gt;
&lt;div class="section" id="best-novel-vote"&gt;
&lt;h3&gt;Best Novel Vote&lt;/h3&gt;
&lt;ol class="arabic simple"&gt;
&lt;li&gt;&lt;em&gt;Someone You Can Build a Nest In&lt;/em&gt; by John Wiswell&lt;/li&gt;
&lt;li&gt;&lt;em&gt;Alien Clay&lt;/em&gt; by Adrian Tchaikovsky&lt;/li&gt;
&lt;li&gt;&lt;em&gt;The Tainted Cup&lt;/em&gt; by Robert Jackson Bennett&lt;/li&gt;
&lt;li&gt;&lt;em&gt;A Sorceress Comes to Call&lt;/em&gt; by T. Kingfisher&lt;/li&gt;
&lt;li&gt;&lt;em&gt;Service Model&lt;/em&gt; by Adrian Tchaikovsky&lt;/li&gt;
&lt;li&gt;&lt;em&gt;The Ministry of Time&lt;/em&gt; by Kaliane Bradely&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;
&lt;div class="section" id="someone-you-can-build-a-nest-in"&gt;
&lt;h3&gt;Someone You Can Build a Nest In&lt;/h3&gt;
&lt;p&gt;&lt;a class="reference external" href="https://johnwiswell.blogspot.com/2024/02/someone-you-can-build-nest-in-launches.html"&gt;Someone You Can Build a Nest In&lt;/a&gt; by John Wiswell deals with themes that most other Hugo winners I have read do not (or do little): dependence, pain, family, parenting, &amp;quot;otherness&amp;quot;, self. It is a challenging read, yet has some good dark humor and a lot of action and adventure packed in.&lt;/p&gt;
&lt;p&gt;The opening paragraphs set up the world of the novel splendidly and makes one eager to read more (quickly, in my case). From her very birth, in memory, of Shesheshen to her hibernation and immediate peril there is action and interest in her experiences -- I felt on &amp;quot;her side&amp;quot; right away and carried that relationship through the whole novel.&lt;/p&gt;
&lt;p&gt;Then the introduction of Homily and slow unrolling of her life and relationship with Shesheshen made for an amazing story that was times silly and other times difficult to read and face. Homily's dependence on her family and Shesheshen's confusion over her feelings about Homily mixed so well even while keeping a pretty fast action pace with the Wulfyre's hunt for the wyrm.&lt;/p&gt;
&lt;p&gt;The ending was satisfying and my only real knock on the story was in the penultimate Part Seven. Shesheshen's confusing (even in the state she was in) with her sister felt a little unbelievable and predictable as it was playing out. And to some degree I lost the path of the Baroness in this section -- she seemed to kind of fade away inexplicably (OK, so a bomb had something to do with it) and then reappear in a much less favorable position for herself. That is, after her encounter with Shesheshen I would've expected her to seek out Epigram and Homily instead of allowing Homily and Shesheshen to connect again.&lt;/p&gt;
&lt;p&gt;Wiswell's dry, dark humor in places reminded me of Connie Willis and Martha Wells. And reading the acknowledgments at the end made it clear that Wiswell poured a lot of himself and his experiences in to the novel. That was surely no small task and I'm thankful he was able to make it work so well.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="service-model"&gt;
&lt;h3&gt;Service Model&lt;/h3&gt;
&lt;p&gt;Reading &lt;a class="reference external" href="https://en.wikipedia.org/wiki/Service_Model"&gt;Service Model&lt;/a&gt; by Adrian Tchaikovsky was neither good nor bad. It was just something I did. I was excited going in to this one given some favorable reviews comparing the writing to John Scalzi and Martha Wells (for Murderbot). The first few chapters dealing with Charles's crime, the doctor's visit, and the investigation of the detective was silly and downright hilarious at parts. From there the novel felt repetitive and tedious as Uncharles traveled along with &amp;quot;the Wonk&amp;quot; and learned about the post-apocalyptic state of the world.&lt;/p&gt;
&lt;p&gt;My mental image through most of the novel used characters straight out of &lt;a class="reference external" href="https://en.wikipedia.org/wiki/Futurama"&gt;Futurama&lt;/a&gt;. In my teens and twenties I was a huge fan of Futurama and a lot of the situations and scenarios Unchales and the Wonk encounter would have fit perfectly in to any season. From silly logical quips to obsessions with efficiency, the ridiculousness of bureaucracy, and the non-perfect functioning of systems that are meant to make human lives easier Service Model pokes fun at many of the same things that Futurama did. In this respect, Service Model was hindered by a much smaller &amp;quot;main cast&amp;quot; in Uncharles and the Wonk. Other characters came and went, but none stuck around or made much of impression (except perhaps the short-lived Inspector Birdbot and Doctor Namehere) as the giant cast of Futurama.&lt;/p&gt;
&lt;p&gt;After that, the farm, library, wasteland, and courthouse were more serious, less silly, and not well enough developed to really get in to. The dark humor lost a lot of it's edge as things got more serious along the way and none of the other secondary characters stood out as much. I also could never quite place how I felt about Uncharles not being unable to recognize that the Wonk was human. This led to a lot of amusing little anecdotes and did build up nicely in the end, but it was clear enough to the reader very early on that the revelation felt somewhat hollow and uninteresting when it (finally) happened.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="the-tainted-cup"&gt;
&lt;h3&gt;The Tainted Cup&lt;/h3&gt;
&lt;p&gt;&lt;a class="reference external" href="https://en.wikipedia.org/wiki/The_Tainted_Cup"&gt;The Tainted Cup&lt;/a&gt; by Robert Jackson Bennett is just as massive as the leviathans it portrays, which are only a secondary aspect of the plot! The scope of the novel reminded me of the first time I read &lt;a class="reference external" href="https://en.wikipedia.org/wiki/The_Fifth_Season_(novel)"&gt;The Fifth Season&lt;/a&gt; and I suspect that the next book in Bennett's planned series will show up as a Hugo finalist next year as well (much like the Broken Earth series). One key difference that I like about The Tainted Cup is the the first person POV (as opposed to Broken Earth's third person POV). I assume this is more common for murder mysteries anyway (I don't read many), but it helped pull me deeply in to the mystery and share confusion and excitement with Din (who is an excellent storyteller).&lt;/p&gt;
&lt;p&gt;The first few chapters were pretty heavy on the world-building and that paid off as the murders piled up and the plot thicken. The background of the leviathans and the forced deadline for Din and Ana's work was just as intriguing as the main storyline. I hope top read more in the series in the future.&lt;/p&gt;
&lt;p&gt;Once the background was establish I settled in to Din's voice and perspective. The unveiling of his special abilities and his fears about his shortcomings is well paced to the events of the novel. His relationship with Strovi could have used more development and felt a bit &amp;quot;tacked on&amp;quot;, but presumably (and hopefully?) that will built upon somehow in upcoming novels in the series (if not with Strovi specifically). Din's relationship with Miljin was much better addressed given it's important to the story and although it seems unlikely, I'd love to see more of Miljin in the future.&lt;/p&gt;
&lt;p&gt;Ana's eccentricities are often silly and fun to read. Her Sherlockian attributes are clear even at the very end of the novel when Din gifts her a box of &amp;quot;moodies&amp;quot;, making her happy as can be. Bennett does an excellent job providing the clues and pacing her revelations about each of the various murders. I recalled wondering at one moment about late in the novel what the title of has to do with anything only to have it revealed within a couple of pages. This felt random, but was actually well set up by the development of the plot and the big reveals in the final few chapters.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="the-ministry-of-time"&gt;
&lt;h3&gt;The Ministry of Time&lt;/h3&gt;
&lt;p&gt;&lt;a class="reference external" href="https://en.wikipedia.org/wiki/The_Ministry_of_Time_(novel)"&gt;The Ministry of Time&lt;/a&gt; by Kaliane Bradely imagines a depressing future where time travel is possible and the furthest known future is only a few hundred years away and in dire straits. So much so that the perceived &amp;quot;villains&amp;quot; of the first half of the novel end up seeming to me like the most redeemable characters in the novel. It does not end well for them in the novel's present or future and mostly I just felt bad for them by the end of the book.&lt;/p&gt;
&lt;p&gt;To Bradely's credit, the narrator is an excellent example of a &lt;a class="reference external" href="https://en.wikipedia.org/wiki/Unreliable_narrator"&gt;unreliable narrator&lt;/a&gt;. I spent much of the novel not trusting her and not liking her. The last few chapters just made that dislike even more intense -- to the point that I didn't much care about her (or the main plot) at all. In a way, that's an impressive achievement. Unfortunately for me, it made reading the novel quite unenjoyable.&lt;/p&gt;
&lt;p&gt;In the end, as the narrator continues to selfishly pursue her singular goal with no regard for the potentially devastating side effects, I mostly felt I had read a cautionary tale about the climate future of our planet. It felt real and depressing. I need something to anchor on in the novel and it just wasn't there. The expats were certainly likeable and fun to read at times, but ultimately they were only props to the inevitable storyline that never really piqued my interest.&lt;/p&gt;
&lt;p&gt;Aside from the time travel aspect, I struggled to think of this even as speculative fiction. It felt more like a very slow burning spy thriller that just happened to include some time travel (i.e., most of the story could have remained roughly the same without anyone walking through a time door).&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="a-sorceress-comes-to-call"&gt;
&lt;h3&gt;A Sorceress Comes to Call&lt;/h3&gt;
&lt;p&gt;&lt;a class="reference external" href="https://torpublishinggroup.com/a-sorceress-comes-to-call/"&gt;A Sorceress Comes to Call&lt;/a&gt; by T. Kingfisher is a character-driven story of deception and sorcery that builds slowly as all the pieces come together for an intense and violent finale. Most of the characters are well developed, there is lots of dark humor peppered throughout, and by the end I felt invested in the outcome.&lt;/p&gt;
&lt;p&gt;Cordelia's difficult relationship with her mother is established quickly in the first few chapters, but feels like the least developed relationship overall -- Evangeline is terrible to Cordelia and seems to see only her as an extension of herself. That doesn't change much throughout the novel. Cordelia is extensively developed through her relationships with all the other characters while Evangeline seems to remain mostly static.&lt;/p&gt;
&lt;p&gt;Hester and Lord Evermore's romantic subplot was, by comparison, deeply complex and well developed. I often found myself more interested in the development of that relationship than what was happening with Cordelia and Evangeline, possibly just because of the slow pacing of the novel overall.&lt;/p&gt;
&lt;p&gt;The various secondary characters were fun to read. Alice and Cordelia's relationship felt so vital to Alice's development. And Willard delivered some absolutely hilarious lines in the last stretch of the novel.&lt;/p&gt;
&lt;p&gt;Overall, I felt left just wanting to know more; about Evangeline primarily. But also about Falada, and Hester and Lord Evermore before Cordelia came into their lives. This novel read to me like a portion of a larger work and the slow development made it hard to just sink in to and read quacky.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="alien-clay"&gt;
&lt;h3&gt;Alien Clay&lt;/h3&gt;
&lt;p&gt;&lt;a class="reference external" href="https://en.wikipedia.org/wiki/Alien_Clay"&gt;Alien Clay&lt;/a&gt; by Adrian Tchaikovsky is exciting mix of broad, creative sci-fi, black humor, and political intrigue that perhaps feels a bit too close to current day politics. From the very beginning I am taken by Daghdev's plight and ridiculous ability to survive his arrival on Kiln and unfold the story of how he was sent there. The combination of a vast, highly imaginative sci-fi world and a fast-paced political action plot is something I am always drawn to. Tchaikovsky is an excellent in these regards.&lt;/p&gt;
&lt;p&gt;Daghdev's constant foreshadowing and examination of the situation and environment on Kiln is engaging for it's mix of serious and silly elements. I found myself really &lt;em&gt;wanting&lt;/em&gt; the first revolt to succeed even though it was pretty clear it would fail. Tchaikovsky's exploration of how and why movements against entrenched fascist regimes ultimately fail feels accurate (and frustrating!).&lt;/p&gt;
&lt;p&gt;Tying this thesis to Kiln's very nature and the second revolt is expertly done and makes for an enjoyable, but also deep read. It was hard to put down in the final chapters and is one of the better novel endings I have read in a while for how well it wraps up all the loose ends and provides an tantalizing setup for the character's next steps (even without necessarily making it feel like a series is needed).&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="section" id="best-novella"&gt;
&lt;h2&gt;Best Novella&lt;/h2&gt;
&lt;ol class="arabic simple"&gt;
&lt;li&gt;&lt;a class="reference external" href="https://www.raynayler.net/the-tusks-of-extinction.html"&gt;The Tusks of Extinction&lt;/a&gt; by Ray Nayler: a this story fits wonderfully in the &amp;quot;novella&amp;quot; category. There is just enough story, backstory, character development, and action. I'm not left wanting to know more or feeling like I didn't know enough. The plot is creative, the characters are engaging, and the slightly non-linear storytelling works well for the story. Damira's experience felt both sufficiently &amp;quot;sci-fi&amp;quot; and seemingly like a potentially reasonable thing that could in a not-too-distant future. I highly enjoy this type of speculative fiction.&lt;/li&gt;
&lt;li&gt;&lt;a class="reference external" href="https://en.wikipedia.org/wiki/What_Feasts_at_Night"&gt;What Feasts at Night&lt;/a&gt; by T. Kingfisher: a dark, haunting fairy tale about a possible witch in a cabin in the woods. Excellent stuff. I was not able to put this one down for the last third of the book. I particularly loved Kingfisher's treatment of &lt;em&gt;silence&lt;/em&gt; as a heavy, terrifying thing. I'll have to grab &lt;a class="reference external" href="https://en.wikipedia.org/wiki/What_Moves_the_Dead"&gt;What Moves the Dead&lt;/a&gt; for a future read.&lt;/li&gt;
&lt;li&gt;&lt;a class="reference external" href="https://premeemohamed.com/books/the-butcher-of-the-forest/"&gt;The Butcher of the Forest&lt;/a&gt; by Premee Mohamed: a tantalizing preview of a much larger world. At times this felt similar to &lt;em&gt;Nettle &amp;amp; Bone&lt;/em&gt; in it's fairy tale quality and storytelling. Veris's &amp;quot;false&amp;quot; worst memory was more graphic than it needed to be. And the Tyrant's decision about her fate in the end didn't fit well overall. This is story that would have benefitted more from a dark, depressing unhappy ending (not that it wasn't, it just could've been worse).&lt;/li&gt;
&lt;li&gt;&lt;a class="reference external" href="https://www.sofiasamatar.com/books/the-practice-the-horizon-and-the-chain/"&gt;The Practice, the Horizon, and the Chain&lt;/a&gt; by Sofia Samatar: an enjoyable read, but lacked any major &amp;quot;hook&amp;quot; in the story. Manual mining of asteroids using slave labor seems quite far-fetched. I was hoping for some twist that explained more about why the imagined society had established these casts beyond just: &amp;quot;to mine minerals&amp;quot;.&lt;/li&gt;
&lt;li&gt;&lt;a class="reference external" href="https://www.aliettedebodard.com/bibliography/novels/navigational-entanglements/"&gt;Navigational Entanglements&lt;/a&gt; by Aliette de Bodard: a lot of good elements in this story that added up to just too much content for a novella. I needed more time to understand the clan and personality relationships and also to envision what a &amp;quot;shadow&amp;quot; is. The actions and development of characters just came too fast and had too much going on for me to settle in to the story and/or enjoy any particular aspect of it.&lt;/li&gt;
&lt;li&gt;&lt;a class="reference external" href="https://nghivo.com/books/the-singing-hills-cycle/the-brides-of-high-hill/"&gt;The Brides of High Hill&lt;/a&gt; by Nghi Vo: while I enjoyed the way the novel ended, I felt like I missing a lot of pieces about the main character that presumably would have come from the other novellas in the series. It was difficult to connect with and understand Chih and too much of what unfolded in the story seemed require that understanding. I would rate this higher if considering an award specific to horror.&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;
&lt;div class="section" id="best-dramatic-presentation-long-form"&gt;
&lt;h2&gt;Best Dramatic Presentation (Long Form)&lt;/h2&gt;
&lt;ol class="arabic simple"&gt;
&lt;li&gt;&lt;a class="reference external" href="https://www.imdb.com/title/tt15239678/"&gt;Dune 2&lt;/a&gt;: I was a big fan of the first film in this series and the sequel absolutely did not disappoint. Excellent acting, cinematography, music, sounds, story. All around epic quality film.&lt;/li&gt;
&lt;li&gt;&lt;a class="reference external" href="https://www.imdb.com/title/tt4772188/"&gt;Flow&lt;/a&gt;: I watched this with young children (ages five and seven). I was worried they may not enjoy the different style of animal movie, but they were very into and loved talking about the animals and what they thought was happening in the world of the film. It was fun to enjoy a different style of animal movies for younger audiences.&lt;/li&gt;
&lt;li&gt;&lt;a class="reference external" href="https://www.imdb.com/title/tt1262426/"&gt;Wicked&lt;/a&gt;: I have never seen the musical so the movie was entirely new content and story for me. I enjoyed the story and the singing was excellent. My biggest complaint is probably just that it's only a Part One. It felt pretty complete as-is for setting up the character.&lt;/li&gt;
&lt;li&gt;&lt;a class="reference external" href="https://www.imdb.com/title/tt29623480/"&gt;The Wild Robot&lt;/a&gt;: This one was also popular with young children. The story and plot felt a little more interesting and in depth and I enjoyed the animation and voice performances.&lt;/li&gt;
&lt;li&gt;&lt;a class="reference external" href="https://www.imdb.com/title/tt12037194/"&gt;Furiosa: A Mad Max Saga&lt;/a&gt;: I was hoping for a Mad Max sequel more than this, but I enjoyed it more than I thought I would. The story and acting was good. My major complaint is with the odd style and cinematography used extensively in the film. Something felt &amp;quot;fake&amp;quot; or overly edited about some of the scenes and it was hard to shake that.&lt;/li&gt;
&lt;li&gt;&lt;a class="reference external" href="https://a24films.com/films/i-saw-the-tv-glow"&gt;I Saw the TV Glow&lt;/a&gt;: There was a lot of promise in this film. I liked the concept, the shooting style, the music, and the characters. The presentation was just too disjointed to settle in to. I wanted more Pink Opaque content and a deeper exploration of Maddy's beliefs about what was going on. And the end was just not satisfy enough for all of the possibilities that the film set up. Late 90s teen awkwardness was captures expertly so mostly I came away from the film feeling nostalgic and depressed.&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;
</content><category term="Literature"></category><category term="books"></category><category term="hugo"></category><category term="reading"></category><category term="sci-fi"></category></entry><entry><title>Reading 71 Years of Hugo Award Winners</title><link href="https://www.chris-wells.net/articles/2025/04/17/71-years-hugo" rel="alternate"></link><published>2025-04-17T20:40:00-07:00</published><updated>2025-04-17T20:40:00-07:00</updated><author><name>Christopher Charbonneau Wells</name></author><id>tag:www.chris-wells.net,2025-04-17:/articles/2025/04/17/71-years-hugo</id><summary type="html">
&lt;p&gt;Sometime back in May 2019 I started reading award winners of the &lt;a class="reference external" href="https://en.m.wikipedia.org/wiki/Hugo_Award_for_Best_Novel"&gt;Hugo award for best novel&lt;/a&gt; in chronological order starting with the first novel awarded (from 1953).&lt;/p&gt;
&lt;p&gt;I regret not taking more time to document the process. I don't even recall how I decided to follow the list. I just needed something to help me to decide &lt;em&gt;what&lt;/em&gt; to read at all. May 2019 is my best estimate based on exchange I had with my wife on Telegram in early June about Ben Reich from &lt;a class="reference external" href="https://en.wikipedia.org/wiki/The_Demolished_Man"&gt;The Demolished Man&lt;/a&gt;. Beyond that, I have kept only minimal &amp;quot;ratings&amp;quot; of the books and whatever stuck with me through 5+ years.&lt;/p&gt;
&lt;p&gt;I finished up just in time for the announcement of the 2025 Hugo award finalists so now I'm going to take the time to read all the nominees for the year and try to do a better job documenting my thoughts.&lt;/p&gt;
&lt;p&gt;Here are some short thoughts and recollections from all that reading (not reviews by any means!).&lt;/p&gt;
</summary><content type="html">&lt;span class="target" id="top"&gt;&lt;/span&gt;
&lt;p&gt;Sometime back in May 2019 I started reading award winners of the &lt;a class="reference external" href="https://en.m.wikipedia.org/wiki/Hugo_Award_for_Best_Novel"&gt;Hugo award for best novel&lt;/a&gt; in chronological order starting with the first novel awarded (from 1953).&lt;/p&gt;
&lt;p&gt;I regret not taking more time to document the process. I don't even recall how I decided to follow the list. I just needed something to help me to decide &lt;em&gt;what&lt;/em&gt; to read at all. May 2019 is my best estimate based on exchange I had with my wife on Telegram in early June about Ben Reich from &lt;a class="reference external" href="https://en.wikipedia.org/wiki/The_Demolished_Man"&gt;The Demolished Man&lt;/a&gt;. Beyond that, I have kept only minimal &amp;quot;ratings&amp;quot; of the books and whatever stuck with me through 5+ years.&lt;/p&gt;
&lt;p&gt;I finished up just in time for the announcement of the 2025 Hugo award finalists so now I'm going to take the time to read all the nominees for the year and try to do a better job documenting my thoughts.&lt;/p&gt;
&lt;p&gt;Here are some short thoughts and recollections from all that reading (not reviews by any means!).&lt;/p&gt;

&lt;div class="section" id="s-and-60s"&gt;
&lt;h2&gt;1950s and 60s&lt;/h2&gt;
&lt;p&gt;My primary memory of the first 17 years of books is &lt;a class="reference external" href="https://en.wikipedia.org/wiki/The_Man_in_the_High_Castle"&gt;The Man in the High Castle&lt;/a&gt;. My only exposure to Philip K. Dick before reading this one was his influence in &lt;em&gt;film&lt;/em&gt;: Blade Runner, Total Recall, Screamers, Minority Report, A Scanner Darkly, etc. The Man in the High Castle is the earliest of the reads with a more &amp;quot;believable&amp;quot; alternate reality. The scene that really sticks with me is Tagomi's &amp;quot;spiritual experience&amp;quot; of the alternate reality where Germany and Japan did not win World War II.&lt;/p&gt;
&lt;p&gt;Outside of that I most enjoyed &lt;a class="reference external" href="https://en.wikipedia.org/wiki/Starship_Troopers"&gt;Starship Troopers&lt;/a&gt;, &lt;a class="reference external" href="https://en.wikipedia.org/wiki/The_Wanderer_(Leiber_novel)"&gt;The Wanderer&lt;/a&gt;, &lt;a class="reference external" href="https://en.wikipedia.org/wiki/Dune_(novel)"&gt;Dune&lt;/a&gt;, and &lt;a class="reference external" href="https://en.wikipedia.org/wiki/The_Moon_Is_a_Harsh_Mistress"&gt;The Moon Is a Harsh Mistress&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;There were two I did not enjoy at all: &lt;a class="reference external" href="https://en.wikipedia.org/wiki/The_Big_Time_(novel)"&gt;The Big Time&lt;/a&gt;, which felt more like reading a play than a novel, and &lt;a class="reference external" href="https://en.wikipedia.org/wiki/Stand_on_Zanzibar"&gt;Stand on Zanzibar&lt;/a&gt;, which I did not actually finish. I'd like to at least go back to &lt;em&gt;Zanzibar&lt;/em&gt; someday as I don't recall what I disliked about it.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="s"&gt;
&lt;h2&gt;1970s&lt;/h2&gt;
&lt;p&gt;&lt;a class="reference external" href="https://en.wikipedia.org/wiki/Dreamsnake"&gt;Dreamsnake&lt;/a&gt; dominates almost all my memories of this period. It was the best novel I read to date and remains one of my favorites of all the Hugo winners. McIntyre's development of Snake as the main character is masterful and this felt like the most developed of &lt;em&gt;any&lt;/em&gt; female characters among Hugo winners to that point by far. If one read &lt;em&gt;Dreamsnake&lt;/em&gt; out of the blue today, the impact may not be as great. But reading it with the context of all previous Hugo winners, it felt like something entirely different and well ahead of it's time.&lt;/p&gt;
&lt;p&gt;The other novels from this era I enjoyed were: &lt;a class="reference external" href="https://en.wikipedia.org/wiki/Ringworld"&gt;Ringworld&lt;/a&gt;, &lt;a class="reference external" href="https://en.wikipedia.org/wiki/Rendezvous_with_Rama"&gt;Rendezvous with Rama&lt;/a&gt; and &lt;cite&gt;The Forever War&lt;/cite&gt;.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="s-1"&gt;
&lt;h2&gt;1980s&lt;/h2&gt;
&lt;p&gt;Just being able to produce a novel about dolphins in spaceships is an achievement in itself. Then to make the story (and ultimately the series) compelling and entertaining is just something else entirely. I remember going in to &lt;a class="reference external" href="https://en.wikipedia.org/wiki/Startide_Rising"&gt;Startide Rising&lt;/a&gt; with a lot of skepticism but Brin builds an amazingly creative and interesting world.&lt;/p&gt;
&lt;p&gt;The concept of &lt;a class="reference external" href="https://en.wikipedia.org/wiki/Uplift_(science_fiction)"&gt;uplift&lt;/a&gt; in general would benefit from a lot more literature and there are other novels I'll check out in the future that deal with it. &lt;a class="reference external" href="https://en.wikipedia.org/wiki/The_Uplift_War"&gt;The Uplift War&lt;/a&gt; was also a fun read and maybe only doesn't register as a favorite because Startide was some good and I read it first. I'll probably read more of Brin's series in the future (when I have decide on stuff to read on my own!).&lt;/p&gt;
&lt;p&gt;The thing I enjoyed most about &lt;a class="reference external" href="https://en.wikipedia.org/wiki/Ender%27s_Game"&gt;Ender's Game&lt;/a&gt; is that I went in to it with no background about the book or movie. The build up to the midpoint of the novel was entertaining and I didn't really expect it to develop in the way it did for the second half. The second half, Ender's experience with the queen and quest to find a new home for the &amp;quot;buggers&amp;quot;, was what really drew me in and I finished this one excited to read the sequel next.&lt;/p&gt;
&lt;p&gt;In general reading through the Hugo's in order has been kind of difficult when I come across a series or author I enjoy. I wanted to stick to the plan and avoiding reading anything else (also because I am a slow reader and wanted to actually finish/catch up the present) so getting to read a sequel right away was a treat.&lt;/p&gt;
&lt;p&gt;I remember thinking of &lt;a class="reference external" href="https://en.wikipedia.org/wiki/Speaker_for_the_Dead"&gt;Speaker for the Dead&lt;/a&gt; as the better of the two novels. It felt like a much needed continuation of Ender's Game to explore the more interesting themes dealing with Ender's role as &amp;quot;speaker for the dead&amp;quot;.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="s-2"&gt;
&lt;h2&gt;1990s&lt;/h2&gt;
&lt;p&gt;The years of &lt;a class="reference external" href="https://en.wikipedia.org/wiki/Lois_McMaster_Bujold"&gt;Lois McMaster Bujold&lt;/a&gt; and &lt;a class="reference external" href="https://en.wikipedia.org/wiki/Connie_Willis"&gt;Connie Willis&lt;/a&gt;!&lt;/p&gt;
&lt;p&gt;&lt;a class="reference external" href="https://en.wikipedia.org/wiki/The_Vor_Game"&gt;The Vor Game&lt;/a&gt;, &lt;a class="reference external" href="https://en.wikipedia.org/wiki/Barrayar"&gt;Barrayar&lt;/a&gt;, and &lt;a class="reference external" href="https://en.m.wikipedia.org/wiki/Mirror_Dance"&gt;Mirror Dance&lt;/a&gt; are all such fun reads! The development of Miles as a primary character that uses intellect over strength in a universe that highly prioritizes physical attributes like strength and beauty is excellent. Bujold builds good suspense and action, keeps plots moving, and creates compelling characters and storylines. While I have stayed mostly committed to the list, this is &lt;em&gt;definitely&lt;/em&gt; a universe that I will revisit (and probably push my young children too when they are more in the &amp;quot;Young Adult&amp;quot; reading age).&lt;/p&gt;
&lt;p&gt;1993 was a great year for the Hugo award. I thoroughly enjoyed &lt;a class="reference external" href="https://en.wikipedia.org/wiki/A_Fire_Upon_the_Deep"&gt;A Fire Upon the Deep&lt;/a&gt; and it only suffers in memory because of how much I loved &lt;a class="reference external" href="https://en.wikipedia.org/wiki/Doomsday_Book_(novel)"&gt;Doomsday Book&lt;/a&gt;. I remember feeling intimidated by Doomsday Book for it's length but ended up reading it quickly because it was just so intense and ridiculous and enjoyable. I also ending up reading and learning a lot about the &lt;a class="reference external" href="https://en.wikipedia.org/wiki/Black_Death"&gt;Black Death&lt;/a&gt; and just became so attached to Kivrin's plight and the efforts to recover her. It is one of the most enthralling, terrifying, and humorous books I've ever read.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="s-3"&gt;
&lt;h2&gt;2000s&lt;/h2&gt;
&lt;p&gt;The two winners from the 2000s that stand out most in my memory are &lt;a class="reference external" href="https://en.m.wikipedia.org/wiki/Paladin_of_Souls"&gt;Paladin of Souls&lt;/a&gt; and &lt;a class="reference external" href="https://en.wikipedia.org/wiki/The_Yiddish_Policemen%27s_Union"&gt;The Yiddish Policemen's Union&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Paladin&lt;/em&gt; felt by far the most like a &amp;quot;fantasy&amp;quot; as opposed to sci-fi and despite my enjoyment of everything else Bujold I was a skeptical going in it. But of course the story was complex and interesting, the characters were well developed, and I read through it much faster than usual because it was so engrossing.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;The Yiddish Policemen's Union&lt;/em&gt; reminded me of &lt;em&gt;The Man in the High Castle&lt;/em&gt; as they both created well defined alternative realities that were easy to accept and enjoy reading about. This was also the first detective novel I have read and I found I thoroughly enjoyed it.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="s-4"&gt;
&lt;h2&gt;2010s&lt;/h2&gt;
&lt;p&gt;The 2010s were full of great novels, or maybe it's just the fact that I remember more clearly those recent reads.&lt;/p&gt;
&lt;p&gt;Just a few years after &lt;em&gt;The Yiddish Policemen's Union&lt;/em&gt; I ended up enjoying another detective novel in &lt;a class="reference external" href="https://en.wikipedia.org/wiki/The_City_%26_the_City"&gt;The City and the City&lt;/a&gt;. This was my first exposure to &lt;a class="reference external" href="https://en.wikipedia.org/wiki/Weird_fiction"&gt;weird fiction&lt;/a&gt; and its definitely a genre I'll pursue more in the future. I also sought out and enjoyed the &lt;a class="reference external" href="https://en.wikipedia.org/wiki/The_City_and_the_City_(TV_series)"&gt;BBC Two TV series&lt;/a&gt; adaptation and was happy with how it compared to what I saw in mind (mostly).&lt;/p&gt;
&lt;p&gt;I have loved everything Connie Willis and was really looking forward to &lt;a class="reference external" href="https://en.wikipedia.org/wiki/Blackout/All_Clear"&gt;Blackout/All Clear&lt;/a&gt;. Her style and substance was there but clocking in at over 1,000 pages it did feel a bit like a slog towards the end (and was very clearly meant to be one novel given how abruptly Blackout ended). By no means did it turn me off to Willis, it just felt like a lot.&lt;/p&gt;
&lt;p&gt;Comparatively, &lt;a class="reference external" href="https://en.wikipedia.org/wiki/Among_Others"&gt;Among Others&lt;/a&gt; was a fun, short read. It felt close to weird fiction, but maybe that was because I had read &lt;em&gt;The City and the City&lt;/em&gt; so recently. I really loved the way the story hinted at magical things without committing and keeping something of an unreliable narrator vibe. It genuinely felt like I was reading a 15 year old's diary.&lt;/p&gt;
&lt;p&gt;I went in to &lt;a class="reference external" href="https://en.wikipedia.org/wiki/Redshirts_(novel)"&gt;Redshirts&lt;/a&gt; without knowing anything about the novel or John Scalzi generally and boy was it a fun surprise. MY only exposure to Star Trek has been the recent films so I got to spend lots of times looking up and learning about the ridiculous and fun little things the novel poked fun at. It was nice to be read something so silly.&lt;/p&gt;
&lt;p&gt;Reading &lt;a class="reference external" href="https://en.wikipedia.org/wiki/Ancillary_Justice"&gt;Ancillary Justice&lt;/a&gt; after all the more genre-twisting entries from the 2000s and 2010s may have impacted my feelings about it. While I enjoyed the read, it felt more &amp;quot;old school&amp;quot; in a bad way and perhaps a bit boring. There are many entries from the earlier years I do &lt;em&gt;not&lt;/em&gt; mention here for similar reasons -- they were OK, if not memorable.&lt;/p&gt;
&lt;p&gt;&lt;a class="reference external" href="https://en.wikipedia.org/wiki/The_Three-Body_Problem_(novel)"&gt;The Three-Body Problem&lt;/a&gt; was certainly the most &amp;quot;hyped&amp;quot; novel I read of any of the Hugo winners. It was the only one I'd heard of outside the context of reading through the Hugos. I went in to thinking it must be overrated, but I was very much (and happily) wrong! It is a great sci-fi/kind of horror mix. I watched and mostly enjoyed the &lt;a class="reference external" href="https://en.wikipedia.org/wiki/3_Body_Problem_(TV_series)"&gt;Netflix adaptation&lt;/a&gt; and someday I'll commit the time to the &lt;a class="reference external" href="https://en.wikipedia.org/wiki/Three-Body"&gt;Chinese TV adaptation&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;I absolutely loved the &lt;a class="reference external" href="https://en.wikipedia.org/wiki/N._K._Jemisin#Broken_Earth_series"&gt;Broken Earth series&lt;/a&gt;. Just a lovely blend of sci-fi and fantasy and ultimately an insane, difficult, and touching story about motherhood. Since becoming a parent I tend to find it difficult to read books and watch TV or film that deals with parenting (or young children generally) and this series does a great job of exploring the difficulties of those relationships.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="s-so-far"&gt;
&lt;h2&gt;2020s (so far)&lt;/h2&gt;
&lt;p&gt;&lt;a class="reference external" href="https://en.wikipedia.org/wiki/A_Memory_Called_Empire"&gt;A Memory Called Empire&lt;/a&gt; and &lt;a class="reference external" href="https://en.wikipedia.org/wiki/A_Desolation_Called_Peace"&gt;A Desolation Called Peace&lt;/a&gt; felt like sort of anti-&lt;em&gt;Ancillary Justice&lt;/em&gt; in that they brought more depth and intensity to the space opera and kind of &amp;quot;updated&amp;quot; it over their predecessors. I enjoyed the &amp;quot;political&amp;quot; aspects of the novel and how it examined language and culture. It reminded me about how much moving out of the United States for a few years got me to recognize the intricacies of culture.&lt;/p&gt;
&lt;p&gt;&lt;a class="reference external" href="https://en.wikipedia.org/wiki/Network_Effect_(novel)"&gt;Network Effect&lt;/a&gt; was &lt;em&gt;such&lt;/em&gt; a joy to read. Like &lt;em&gt;Redshirts&lt;/em&gt;, it brought some fun in to the genre and Wells's writing and humor is just wonderful. Her dry and sometimes sadistic humor reminded me of how I felt early in my life reading Vonnegut. After so many years of sticking to reading the Hugo award winners and nothing else I actually took a short break to read up everything &lt;a class="reference external" href="https://en.wikipedia.org/wiki/The_Murderbot_Diaries"&gt;The Murderbot Diaries&lt;/a&gt; and I was sad when I finished them all. I've got high hopes for the Apple TV adaptation.&lt;/p&gt;
&lt;p&gt;&lt;a class="reference external" href="https://en.wikipedia.org/wiki/Nettle_%26_Bone"&gt;Nettle &amp;amp; Bone&lt;/a&gt; was another great fantasy read like &lt;em&gt;Paladin of Souls&lt;/em&gt;. Despite being straightforward and short I greatly enjoyed the story and it made me think of something like &amp;quot;fantasy fairy tales&amp;quot; as a separate subgenre of interest. In a similar vein, I enjoyed &lt;a class="reference external" href="https://en.wikipedia.org/wiki/Uprooted_(novel)"&gt;Uprooted&lt;/a&gt; as a recommended read after I finished the full list of was waiting for the 2025 nominees to be announced.&lt;/p&gt;
&lt;/div&gt;
</content><category term="Literature"></category><category term="books"></category><category term="hugo"></category><category term="reading"></category><category term="sci-fi"></category></entry><entry><title>Pipenv to UV</title><link href="https://www.chris-wells.net/articles/2025/04/12/pipenv-to-uv" rel="alternate"></link><published>2025-04-12T10:00:00-07:00</published><updated>2025-04-12T10:00:00-07:00</updated><author><name>Christopher Charbonneau Wells</name></author><id>tag:www.chris-wells.net,2025-04-12:/articles/2025/04/12/pipenv-to-uv</id><summary type="html">
&lt;p&gt;I have been (mostly) happily using &lt;a class="reference external" href="https://pipenv.pypa.io/en/latest/"&gt;Pipenv&lt;/a&gt; for many years now on all of my Python projects. Recently, I heard about &lt;a class="reference external" href="https://docs.astral.sh/uv/"&gt;UV&lt;/a&gt; so I'm giving it a try with the very small set of Python dependencies used for this blog.&lt;/p&gt;
</summary><content type="html">&lt;span class="target" id="top"&gt;&lt;/span&gt;
&lt;p&gt;I have been (mostly) happily using &lt;a class="reference external" href="https://pipenv.pypa.io/en/latest/"&gt;Pipenv&lt;/a&gt; for many years now on all of my Python projects. Recently, I heard about &lt;a class="reference external" href="https://docs.astral.sh/uv/"&gt;UV&lt;/a&gt; so I'm giving it a try with the very small set of Python dependencies used for this blog.&lt;/p&gt;

&lt;div class="section" id="installation"&gt;
&lt;h2&gt;Installation&lt;/h2&gt;
&lt;p&gt;I'm also moving to using a devcontainer for this blog's dependencies so for installation I'm just using the devcontainer &lt;a class="reference external" href="https://github.com/va-h/devcontainers-features/tree/main/src/uv"&gt;UV feature&lt;/a&gt;. Then I install the Python version I want to use (3.12 here):&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;table class="highlighttable"&gt;&lt;tr&gt;&lt;td class="linenos"&gt;&lt;div class="linenodiv"&gt;&lt;pre&gt;&lt;span class="normal"&gt;0&lt;/span&gt;
&lt;span class="normal"&gt;1&lt;/span&gt;
&lt;span class="normal"&gt;2&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;div&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;uv&lt;span class="w"&gt; &lt;/span&gt;python&lt;span class="w"&gt; &lt;/span&gt;install&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;3&lt;/span&gt;.12
Installed&lt;span class="w"&gt; &lt;/span&gt;Python&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;3&lt;/span&gt;.12.10&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;in&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;5&lt;/span&gt;.25s
&lt;span class="w"&gt; &lt;/span&gt;+&lt;span class="w"&gt; &lt;/span&gt;cpython-3.12.10-linux-x86_64-gnu
&lt;/pre&gt;&lt;/div&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/div&gt;
&lt;p&gt;And initial my project:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;table class="highlighttable"&gt;&lt;tr&gt;&lt;td class="linenos"&gt;&lt;div class="linenodiv"&gt;&lt;pre&gt;&lt;span class="normal"&gt;0&lt;/span&gt;
&lt;span class="normal"&gt;1&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;div&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;uv&lt;span class="w"&gt; &lt;/span&gt;init
Initialized&lt;span class="w"&gt; &lt;/span&gt;project&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="sb"&gt;`&lt;/span&gt;chris-wells.net&lt;span class="sb"&gt;`&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/div&gt;
&lt;p&gt;This adds a &lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;.python-version&lt;/span&gt;&lt;/tt&gt; file to the repo to specify the default Python version, a &lt;tt class="docutils literal"&gt;main.py&lt;/tt&gt; (which is this case I delete), a &lt;tt class="docutils literal"&gt;pyproject.toml&lt;/tt&gt; file with project information, and an empty &lt;tt class="docutils literal"&gt;README.md&lt;/tt&gt;.&lt;/p&gt;
&lt;p&gt;Then, I instally my short list of requirements currently in the &lt;tt class="docutils literal"&gt;Pipfile&lt;/tt&gt;.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;table class="highlighttable"&gt;&lt;tr&gt;&lt;td class="linenos"&gt;&lt;div class="linenodiv"&gt;&lt;pre&gt;&lt;span class="normal"&gt; 0&lt;/span&gt;
&lt;span class="normal"&gt; 1&lt;/span&gt;
&lt;span class="normal"&gt; 2&lt;/span&gt;
&lt;span class="normal"&gt; 3&lt;/span&gt;
&lt;span class="normal"&gt; 4&lt;/span&gt;
&lt;span class="normal"&gt; 5&lt;/span&gt;
&lt;span class="normal"&gt; 6&lt;/span&gt;
&lt;span class="normal"&gt; 7&lt;/span&gt;
&lt;span class="normal"&gt; 8&lt;/span&gt;
&lt;span class="normal"&gt; 9&lt;/span&gt;
&lt;span class="normal"&gt;10&lt;/span&gt;
&lt;span class="normal"&gt;11&lt;/span&gt;
&lt;span class="normal"&gt;12&lt;/span&gt;
&lt;span class="normal"&gt;13&lt;/span&gt;
&lt;span class="normal"&gt;14&lt;/span&gt;
&lt;span class="normal"&gt;15&lt;/span&gt;
&lt;span class="normal"&gt;16&lt;/span&gt;
&lt;span class="normal"&gt;17&lt;/span&gt;
&lt;span class="normal"&gt;18&lt;/span&gt;
&lt;span class="normal"&gt;19&lt;/span&gt;
&lt;span class="normal"&gt;20&lt;/span&gt;
&lt;span class="normal"&gt;21&lt;/span&gt;
&lt;span class="normal"&gt;22&lt;/span&gt;
&lt;span class="normal"&gt;23&lt;/span&gt;
&lt;span class="normal"&gt;24&lt;/span&gt;
&lt;span class="normal"&gt;25&lt;/span&gt;
&lt;span class="normal"&gt;26&lt;/span&gt;
&lt;span class="normal"&gt;27&lt;/span&gt;
&lt;span class="normal"&gt;28&lt;/span&gt;
&lt;span class="normal"&gt;29&lt;/span&gt;
&lt;span class="normal"&gt;30&lt;/span&gt;
&lt;span class="normal"&gt;31&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;div&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;$&lt;span class="w"&gt; &lt;/span&gt;uv&lt;span class="w"&gt; &lt;/span&gt;add&lt;span class="w"&gt; &lt;/span&gt;pelican&lt;span class="w"&gt; &lt;/span&gt;markdown&lt;span class="w"&gt; &lt;/span&gt;pelican-youtube
Using&lt;span class="w"&gt; &lt;/span&gt;CPython&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;3&lt;/span&gt;.12.10&lt;span class="w"&gt; &lt;/span&gt;interpreter&lt;span class="w"&gt; &lt;/span&gt;at:&lt;span class="w"&gt; &lt;/span&gt;/usr/local/bin/python3.12
Creating&lt;span class="w"&gt; &lt;/span&gt;virtual&lt;span class="w"&gt; &lt;/span&gt;environment&lt;span class="w"&gt; &lt;/span&gt;at:&lt;span class="w"&gt; &lt;/span&gt;.venv
Resolved&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;24&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;packages&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;in&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;564ms
Prepared&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;22&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;packages&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;in&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;4&lt;/span&gt;.34s
░░░░░░░░░░░░░░░░░░░░&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;/22&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;Installing&lt;span class="w"&gt; &lt;/span&gt;wheels...

warning:&lt;span class="w"&gt; &lt;/span&gt;Failed&lt;span class="w"&gt; &lt;/span&gt;to&lt;span class="w"&gt; &lt;/span&gt;hardlink&lt;span class="w"&gt; &lt;/span&gt;files&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;falling&lt;span class="w"&gt; &lt;/span&gt;back&lt;span class="w"&gt; &lt;/span&gt;to&lt;span class="w"&gt; &lt;/span&gt;full&lt;span class="w"&gt; &lt;/span&gt;copy.&lt;span class="w"&gt; &lt;/span&gt;This&lt;span class="w"&gt; &lt;/span&gt;may&lt;span class="w"&gt; &lt;/span&gt;lead&lt;span class="w"&gt; &lt;/span&gt;to&lt;span class="w"&gt; &lt;/span&gt;degraded&lt;span class="w"&gt; &lt;/span&gt;performance.&lt;span class="w"&gt; &lt;/span&gt;If&lt;span class="w"&gt; &lt;/span&gt;the&lt;span class="w"&gt; &lt;/span&gt;cache&lt;span class="w"&gt; &lt;/span&gt;and&lt;span class="w"&gt; &lt;/span&gt;target&lt;span class="w"&gt; &lt;/span&gt;directories&lt;span class="w"&gt; &lt;/span&gt;are&lt;span class="w"&gt; &lt;/span&gt;on&lt;span class="w"&gt; &lt;/span&gt;different&lt;span class="w"&gt; &lt;/span&gt;filesystems,&lt;span class="w"&gt; &lt;/span&gt;hardlinking&lt;span class="w"&gt; &lt;/span&gt;may&lt;span class="w"&gt; &lt;/span&gt;not&lt;span class="w"&gt; &lt;/span&gt;be&lt;span class="w"&gt; &lt;/span&gt;supported.&lt;span class="w"&gt; &lt;/span&gt;If&lt;span class="w"&gt; &lt;/span&gt;this&lt;span class="w"&gt; &lt;/span&gt;is&lt;span class="w"&gt; &lt;/span&gt;intentional,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;set&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="sb"&gt;`&lt;/span&gt;&lt;span class="nb"&gt;export&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;UV_LINK_MODE&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;copy&lt;span class="sb"&gt;`&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;or&lt;span class="w"&gt; &lt;/span&gt;use&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="sb"&gt;`&lt;/span&gt;--link-mode&lt;span class="o"&gt;=&lt;/span&gt;copy&lt;span class="sb"&gt;`&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;to&lt;span class="w"&gt; &lt;/span&gt;suppress&lt;span class="w"&gt; &lt;/span&gt;this&lt;span class="w"&gt; &lt;/span&gt;warning.

Installed&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;22&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;packages&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;in&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;72ms
+&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;anyio&lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;&lt;span class="m"&gt;4&lt;/span&gt;.9.0
+&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;blinker&lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;.9.0
+&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;docutils&lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;.21.2
+&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;feedgenerator&lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;&lt;span class="m"&gt;2&lt;/span&gt;.1.0
+&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;idna&lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;&lt;span class="m"&gt;3&lt;/span&gt;.10
+&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;jinja2&lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;&lt;span class="m"&gt;3&lt;/span&gt;.1.6
+&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;markdown&lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;&lt;span class="m"&gt;3&lt;/span&gt;.8
+&lt;span class="w"&gt; &lt;/span&gt;markdown-it-py&lt;span class="o"&gt;==&lt;/span&gt;&lt;span class="m"&gt;3&lt;/span&gt;.0.0
+&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;markupsafe&lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;&lt;span class="m"&gt;3&lt;/span&gt;.0.2
+&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;mdurl&lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;.1.2
+&lt;span class="w"&gt; &lt;/span&gt;ordered-set&lt;span class="o"&gt;==&lt;/span&gt;&lt;span class="m"&gt;4&lt;/span&gt;.1.0
+&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;pelican&lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;&lt;span class="m"&gt;4&lt;/span&gt;.11.0
+&lt;span class="w"&gt; &lt;/span&gt;pelican-youtube&lt;span class="o"&gt;==&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;.3.0
+&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;pygments&lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;&lt;span class="m"&gt;2&lt;/span&gt;.18.0
+&lt;span class="w"&gt; &lt;/span&gt;python-dateutil&lt;span class="o"&gt;==&lt;/span&gt;&lt;span class="m"&gt;2&lt;/span&gt;.9.0.post0
+&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;pytz&lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;&lt;span class="m"&gt;2025&lt;/span&gt;.2
+&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;rich&lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;&lt;span class="m"&gt;14&lt;/span&gt;.0.0
+&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;six&lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;.17.0
+&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;sniffio&lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;.3.1
+&lt;span class="w"&gt; &lt;/span&gt;typing-extensions&lt;span class="o"&gt;==&lt;/span&gt;&lt;span class="m"&gt;4&lt;/span&gt;.13.2
+&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;unidecode&lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;.3.8
+&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;watchfiles&lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;.0.5
&lt;/pre&gt;&lt;/div&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/div&gt;
&lt;p&gt;This command installs the requirements and their dependencies, updates the &lt;tt class="docutils literal"&gt;pyproject.toml&lt;/tt&gt; file, and adds a &lt;tt class="docutils literal"&gt;uv.lock&lt;/tt&gt; file to lock the specific dependency versions.&lt;/p&gt;
&lt;p&gt;Since speed is a big focus of UV the warning message interested me. This is a teeny project and the performance of UV isn't relevant, but I figured it was worth learning about. After following the documentation at &lt;a class="reference external" href="https://docs.astral.sh/uv/guides/integration/docker/"&gt;Using uv in Docker&lt;/a&gt; I ended up adding some configuration to my DevContainer &lt;tt class="docutils literal"&gt;Dockerfile&lt;/tt&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;table class="highlighttable"&gt;&lt;tr&gt;&lt;td class="linenos"&gt;&lt;div class="linenodiv"&gt;&lt;pre&gt;&lt;span class="normal"&gt;0&lt;/span&gt;
&lt;span class="normal"&gt;1&lt;/span&gt;
&lt;span class="normal"&gt;2&lt;/span&gt;
&lt;span class="normal"&gt;3&lt;/span&gt;
&lt;span class="normal"&gt;4&lt;/span&gt;
&lt;span class="normal"&gt;5&lt;/span&gt;
&lt;span class="normal"&gt;6&lt;/span&gt;
&lt;span class="normal"&gt;7&lt;/span&gt;
&lt;span class="normal"&gt;8&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;div&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="c"&gt;# Install UV&lt;/span&gt;
&lt;span class="c"&gt;# See: https://docs.astral.sh/uv/guides/integration/docker/#installing-uv&lt;/span&gt;
&lt;span class="c"&gt;# See: https://docs.astral.sh/uv/guides/integration/docker/#caching&lt;/span&gt;
&lt;span class="k"&gt;ADD&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;https://astral.sh/uv/install.sh&lt;span class="w"&gt; &lt;/span&gt;/uv-installer.sh
&lt;span class="k"&gt;ENV&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;UV_INSTALL_DIR&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;/home/&lt;/span&gt;&lt;span class="nv"&gt;$USERNAME&lt;/span&gt;&lt;span class="s2"&gt;/.local/bin/&amp;quot;&lt;/span&gt;
&lt;span class="k"&gt;RUN&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;sh&lt;span class="w"&gt; &lt;/span&gt;/uv-installer.sh&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;rm&lt;span class="w"&gt; &lt;/span&gt;/uv-installer.sh
&lt;span class="k"&gt;ENV&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;PATH&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;/home/&lt;/span&gt;&lt;span class="nv"&gt;$USERNAME&lt;/span&gt;&lt;span class="s2"&gt;/.local/bin/:&lt;/span&gt;&lt;span class="nv"&gt;$PATH&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;
&lt;span class="k"&gt;ENV&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;UV_LINK_MODE&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;copy
&lt;span class="k"&gt;RUN&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;--mount&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nv"&gt;type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;cache,target&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;/home/&lt;/span&gt;&lt;span class="nv"&gt;$USERNAME&lt;/span&gt;&lt;span class="s2"&gt;/.cache/uv&amp;quot;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/div&gt;
&lt;p&gt;This change tells UV to use the less efficient &amp;quot;copy&amp;quot; link mode but it does so on a Docker cache volume to improve speed.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="running-code"&gt;
&lt;h2&gt;Running Code&lt;/h2&gt;
&lt;p&gt;Now that UV is installed and configured I'll try running something:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;table class="highlighttable"&gt;&lt;tr&gt;&lt;td class="linenos"&gt;&lt;div class="linenodiv"&gt;&lt;pre&gt;&lt;span class="normal"&gt; 0&lt;/span&gt;
&lt;span class="normal"&gt; 1&lt;/span&gt;
&lt;span class="normal"&gt; 2&lt;/span&gt;
&lt;span class="normal"&gt; 3&lt;/span&gt;
&lt;span class="normal"&gt; 4&lt;/span&gt;
&lt;span class="normal"&gt; 5&lt;/span&gt;
&lt;span class="normal"&gt; 6&lt;/span&gt;
&lt;span class="normal"&gt; 7&lt;/span&gt;
&lt;span class="normal"&gt; 8&lt;/span&gt;
&lt;span class="normal"&gt; 9&lt;/span&gt;
&lt;span class="normal"&gt;10&lt;/span&gt;
&lt;span class="normal"&gt;11&lt;/span&gt;
&lt;span class="normal"&gt;12&lt;/span&gt;
&lt;span class="normal"&gt;13&lt;/span&gt;
&lt;span class="normal"&gt;14&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;div&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;uv&lt;span class="w"&gt; &lt;/span&gt;run&lt;span class="w"&gt; &lt;/span&gt;pelican
&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="m"&gt;16&lt;/span&gt;:40:54&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;ERROR&lt;span class="w"&gt;    &lt;/span&gt;Cannot&lt;span class="w"&gt; &lt;/span&gt;load&lt;span class="w"&gt; &lt;/span&gt;plugin&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="sb"&gt;`&lt;/span&gt;pelican_youtube&lt;span class="sb"&gt;`&lt;/span&gt;
&lt;span class="w"&gt;                    &lt;/span&gt;Cannot&lt;span class="w"&gt; &lt;/span&gt;import&lt;span class="w"&gt; &lt;/span&gt;plugin&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="sb"&gt;`&lt;/span&gt;pelican_youtube&lt;span class="sb"&gt;`&lt;/span&gt;
&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="m"&gt;16&lt;/span&gt;:40:55&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;ERROR&lt;span class="w"&gt;    &lt;/span&gt;Could&lt;span class="w"&gt; &lt;/span&gt;not&lt;span class="w"&gt; &lt;/span&gt;process&lt;span class="w"&gt; &lt;/span&gt;articles/2017/aoc-2017/05-days-16-20.rst
&lt;span class="w"&gt;                    &lt;/span&gt;/workspaces/chrxs.net/content/articles/2017/aoc-2017/05-days-16-20.rst:159:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;ERROR/3&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;Unknown&lt;span class="w"&gt; &lt;/span&gt;directive&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;type&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;youtube&amp;quot;&lt;/span&gt;

&lt;span class="w"&gt;                    &lt;/span&gt;..&lt;span class="w"&gt; &lt;/span&gt;youtube::&lt;span class="w"&gt; &lt;/span&gt;_cZC67wXUTs

WARNING&lt;span class="w"&gt;  &lt;/span&gt;Unable&lt;span class="w"&gt; &lt;/span&gt;to&lt;span class="w"&gt; &lt;/span&gt;find&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;05-days-16-20.rst#day-16-one-billion-permutations-in-0-535-seconds&amp;#39;&lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;skipping&lt;span class="w"&gt; &lt;/span&gt;url&lt;span class="w"&gt; &lt;/span&gt;replacement.
WARNING&lt;span class="w"&gt;  &lt;/span&gt;Unable&lt;span class="w"&gt; &lt;/span&gt;to&lt;span class="w"&gt; &lt;/span&gt;find&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;05-days-16-20.rst#day-17-collections-deque&amp;#39;&lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;skipping&lt;span class="w"&gt; &lt;/span&gt;url&lt;span class="w"&gt; &lt;/span&gt;replacement.
WARNING&lt;span class="w"&gt;  &lt;/span&gt;Unable&lt;span class="w"&gt; &lt;/span&gt;to&lt;span class="w"&gt; &lt;/span&gt;find&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;05-days-16-20.rst#day-18-negative-numbers-and-str-isdigit&amp;#39;&lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;skipping&lt;span class="w"&gt; &lt;/span&gt;url&lt;span class="w"&gt; &lt;/span&gt;replacement.
WARNING&lt;span class="w"&gt;  &lt;/span&gt;Unable&lt;span class="w"&gt; &lt;/span&gt;to&lt;span class="w"&gt; &lt;/span&gt;find&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;05-days-16-20.rst#day-19-the-internet-is-not-a-big-truck&amp;#39;&lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;skipping&lt;span class="w"&gt; &lt;/span&gt;url&lt;span class="w"&gt; &lt;/span&gt;replacement.
WARNING&lt;span class="w"&gt;  &lt;/span&gt;Other&lt;span class="w"&gt; &lt;/span&gt;resources&lt;span class="w"&gt; &lt;/span&gt;were&lt;span class="w"&gt; &lt;/span&gt;not&lt;span class="w"&gt; &lt;/span&gt;found&lt;span class="w"&gt; &lt;/span&gt;and&lt;span class="w"&gt; &lt;/span&gt;their&lt;span class="w"&gt; &lt;/span&gt;urls&lt;span class="w"&gt; &lt;/span&gt;not&lt;span class="w"&gt; &lt;/span&gt;replaced

Done:&lt;span class="w"&gt; &lt;/span&gt;Processed&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;19&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;articles,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;draft,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;hidden&lt;span class="w"&gt; &lt;/span&gt;articles,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;2&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;pages,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;hidden&lt;span class="w"&gt; &lt;/span&gt;pages&lt;span class="w"&gt; &lt;/span&gt;and&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;draft&lt;span class="w"&gt; &lt;/span&gt;pages&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;in&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;.11&lt;span class="w"&gt; &lt;/span&gt;seconds.
&lt;/pre&gt;&lt;/div&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/div&gt;
&lt;p&gt;Mostly good! It looks like in the 2+ years since I have updated this blog and used Pelican the &lt;tt class="docutils literal"&gt;pelican_youtube&lt;/tt&gt; plugin is not working.&lt;/p&gt;
&lt;p&gt;This turns out to be because &lt;a class="reference external" href="https://docs.getpelican.com/en/latest/changelog.html#id66"&gt;Pelican 4.5&lt;/a&gt; introduced support for &lt;a class="reference external" href="https://docs.getpelican.com/en/latest/plugins.html#how-to-use-plugins"&gt;plugin autodiscovery&lt;/a&gt; so I solved this by removing the &lt;tt class="docutils literal"&gt;PLUGIN_PATHS&lt;/tt&gt; and &lt;tt class="docutils literal"&gt;PLUGINS&lt;/tt&gt; settings from my &lt;tt class="docutils literal"&gt;pelicanconf.py&lt;/tt&gt;.&lt;/p&gt;
&lt;p&gt;With that corrected I'm now able to get a happy build with the latest version of Pelican!&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;table class="highlighttable"&gt;&lt;tr&gt;&lt;td class="linenos"&gt;&lt;div class="linenodiv"&gt;&lt;pre&gt;&lt;span class="normal"&gt;0&lt;/span&gt;
&lt;span class="normal"&gt;1&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;div&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;uv&lt;span class="w"&gt; &lt;/span&gt;run&lt;span class="w"&gt; &lt;/span&gt;pelican
Done:&lt;span class="w"&gt; &lt;/span&gt;Processed&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;20&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;articles,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;draft,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;hidden&lt;span class="w"&gt; &lt;/span&gt;articles,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;2&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;pages,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;hidden&lt;span class="w"&gt; &lt;/span&gt;pages&lt;span class="w"&gt; &lt;/span&gt;and&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;draft&lt;span class="w"&gt; &lt;/span&gt;pages&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;in&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;.07&lt;span class="w"&gt; &lt;/span&gt;seconds.
&lt;/pre&gt;&lt;/div&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/div&gt;
&lt;p&gt;And I can run the listen server also via UV:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;table class="highlighttable"&gt;&lt;tr&gt;&lt;td class="linenos"&gt;&lt;div class="linenodiv"&gt;&lt;pre&gt;&lt;span class="normal"&gt;0&lt;/span&gt;
&lt;span class="normal"&gt;1&lt;/span&gt;
&lt;span class="normal"&gt;2&lt;/span&gt;
&lt;span class="normal"&gt;3&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;div&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;uv&lt;span class="w"&gt; &lt;/span&gt;run&lt;span class="w"&gt; &lt;/span&gt;pelican&lt;span class="w"&gt; &lt;/span&gt;--autoreload&lt;span class="w"&gt; &lt;/span&gt;--listen
---&lt;span class="w"&gt; &lt;/span&gt;AutoReload&lt;span class="w"&gt; &lt;/span&gt;Mode:&lt;span class="w"&gt; &lt;/span&gt;Monitoring&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="sb"&gt;`&lt;/span&gt;content&lt;span class="sb"&gt;`&lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="sb"&gt;`&lt;/span&gt;theme&lt;span class="sb"&gt;`&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;and&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="sb"&gt;`&lt;/span&gt;settings&lt;span class="sb"&gt;`&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;changes.&lt;span class="w"&gt; &lt;/span&gt;---
Serving&lt;span class="w"&gt; &lt;/span&gt;site&lt;span class="w"&gt; &lt;/span&gt;at:&lt;span class="w"&gt; &lt;/span&gt;http://127.0.0.1:8000&lt;span class="w"&gt; &lt;/span&gt;-&lt;span class="w"&gt; &lt;/span&gt;Tap&lt;span class="w"&gt; &lt;/span&gt;CTRL-C&lt;span class="w"&gt; &lt;/span&gt;to&lt;span class="w"&gt; &lt;/span&gt;stop
Done:&lt;span class="w"&gt; &lt;/span&gt;Processed&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;20&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;articles,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;draft,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;hidden&lt;span class="w"&gt; &lt;/span&gt;articles,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;2&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;pages,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;hidden&lt;span class="w"&gt; &lt;/span&gt;pages&lt;span class="w"&gt; &lt;/span&gt;and&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;draft&lt;span class="w"&gt; &lt;/span&gt;pages&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;in&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;.99&lt;span class="w"&gt; &lt;/span&gt;seconds.
&lt;/pre&gt;&lt;/div&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/div&gt;
&lt;/div&gt;
</content><category term="Technology"></category><category term="pip"></category><category term="pipenv"></category><category term="python"></category><category term="uv"></category></entry><entry><title>Ubuntu on a Macbook Pro Mid 2019 (MacBookPro15,1)</title><link href="https://www.chris-wells.net/articles/2022/02/15/ubuntu-macbook-pro-mid-2019" rel="alternate"></link><published>2022-02-15T20:50:00-07:00</published><updated>2022-02-15T20:50:00-07:00</updated><author><name>Christopher Charbonneau Wells</name></author><id>tag:www.chris-wells.net,2022-02-15:/articles/2022/02/15/ubuntu-macbook-pro-mid-2019</id><summary type="html">
&lt;p&gt;&lt;a class="reference external" href="https://www.apple.com/ca/mac/docs/Apple_T2_Security_Chip_Overview.pdf"&gt;Apple's T2 Security Chip&lt;/a&gt; gets itself in the way as much as possible when attempting
to install Linux on a Mid 2019 Macbook Pro and a lot of the available documentation for
Linux-on-the-mac online is pre-T2. After quite a bit of back and forth and eventually
finding the right resources I managed to get Ubuntu installed on my MacBook Pro with
everything working... except the microphone.&lt;/p&gt;
</summary><content type="html">&lt;span class="target" id="top"&gt;&lt;/span&gt;
&lt;p&gt;&lt;a class="reference external" href="https://www.apple.com/ca/mac/docs/Apple_T2_Security_Chip_Overview.pdf"&gt;Apple's T2 Security Chip&lt;/a&gt; gets itself in the way as much as possible when attempting
to install Linux on a Mid 2019 Macbook Pro and a lot of the available documentation for
Linux-on-the-mac online is pre-T2. After quite a bit of back and forth and eventually
finding the right resources I managed to get Ubuntu installed on my MacBook Pro with
everything working... except the microphone.&lt;/p&gt;

&lt;div class="section" id="initial-success-and-failure"&gt;
&lt;h2&gt;Initial Success and Failure&lt;/h2&gt;
&lt;p&gt;Getting started with this process was a bit of a minefield. The first resource I came
across was &lt;a class="reference external" href="https://www.lifewire.com/dual-boot-linux-and-mac-os-4125733"&gt;How to Install and Dual-Boot Linux and macOS&lt;/a&gt;, an extremely detailed and
useful documentation piece with one minor caveat (that I didn't take heed of initially):&lt;/p&gt;
&lt;blockquote&gt;
As of right now, you cannot install Linux on the internal SSD of a newer MacBook Pro
or Mac Pro (2018 or later). You can still install on an external drive, however.&lt;/blockquote&gt;
&lt;p&gt;Despite the warning I did manage to &lt;em&gt;eventually&lt;/em&gt; cobble together a working base Ubuntu
install using Ubuntu 20.04, a bootable USB-C drive (notably: &lt;em&gt;not&lt;/em&gt; a USB-A adapter),
and &lt;a class="reference external" href="http://www.rodsbooks.com/refind/"&gt;rEFInd&lt;/a&gt;. In the end I had a functional Ubuntu installation on the same internal
SSD as my macOS install &lt;strong&gt;but&lt;/strong&gt; none of the peripherals (keyboard, trackpad, speakers,
touchbar, wireless, etc.) worked and Ubuntu was unable to find drivers for them. This
failure/success finally led me down the proper path of seeking out a &lt;em&gt;T2-supported&lt;/em&gt;
version of Linux.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="getting-it-done-right-on-a-t2-macbook"&gt;
&lt;h2&gt;Getting it Done Right (on a T2 MacBook)&lt;/h2&gt;
&lt;p&gt;This process is mostly a mashing of various guides and notes about specific conditions
for the MacBook Pro Mid 2019 (MacBookPro15,1). I've cited sources throughout the text
am mostly just interested in organizing this proces all in one place.&lt;/p&gt;
&lt;div class="section" id="create-bootable-media"&gt;
&lt;h3&gt;1. Create Bootable Media&lt;/h3&gt;
&lt;p&gt;A USB-C device may be necessary for this process. Using a regular USB-A device through
an adapter might not be supported for bootable media.&lt;/p&gt;
&lt;ol class="arabic simple"&gt;
&lt;li&gt;Download the zip files from the &lt;a class="reference external" href="https://github.com/marcosfad/mbp-ubuntu/releases"&gt;latest release of mbp-ubuntu&lt;/a&gt; (the &lt;tt class="docutils literal"&gt;livecd.zip&lt;/tt&gt;
file and any other &lt;tt class="docutils literal"&gt;livecd.zX&lt;/tt&gt; files).&lt;/li&gt;
&lt;li&gt;Join the zip files together: &lt;tt class="docutils literal"&gt;cat livecd.z* &amp;gt; livecd_full.zip&lt;/tt&gt;.&lt;/li&gt;
&lt;li&gt;And unzip them: &lt;tt class="docutils literal"&gt;unzip livecd_full.zip&lt;/tt&gt;.&lt;/li&gt;
&lt;li&gt;Create a bootable device from the resulting ISO image (e.g., with a program like
&lt;a class="reference external" href="https://www.balena.io/etcher/"&gt;Etcher&lt;/a&gt;).&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;
&lt;div class="section" id="prepare-your-hard-drive"&gt;
&lt;h3&gt;2. Prepare Your Hard Drive&lt;/h3&gt;
&lt;ol class="arabic simple"&gt;
&lt;li&gt;Open the Disk Utility application (located in &lt;tt class="docutils literal"&gt;/Applications/Utilities&lt;/tt&gt;).&lt;/li&gt;
&lt;li&gt;Select the internal Apple SSD entry in the left pane.&lt;/li&gt;
&lt;li&gt;Click the &lt;strong&gt;Partition&lt;/strong&gt; button.&lt;/li&gt;
&lt;li&gt;Create a new partition (not volume) of at least 32GB with &amp;quot;MS-DOS (Fat)&amp;quot; as the
format. This is where Ubuntu will be installed so choose a size appropriate for what
you want to do with the system. See &lt;a class="reference external" href="https://support.apple.com/guide/disk-utility/partition-a-physical-disk-dskutl14027/mac"&gt;Partition a physical disk in Disk Utility on Mac&lt;/a&gt;
for additional guidance on this process.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;strong&gt;If the option to add a partition is not available&lt;/strong&gt; it may be because additional disk
space is being used by snapshots. Follow the steps below to free up this space and try
to create the new partition again.&lt;/p&gt;
&lt;ol class="arabic simple"&gt;
&lt;li&gt;Open the Disk Utility application (located in &lt;tt class="docutils literal"&gt;/Applications/Utilities&lt;/tt&gt;).&lt;/li&gt;
&lt;li&gt;Select the primary volume group in the left pane (probably named &amp;quot;Macintosh HD&amp;quot;).&lt;/li&gt;
&lt;li&gt;Select View &amp;gt; Show APFS Snapshots from the menu bar.&lt;/li&gt;
&lt;li&gt;Select all the snapshots and click the delete icon (-).&lt;/li&gt;
&lt;li&gt;Return to the partition options (see above) and try again.&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;
&lt;div class="section" id="boot-the-bootable-media"&gt;
&lt;h3&gt;3. Boot the Bootable Media&lt;/h3&gt;
&lt;ol class="arabic simple"&gt;
&lt;li&gt;Boot in to macOS Recovery mode and open the Startup Security Utility (see
&lt;a class="reference external" href="https://support.apple.com/en-us/HT208198"&gt;About Startup Security Utility on a Mac with the Apple T2 Security Chip&lt;/a&gt;).&lt;/li&gt;
&lt;li&gt;Select &amp;quot;No Security&amp;quot; in Secure Boot.&lt;/li&gt;
&lt;li&gt;Select &amp;quot;Allow booting from external and removable media&amp;quot; in Allowed Boot Modes.&lt;/li&gt;
&lt;li&gt;Close the Startup Security Utility.&lt;/li&gt;
&lt;li&gt;Insert the bootable media created previously directly in to a USB-C port.&lt;/li&gt;
&lt;li&gt;Restart in to the Startup Disk Manager (see &lt;a class="reference external" href="https://support.apple.com/guide/mac-help/change-your-mac-startup-disk-mchlp1034/mac"&gt;Change your Mac startup disk&lt;/a&gt;).&lt;/li&gt;
&lt;li&gt;Select and continue from the bootable media labeled &amp;quot;EFI Boot&amp;quot;. There may be more
than one option with the same label -- try the last first and if it does not work
just repeat the previous step and try another one.&lt;/li&gt;
&lt;li&gt;When prompted select the Ubuntu Live environment and use the kernel with suffix
&amp;quot;hwe-bigsur&amp;quot;.&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;
&lt;div class="section" id="install-ubuntu"&gt;
&lt;h3&gt;4. Install Ubuntu!&lt;/h3&gt;
&lt;ol class="arabic simple"&gt;
&lt;li&gt;In the Ubuntu Live environment click the &amp;quot;Install Ubuntu&amp;quot; application on the desktop.&lt;/li&gt;
&lt;li&gt;Set install options as desired.&lt;/li&gt;
&lt;li&gt;On the &amp;quot;Installation Type&amp;quot; step choose &amp;quot;Something else&amp;quot; and click Continue.&lt;/li&gt;
&lt;li&gt;Select the partition created previously (the current type should be &amp;quot;fat32&amp;quot; and the
device should be something like &lt;tt class="docutils literal"&gt;/dev/nvme0n1p3&lt;/tt&gt;). Be very careful to select the
correct partition here as the next steps will erase it for the Ubuntu install.&lt;/li&gt;
&lt;li&gt;Click delete partition (-).&lt;/li&gt;
&lt;li&gt;Select the &amp;quot;free space&amp;quot; entry created by the partition removal.&lt;/li&gt;
&lt;li&gt;Click add partition (+), set the size to 1024MB, the type to &amp;quot;ext4&amp;quot; and the mount
point to /boot.&lt;/li&gt;
&lt;li&gt;Select the &amp;quot;free space&amp;quot; entry again.&lt;/li&gt;
&lt;li&gt;Click add partition (+), set the size to all the remaining space, the type to &amp;quot;ext4&amp;quot;
and the mount point to root (/).&lt;/li&gt;
&lt;li&gt;Select the /boot partition for &amp;quot;Device for bootloader installation&amp;quot;.&lt;/li&gt;
&lt;li&gt;Continue the installation, shutting down the computer and removing the bootable
media after completion.&lt;/li&gt;
&lt;li&gt;Start in to the Startup Disk Manager (see &lt;a class="reference external" href="https://support.apple.com/guide/mac-help/change-your-mac-startup-disk-mchlp1034/mac"&gt;Change your Mac startup disk&lt;/a&gt;) again.&lt;/li&gt;
&lt;li&gt;Select the new EFI Boot option.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;You should now be booted in to Ubuntu with a mostly functional system!&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="getting-wifi-working"&gt;
&lt;h3&gt;5. Getting Wifi Working&lt;/h3&gt;
&lt;ol class="arabic simple"&gt;
&lt;li&gt;Follow the &lt;a class="reference external" href="https://github.com/t2linux/T2-Ubuntu-Kernel#pre-installation-steps"&gt;Pre installation steps&lt;/a&gt; and &lt;a class="reference external" href="https://github.com/t2linux/T2-Ubuntu-Kernel#the-easy-way"&gt;Installation &amp;gt; The easy way&lt;/a&gt; instructions
from the &lt;a class="reference external" href="https://github.com/t2linux/T2-Ubuntu-Kernel"&gt;T2 Ubuntu Kernel&lt;/a&gt; repo.&lt;/li&gt;
&lt;li&gt;Follow the &lt;a class="reference external" href="https://wiki.t2linux.org/guides/wifi/#setting-up"&gt;Setting up&lt;/a&gt; steps from the &lt;a class="reference external" href="https://wiki.t2linux.org/"&gt;t2linux wiki&lt;/a&gt;.&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;
&lt;div class="section" id="celebrate"&gt;
&lt;h3&gt;6. Celebrate!&lt;/h3&gt;
&lt;p&gt;That's it! Everything (except the microphone...) should now be working in Ubuntu -- even
the touchbar! The &lt;a class="reference external" href="https://wiki.t2linux.org/"&gt;t2linux wiki&lt;/a&gt; also has an &lt;a class="reference external" href="https://wiki.t2linux.org/guides/audio-config/"&gt;audio guide&lt;/a&gt; with some options that may
get the microphone working but I was not able to get it functional from information in
that guide (or anywhere else). I'll update this space if I ever do have success!&lt;/p&gt;
&lt;p&gt;Pretty much all of the information compiled for this post stems in one way or another
from the wonderful &lt;a class="reference external" href="https://github.com/marcosfad/mbp-ubuntu"&gt;marcosfad/mbp-ubuntu&lt;/a&gt; repo. Pop in there and contribute to the
maintainers of that repo if this guide was helpful in any way.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
</content><category term="Technology"></category><category term="apple"></category><category term="linux"></category></entry><entry><title>Back to PHP</title><link href="https://www.chris-wells.net/articles/2018/08/11/back-to-php" rel="alternate"></link><published>2018-08-11T15:00:00-07:00</published><updated>2018-08-11T15:00:00-07:00</updated><author><name>Christopher Charbonneau Wells</name></author><id>tag:www.chris-wells.net,2018-08-11:/articles/2018/08/11/back-to-php</id><summary type="html">
&lt;p&gt;A few months back I started a new job with a much greater focus on development
work over IT systems management. Unfortunately this has led to a pretty big
drop off in amount of time spent on personal side projects, but happily my new
employer is fully supportive of open source and I am able to release much of
what I work on to the wider community.&lt;/p&gt;
</summary><content type="html">&lt;span class="target" id="top"&gt;&lt;/span&gt;
&lt;p&gt;A few months back I started a new job with a much greater focus on development
work over IT systems management. Unfortunately this has led to a pretty big
drop off in amount of time spent on personal side projects, but happily my new
employer is fully supportive of open source and I am able to release much of
what I work on to the wider community.&lt;/p&gt;

&lt;p&gt;I have recently pushed three projects out:&lt;/p&gt;
&lt;ol class="arabic simple"&gt;
&lt;li&gt;&lt;a class="reference external" href="https://www.drupal.org/project/contextual_filter_range_validator"&gt;Views Contextual Range Validator&lt;/a&gt;: A &lt;em&gt;very&lt;/em&gt; simple Drupal 8 module adding
a Views contextual filter for range validation, descriptively named.&lt;/li&gt;
&lt;li&gt;&lt;a class="reference external" href="https://www.drupal.org/project/pbs_passport"&gt;PBS Passport&lt;/a&gt;: An OAuth2-based authorization system for Drupal 7 and
PBS.org accounts.&lt;/li&gt;
&lt;li&gt;&lt;a class="reference external" href="https://www.drupal.org/project/lightbox_campaigns"&gt;Lightbox Campaigns&lt;/a&gt;: A Drupal 7 and 8 module for running custom, targeted
&amp;quot;campaigns&amp;quot; using full screen &amp;quot;lightbox&amp;quot; displays.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;It has been interesting returning to the world of PHP after focusing so heavily
on free time personal projects in Python. I am saddened to find myself away
from the (much) less verbose syntax of Python, but happy to be back in the
language that I grew up hacking on.&lt;/p&gt;
&lt;p&gt;This has also given me the opportunity to work more with custom module
development in Drupal 8 and I'm excited to do more of that work in the future
as the community behind Drupal has really improved the APIs. The Lightbox
Campaigns module was a particularly interesting adventure because I initially
developed in Drupal 8 and it seemed almost too easy to get the initial
groundwork in place. Porting to Drupal 7 proved much more difficult because
the same functionality had to rely on multiple other dependencies and hooks
that seemed determine to enforce a particular hierarchy for entity management.&lt;/p&gt;
&lt;p&gt;There are more projects in the pipe and planned down the road. I will continue
to add things to my &lt;a class="reference external" href="https://www.chris-wells.net/projects"&gt;Projects&lt;/a&gt; page and Drupal stuff can always be found on my
&lt;a class="reference external" href="https://www.drupal.org/u/wells"&gt;Drupal.org profile&lt;/a&gt;.&lt;/p&gt;
</content><category term="Asides"></category><category term="drupal"></category><category term="php"></category></entry><entry><title>Pip, Pis, Pandas and Wheels</title><link href="https://www.chris-wells.net/articles/2018/05/07/pip-pis-pandas-wheels" rel="alternate"></link><published>2018-05-07T20:30:00-07:00</published><updated>2018-05-07T20:30:00-07:00</updated><author><name>Christopher Charbonneau Wells</name></author><id>tag:www.chris-wells.net,2018-05-07:/articles/2018/05/07/pip-pis-pandas-wheels</id><summary type="html">
&lt;p&gt;A user attempting to install &lt;a class="reference external" href="http://baby-buddy.net/"&gt;Baby Buddy&lt;/a&gt; submitted an &lt;a class="reference external" href="https://github.com/cdubz/babybuddy/issues/41"&gt;interesting issue&lt;/a&gt;
with the following error during the &lt;tt class="docutils literal"&gt;pipenv install&lt;/tt&gt; process:&lt;/p&gt;
&lt;pre class="code literal-block"&gt;
THESE PACKAGES DO NOT MATCH THE HASHES FROM Pipfile.lock!. If you have updated the package versions, please update the hashes. Otherwise, examine the package contents carefully; someone may have tampered with them.
    docopt==0.6.2 from https://www.piwheels.org/simple/docopt/docopt-0.6.2-py2.py3-none-any.whl#sha256=0340515c74203895f92f87702896e45424bf51dc71bf15b4748450f50be04346 (from -r /tmp/pipenv-vf5_eub9-requirements/pipenv-k7_dvsro-requirement.txt (line 1)):
        Expected sha256 49b3a825280bd66b3aa83585ef59c4a8c82f2c8a522dbe754a8bc8d08c85c491
             Got        0340515c74203895f92f87702896e45424bf51dc71bf15b4748450f50be04346
&lt;/pre&gt;
&lt;p&gt;Hash checking and &lt;tt class="docutils literal"&gt;Pipfile.lock&lt;/tt&gt; are a part of the &lt;a class="reference external" href="https://docs.pipenv.org/"&gt;pipenv&lt;/a&gt; toolchain and
meant to verify the integrity of packages being installed. Committing the lock
file is &lt;a class="reference external" href="https://github.com/pypa/pipenv/issues/598"&gt;recommended practice&lt;/a&gt; and generally something I have not had many
problems with. There are some old tickets on GitHub reporting issues with this
hashing between operating systems, but the latest versions of pipenv supposedly
do not have these problems.&lt;/p&gt;
&lt;p&gt;Why is this user getting a hash match error? I had a Pi lying around, so I
decided to try replicating the issue. Many hours later, I got Baby Buddy up and
running on my (second) Pi and learned a lot about the Python packaging process
and how it can go wrong on ARM devices.&lt;/p&gt;
</summary><content type="html">&lt;span class="target" id="top"&gt;&lt;/span&gt;
&lt;p&gt;A user attempting to install &lt;a class="reference external" href="http://baby-buddy.net/"&gt;Baby Buddy&lt;/a&gt; submitted an &lt;a class="reference external" href="https://github.com/cdubz/babybuddy/issues/41"&gt;interesting issue&lt;/a&gt;
with the following error during the &lt;tt class="docutils literal"&gt;pipenv install&lt;/tt&gt; process:&lt;/p&gt;
&lt;pre class="code literal-block"&gt;
THESE PACKAGES DO NOT MATCH THE HASHES FROM Pipfile.lock!. If you have updated the package versions, please update the hashes. Otherwise, examine the package contents carefully; someone may have tampered with them.
    docopt==0.6.2 from https://www.piwheels.org/simple/docopt/docopt-0.6.2-py2.py3-none-any.whl#sha256=0340515c74203895f92f87702896e45424bf51dc71bf15b4748450f50be04346 (from -r /tmp/pipenv-vf5_eub9-requirements/pipenv-k7_dvsro-requirement.txt (line 1)):
        Expected sha256 49b3a825280bd66b3aa83585ef59c4a8c82f2c8a522dbe754a8bc8d08c85c491
             Got        0340515c74203895f92f87702896e45424bf51dc71bf15b4748450f50be04346
&lt;/pre&gt;
&lt;p&gt;Hash checking and &lt;tt class="docutils literal"&gt;Pipfile.lock&lt;/tt&gt; are a part of the &lt;a class="reference external" href="https://docs.pipenv.org/"&gt;pipenv&lt;/a&gt; toolchain and
meant to verify the integrity of packages being installed. Committing the lock
file is &lt;a class="reference external" href="https://github.com/pypa/pipenv/issues/598"&gt;recommended practice&lt;/a&gt; and generally something I have not had many
problems with. There are some old tickets on GitHub reporting issues with this
hashing between operating systems, but the latest versions of pipenv supposedly
do not have these problems.&lt;/p&gt;
&lt;p&gt;Why is this user getting a hash match error? I had a Pi lying around, so I
decided to try replicating the issue. Many hours later, I got Baby Buddy up and
running on my (second) Pi and learned a lot about the Python packaging process
and how it can go wrong on ARM devices.&lt;/p&gt;

&lt;div class="section" id="kernel-panics-and-segfaults"&gt;
&lt;h2&gt;Kernel Panics and Segfaults&lt;/h2&gt;
&lt;p&gt;In my initial tests, I couldn't even seem to get my Pi to install pipenv
itself, despite the report from the user of issues &lt;em&gt;after&lt;/em&gt; that point. The test
Pi's syslog filled with errors similar to:&lt;/p&gt;
&lt;pre class="code literal-block"&gt;
May  5 23:05:31 raspberrypi kernel: [18801.650989] [&amp;lt;8010ffd8&amp;gt;] (unwind_backtrace) from [&amp;lt;8010c240&amp;gt;] (show_stack+0x20/0x24)
May  5 23:05:31 raspberrypi kernel: [18801.653797] [&amp;lt;8010c240&amp;gt;] (show_stack) from [&amp;lt;807840a4&amp;gt;] (dump_stack+0xd4/0x118)
May  5 23:05:31 raspberrypi kernel: [18801.655292] [&amp;lt;807840a4&amp;gt;] (dump_stack) from [&amp;lt;80254a20&amp;gt;] (print_bad_pte+0x150/0x1b4)
May  5 23:05:31 raspberrypi kernel: [18801.658037] [&amp;lt;80254a20&amp;gt;] (print_bad_pte) from [&amp;lt;80256d6c&amp;gt;] (unmap_page_range+0x57c/0x668)
May  5 23:05:31 raspberrypi kernel: [18801.660979] [&amp;lt;80256d6c&amp;gt;] (unmap_page_range) from [&amp;lt;80256ea4&amp;gt;] (unmap_single_vma+0x4c/0x54)
May  5 23:05:31 raspberrypi kernel: [18801.664093] [&amp;lt;80256ea4&amp;gt;] (unmap_single_vma) from [&amp;lt;80257150&amp;gt;] (unmap_vmas+0x64/0x78)
May  5 23:05:31 raspberrypi kernel: [18801.667274] [&amp;lt;80257150&amp;gt;] (unmap_vmas) from [&amp;lt;8025d468&amp;gt;] (exit_mmap+0xac/0x154)
May  5 23:05:31 raspberrypi kernel: [18801.668941] [&amp;lt;8025d468&amp;gt;] (exit_mmap) from [&amp;lt;8011ac74&amp;gt;] (mmput+0x58/0x108)
May  5 23:05:31 raspberrypi kernel: [18801.670592] [&amp;lt;8011ac74&amp;gt;] (mmput) from [&amp;lt;80121d84&amp;gt;] (do_exit+0x35c/0xb9c)
May  5 23:05:31 raspberrypi kernel: [18801.672289] [&amp;lt;80121d84&amp;gt;] (do_exit) from [&amp;lt;8012265c&amp;gt;] (do_group_exit+0x4c/0xe4)
May  5 23:05:31 raspberrypi kernel: [18801.673960] [&amp;lt;8012265c&amp;gt;] (do_group_exit) from [&amp;lt;8012da40&amp;gt;] (get_signal+0x36c/0x6bc)
May  5 23:05:31 raspberrypi kernel: [18801.677190] [&amp;lt;8012da40&amp;gt;] (get_signal) from [&amp;lt;8010b2f4&amp;gt;] (do_signal+0xc4/0x3e4)
May  5 23:05:31 raspberrypi kernel: [18801.678882] [&amp;lt;8010b2f4&amp;gt;] (do_signal) from [&amp;lt;8010b7fc&amp;gt;] (do_work_pending+0xb8/0xd0)
May  5 23:05:31 raspberrypi kernel: [18801.682219] [&amp;lt;8010b7fc&amp;gt;] (do_work_pending) from [&amp;lt;80108094&amp;gt;] (slow_work_pending+0xc/0x20)
May  5 23:05:31 raspberrypi kernel: [18801.685688] BUG: Bad page map in process pip  pte:1739f75f pmd:31beb835
May  5 23:05:31 raspberrypi kernel: [18801.687473] page:ba6e525c count:1 mapcount:-1 mapping:b1ab06c9 index:0x3f1
May  5 23:05:31 raspberrypi kernel: [18801.689239] flags: 0x68(uptodate|lru|active)
May  5 23:05:31 raspberrypi kernel: [18801.690919] raw: 00000068 b1ab06c9 000003f1 fffffffe 00000001 ba6e5228 ba7194c4 00000000
May  5 23:05:31 raspberrypi kernel: [18801.694201] raw: 00000000
May  5 23:05:31 raspberrypi kernel: [18801.695918] page dumped because: bad pte
May  5 23:05:31 raspberrypi kernel: [18801.697552] addr:003f1000 vm_flags:00100073 anon_vma:b1ab06c8 mapping:  (null) index:3f1
May  5 23:05:31 raspberrypi kernel: [18801.700631] file:  (null) fault:  (null) mmap:  (null) readpage:  (null)
May  5 23:05:31 raspberrypi kernel: [18801.702198] CPU: 0 PID: 4148 Comm: pip Tainted: G    B D  C      4.14.34-v7+ #1110
May  5 23:05:31 raspberrypi kernel: [18801.705262] Hardware name: BCM2835
&lt;/pre&gt;
&lt;p&gt;and&lt;/p&gt;
&lt;pre class="code literal-block"&gt;
May 5 23:05:30 raspberrypi kernel: [18800.789345] Internal error: Oops: 5 [#1] SMP ARM
&lt;/pre&gt;
&lt;p&gt;After trying some random things, I ran filesystem and memory tests. Everything
checked out, but the Pi was consistently failing under load. I also eventually
did some testing on the power supply and other aspects of the Pi, but that will
have to wait for another post. I had a second Pi lying around, so I grabbed
that and managed to reproduce an error similar to the reported one.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="hash-match-failures"&gt;
&lt;h2&gt;Hash Match Failures&lt;/h2&gt;
&lt;p&gt;Similar to the initial user report, the &lt;tt class="docutils literal"&gt;pipenv install&lt;/tt&gt; command failed with
a hash match issue. But unlike the other affected user, my failing has was
for a different package.&lt;/p&gt;
&lt;p&gt;I eventually stumbled on this very interesting blog post from Raspberry Pi
Community Manager &lt;a class="reference external" href="http://bennuttall.com/"&gt;Ben Nuttall&lt;/a&gt;:
&lt;a class="reference external" href="http://bennuttall.com/piwheels-building-a-faster-python-package-repository-for-raspberry-pi-users/"&gt;building a faster Python package repository for Raspberry Pi users&lt;/a&gt;. In a
nutshell, Ben explains that &lt;a class="reference external" href="https://pypi.org/"&gt;PyPI&lt;/a&gt; uses &lt;a class="reference external" href="https://pythonwheels.com/"&gt;&amp;quot;wheels&amp;quot;&lt;/a&gt; to prebuild Python
packages for installations on specific architectures, but ARM (Raspberry Pi's
architecture) is not well represented by maintainers who build these wheels.
Ben improved this situation greatly by developing a service to build
&lt;em&gt;and distribute&lt;/em&gt; ARM wheels: &lt;a class="reference external" href="https://www.piwheels.org/"&gt;piwheels&lt;/a&gt;. The piwheels service is now a part of
the default &lt;a class="reference external" href="https://www.raspbian.org/"&gt;Raspbian&lt;/a&gt; distribution, making it much faster to
&lt;tt class="docutils literal"&gt;pip install &lt;span class="pre"&gt;[...]&lt;/span&gt;&lt;/tt&gt; Python packages out of the box!&lt;/p&gt;
&lt;p&gt;What does this have to do hash match failures? Pipenv uses a special file,
&lt;tt class="docutils literal"&gt;Pipfile.lock&lt;/tt&gt; to store hash information for all dependencies in a project
and committing this file is &lt;a class="reference external" href="https://github.com/pypa/pipenv/issues/598"&gt;generally recommended&lt;/a&gt; as a security measure. The
problem is that the hashes are generated from a specific source (most often
PyPI) and for the large majority of instances the same source will likely be
used by other users installing a project. Unfortunately -&lt;/p&gt;
&lt;blockquote&gt;
A package from PyPI, locked on an AMD64 system will have a
&lt;strong&gt;different hash&lt;/strong&gt; than one from piwheels, locked on an ARM system like
the Pi.&lt;/blockquote&gt;
&lt;p&gt;I develop Baby Buddy on my AMD64 desktop, so the lock file included in the
repository has AMD64 hashes generated from PyPI. Using this same lock file on
a Raspberry Pi with Rasbian results in the
&lt;tt class="docutils literal"&gt;THESE PACKAGES DO NOT MATCH THE HASHES FROM Pipfile.lock!&lt;/tt&gt; error from
pipenv.&lt;/p&gt;
&lt;p&gt;How can this be resolved? There are only really two options I could come up
with:&lt;/p&gt;
&lt;ol class="arabic simple"&gt;
&lt;li&gt;Execute &lt;tt class="docutils literal"&gt;pipenv lock&lt;/tt&gt; before &lt;tt class="docutils literal"&gt;pipenv install&lt;/tt&gt; on ARM devices.&lt;/li&gt;
&lt;li&gt;Include the &lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;--skip-lock&lt;/span&gt;&lt;/tt&gt; flag when the &lt;tt class="docutils literal"&gt;pipenv install&lt;/tt&gt; command.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Neither option is particularly ideal, as they both compromise the security
aspect of the lock file (and locking can take a &lt;em&gt;long&lt;/em&gt; time on a Pi). But for
the purposes of further troubleshooting, I chose to use &lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;--skip-lock&lt;/span&gt;&lt;/tt&gt; to get
past this issue. And it worked!&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="pip-and-pandas"&gt;
&lt;h2&gt;Pip and Pandas&lt;/h2&gt;
&lt;p&gt;Happy to have sorted out the lock issue, I executed
&lt;tt class="docutils literal"&gt;pipenv install &lt;span class="pre"&gt;--three&lt;/span&gt; &lt;span class="pre"&gt;--dev&lt;/span&gt; &lt;span class="pre"&gt;--skip-lock&lt;/span&gt;&lt;/tt&gt; and waited for pipenv to do its
thing. Sadly, the install eventually failed with the error:&lt;/p&gt;
&lt;pre class="code literal-block"&gt;
Double requirement given: numpy==1.12.1 [...] (already in numpy==1.9.3 [...], name='numpy')
&lt;/pre&gt;
&lt;p&gt;&lt;a class="reference external" href="http://www.numpy.org/"&gt;Numpy&lt;/a&gt; is a requirement of &lt;a class="reference external" href="https://pandas.pydata.org/"&gt;Pandas&lt;/a&gt;, which Baby Buddy uses for one of its
graphs. I have actually thought about refactoring it out in the past as they
are both pretty heavy requirements and hardly used, but I just haven't found
the time.&lt;/p&gt;
&lt;p&gt;Anyway, Baby Buddy pins Pandas below version 0.22.0 because a while back I had
issue with the latest version of Pandas failing to build for Python 3.4. As it
turns out, there has been a fairly recently issue with new versions of pip
(10.x) and Pandas: &lt;a class="reference external" href="https://github.com/pandas-dev/pandas/issues/20775"&gt;Pandas installation problems with pip version 10&lt;/a&gt;. Two
workaround options are provided:&lt;/p&gt;
&lt;ol class="arabic simple"&gt;
&lt;li&gt;use an older version of pip (9.x), or&lt;/li&gt;
&lt;li&gt;use pip install pandas &lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;--no-build-isolation&lt;/span&gt;&lt;/tt&gt;.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Before attempting these workarounds, I tried a new install of Pandas without
the version pinning. Surprisingly, this worked -- Pandas and all of it's
requirements installed from wheels on piwheels.&lt;/p&gt;
&lt;p&gt;For Baby Buddy, perhaps the logical thing to do to workaround this issue will
be to drop support for Python 3.4. This would allow me to remove the Pandas
pinning and not have to provide special instructions for ARM-based
installations. Before I could think too deeply about that, I had another error
on my hands...&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="psycopg2-and-pyscopg2-binary"&gt;
&lt;h2&gt;Psycopg2 and Pyscopg2-binary&lt;/h2&gt;
&lt;p&gt;&lt;a class="reference external" href="http://initd.org/psycopg/"&gt;Psycopg2&lt;/a&gt; is a Python package that provides a PostgreSQL adapter. It is an
important option to provide to potential Baby Buddy users and also required for
the project's &lt;a class="reference external" href="https://www.heroku.com/"&gt;Heroku&lt;/a&gt; deployment method. Building psycopg2 requires some
external development libraries and other tools that are not always present in
deployment environments. In order to avoid extra dependencies, Baby Buddy uses
the psycopg2-binary package. The reasoning for the two packages in explained in
the &lt;a class="reference external" href="http://initd.org/psycopg/articles/2018/02/08/psycopg-274-released/"&gt;Psycopg 2.7.4 release post&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Piwheels does have a &lt;a class="reference external" href="https://www.piwheels.org/simple/psycopg2-binary/"&gt;psycopg2-binary wheel page&lt;/a&gt;, but it currently does not
list any wheels. Unable to find any wheels, pip attempts to download and build
the package from source, which fails with the error:&lt;/p&gt;
&lt;pre class="code literal-block"&gt;
    Error: pg_config executable not found.

pg_config is required to build psycopg2 from source.  Please add the directory
containing pg_config to the $PATH or specify the full executable path with the
option:

    python setup.py build_ext --pg-config /path/to/pg_config build ...

or with the pg_config option in 'setup.cfg'.

If you prefer to avoid building psycopg2 from source, please install the PyPI
'psycopg2-binary' package instead.

For further information please check the 'doc/src/install.rst' file (also at
&amp;lt;http://initd.org/psycopg/docs/install.html&amp;gt;).
&lt;/pre&gt;
&lt;p&gt;Piwheels does provide &lt;a class="reference external" href="https://www.piwheels.org/simple/psycopg2/"&gt;wheels for psycopg2&lt;/a&gt;, &lt;em&gt;but&lt;/em&gt; not for the latest version
(2.7.4 as of this writing) so &lt;tt class="docutils literal"&gt;pipenv install psycopg2&lt;/tt&gt; fails with the exact
same error message. Ultimately, this issue can be overcome by pinning psycopg2
to &amp;lt;2.4.7, allowing pip to find and (quickly!) install the wheel from piwheels.
Not a long term solution, but it moves things forward a bit here.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="importerror-no-module-named-image"&gt;
&lt;h2&gt;ImportError: No module named 'Image'&lt;/h2&gt;
&lt;p&gt;After working around the issue with Pandas and Psycopg2, the &lt;tt class="docutils literal"&gt;pipenv install&lt;/tt&gt;
process works. However, further down the line in the manual install process,
the &lt;tt class="docutils literal"&gt;gulp collectstatic&lt;/tt&gt; command spits out this error:&lt;/p&gt;
&lt;pre class="code literal-block"&gt;
Traceback (most recent call last):
  File &amp;quot;/home/pi/.local/share/virtualenvs/public-BuvyXxxq/lib/python3.5/site-packages/easy_thumbnails/utils.py&amp;quot;, line 11, in &amp;lt;module&amp;gt;
    from PIL import Image
  File &amp;quot;/home/pi/.local/share/virtualenvs/public-BuvyXxxq/lib/python3.5/site-packages/PIL/Image.py&amp;quot;, line 60, in &amp;lt;module&amp;gt;
    from . import _imaging as core
ImportError: libopenjp2.so.7: cannot open shared object file: No such file or directory

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  [...]
  File &amp;quot;/home/pi/.local/share/virtualenvs/public-BuvyXxxq/lib/python3.5/site-packages/easy_thumbnails/utils.py&amp;quot;, line 13, in &amp;lt;module&amp;gt;
    import Image
ImportError: No module named 'Image'
&lt;/pre&gt;
&lt;p&gt;&lt;em&gt;Blargh&lt;/em&gt;. Luckily, this message is pretty verbose and has an easy fix. The
&lt;tt class="docutils literal"&gt;libopenjp2.so.7&lt;/tt&gt; library is not included in Raspbian and (apparently?)
included in other distributions, as I have never seen this error before. This
is overcome with one additional command during the install process:&lt;/p&gt;
&lt;pre class="code literal-block"&gt;
sudo apt-get install libopenjp2-7-dev
&lt;/pre&gt;
&lt;p&gt;After installing this library, everything else goes smoothly and Baby Buddy
runs on the Pi! &lt;strong&gt;Hooray!&lt;/strong&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="conclusions"&gt;
&lt;h2&gt;Conclusions&lt;/h2&gt;
&lt;p&gt;Packaging is hard. I am often concerned about the fragility of the ecosystem
around web development (or my little corner of it, at least). Baby Buddy is a
fairly simple web app - it uses &lt;a class="reference external" href="https://www.djangoproject.com/"&gt;Django&lt;/a&gt; on the backend and &lt;a class="reference external" href="https://getbootstrap.com/"&gt;Bootstrap&lt;/a&gt; on
the frontend. It is not an SPA. It is not using &lt;a class="reference external" href="https://reactjs.org/"&gt;React&lt;/a&gt; or &lt;a class="reference external" href="https://vuejs.org/"&gt;Vue&lt;/a&gt; or any other
complex libraries. But it still relies on a lot of external moving parts and a
somewhat complicated build process. &lt;a class="reference external" href="https://www.heroku.com/"&gt;Heroku&lt;/a&gt; and &lt;a class="reference external" href="https://www.docker.com/"&gt;Docker&lt;/a&gt; help to ease this
pain, but also add time and complexities of their own.&lt;/p&gt;
&lt;p&gt;But I still love it. And I love the community that surrounds it. It never
occurred to me to run Baby Buddy on a Pi. But one user tried it and the
collective knowledge of other developers, users and community members (of
various communities!) helped us sort it out (temporarily, anyway).&lt;/p&gt;
&lt;p&gt;I'm not quite sure yet how I will address this use case for Baby Buddy
(&lt;a class="reference external" href="https://github.com/cdubz/babybuddy/issues/43"&gt;suggestions welcome&lt;/a&gt;), but I had a damn fun time troubleshooting it! Next
up, what the hell is wrong with the first Pi I tested this on...&lt;/p&gt;
&lt;/div&gt;
</content><category term="Technology"></category><category term="arm"></category><category term="baby buddy"></category><category term="pip"></category><category term="pipenv"></category><category term="python"></category><category term="raspberry pi"></category><category term="troubleshooting"></category></entry><entry><title>Advent of Code 2017: Days 16 - 20</title><link href="https://www.chris-wells.net/articles/2017/12/20/advent-of-code-2017-days-16-20" rel="alternate"></link><published>2017-12-20T10:00:00-04:00</published><updated>2017-12-20T10:00:00-04:00</updated><author><name>Christopher Charbonneau Wells</name></author><id>tag:www.chris-wells.net,2017-12-20:/articles/2017/12/20/advent-of-code-2017-days-16-20</id><summary type="html">
&lt;blockquote class="small"&gt;
    &lt;em&gt;
        This post is part of the series, &lt;a href="/articles/2017/11/30/advent-of-code-2017/"&gt;Advent of Code 2017&lt;/a&gt;,
        where I work my way through (part of) the &lt;a href="http://adventofcode.com/2017"&gt;2017 Advent of Code&lt;/a&gt;
        challenges in order to re-learn some of the basics of Python and
        reflect on some simple concepts and functionality. All my challenge
        code is available in &lt;a href="https://github.com/cdubz/advent-of-code-2017"&gt;cdubz/advent-of-code-2017&lt;/a&gt;
        on GitHub.
    &lt;/em&gt;
&lt;/blockquote&gt;
&lt;h2&gt;Day 16: One Billion Permutations in 0.535 Seconds&lt;/h2&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;Challenge: &lt;a class="reference external" href="http://adventofcode.com/2017/day/16"&gt;Day 16: Permutation Promenade&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Solution: &lt;a class="reference external" href="https://github.com/cdubz/advent-of-code-2017/tree/master/16"&gt;advent-of-code-2017/16&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Ok, not really (: This challenge involved making specific modifications to a
list 1,000,000,000 (&lt;strong&gt;one billion&lt;/strong&gt;) times. Out of curiosity, the first thing I
did was set up a &lt;a class="reference external" href="https://www.chris-wells.net/articles/2017/12/15/advent-of-code-2017-days-11-15#day-15-progressbar2"&gt;progress bar loop&lt;/a&gt; to run the actual modifications all one
billion times. The resulting progress bar estimated that the entire operation
would take around &lt;strong&gt;133 days&lt;/strong&gt;. So... a different approach:&lt;/p&gt;

&lt;span class="normal"&gt;1&lt;/span&gt;
&lt;span class="normal"&gt;2&lt;/span&gt;
&lt;span class="normal"&gt;3&lt;/span&gt;
&lt;span class="normal"&gt;4&lt;/span&gt;
&lt;span class="normal"&gt;5&lt;/span&gt;
&lt;span class="normal"&gt;6&lt;/span&gt;
&lt;span class="normal"&gt;7&lt;/span&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class="code"&gt;
&lt;span class="n"&gt;movements&lt;/span&gt; &lt;span class="o"&gt;=&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;possibilities&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;positions&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;positions&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;move&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;positions&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;movements&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;positions&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;possibilities&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;break&lt;/span&gt;
    &lt;span class="n"&gt;possibilities&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;positions&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;p&gt;Because the &lt;tt class="docutils literal"&gt;movements&lt;/tt&gt; are always the same, eventually the &lt;tt class="docutils literal"&gt;positions&lt;/tt&gt;
list elements will return to their initial state. In this case, that happened
on the 41st movement, making for a &lt;tt class="docutils literal"&gt;possibilities&lt;/tt&gt; list of size 42. Armed
with &lt;a class="reference external" href="https://en.wikipedia.org/wiki/42_(number)#The_Hitchhiker's_Guide_to_the_Galaxy"&gt;the answer to the ultimate question of life, the universe, and everything&lt;/a&gt;,
it was much easier to determine the value of &lt;tt class="docutils literal"&gt;positions&lt;/tt&gt; at the one billionth
permutation:&lt;/p&gt;

&lt;span class="normal"&gt; 9&lt;/span&gt;
&lt;span class="normal"&gt;10&lt;/span&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class="code"&gt;
&lt;span class="n"&gt;answer_key&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pos&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pos&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;possibilities&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;possibilities&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;possibilities&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;answer_key&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;  &lt;span class="c1"&gt;# 1,000,000,000!&lt;/span&gt;
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;p&gt;This all takes a little over half a second, as opposed to the 133 days of
processing that would be needed to run all one billion permutations. Phew!&lt;/p&gt;
</summary><content type="html">&lt;span class="target" id="top"&gt;&lt;/span&gt;
&lt;blockquote class="small"&gt;
    &lt;em&gt;
        This post is part of the series, &lt;a href="/articles/2017/11/30/advent-of-code-2017/"&gt;Advent of Code 2017&lt;/a&gt;,
        where I work my way through (part of) the &lt;a href="http://adventofcode.com/2017"&gt;2017 Advent of Code&lt;/a&gt;
        challenges in order to re-learn some of the basics of Python and
        reflect on some simple concepts and functionality. All my challenge
        code is available in &lt;a href="https://github.com/cdubz/advent-of-code-2017"&gt;cdubz/advent-of-code-2017&lt;/a&gt;
        on GitHub.
    &lt;/em&gt;
&lt;/blockquote&gt;&lt;div class="section" id="day-16-one-billion-permutations-in-0-535-seconds"&gt;
&lt;h2&gt;Day 16: One Billion Permutations in 0.535 Seconds&lt;/h2&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;Challenge: &lt;a class="reference external" href="http://adventofcode.com/2017/day/16"&gt;Day 16: Permutation Promenade&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Solution: &lt;a class="reference external" href="https://github.com/cdubz/advent-of-code-2017/tree/master/16"&gt;advent-of-code-2017/16&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Ok, not really (: This challenge involved making specific modifications to a
list 1,000,000,000 (&lt;strong&gt;one billion&lt;/strong&gt;) times. Out of curiosity, the first thing I
did was set up a &lt;a class="reference external" href="https://www.chris-wells.net/articles/2017/12/15/advent-of-code-2017-days-11-15#day-15-progressbar2"&gt;progress bar loop&lt;/a&gt; to run the actual modifications all one
billion times. The resulting progress bar estimated that the entire operation
would take around &lt;strong&gt;133 days&lt;/strong&gt;. So... a different approach:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;table class="highlighttable"&gt;&lt;tr&gt;&lt;td class="linenos"&gt;&lt;div class="linenodiv"&gt;&lt;pre&gt;&lt;span class="normal"&gt;0&lt;/span&gt;
&lt;span class="normal"&gt;1&lt;/span&gt;
&lt;span class="normal"&gt;2&lt;/span&gt;
&lt;span class="normal"&gt;3&lt;/span&gt;
&lt;span class="normal"&gt;4&lt;/span&gt;
&lt;span class="normal"&gt;5&lt;/span&gt;
&lt;span class="normal"&gt;6&lt;/span&gt;
&lt;span class="normal"&gt;7&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;div&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="n"&gt;positions&lt;/span&gt; &lt;span class="o"&gt;=&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;movements&lt;/span&gt; &lt;span class="o"&gt;=&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;possibilities&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;positions&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;positions&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;move&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;positions&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;movements&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;positions&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;possibilities&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;break&lt;/span&gt;
    &lt;span class="n"&gt;possibilities&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;positions&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/div&gt;
&lt;p&gt;Because the &lt;tt class="docutils literal"&gt;movements&lt;/tt&gt; are always the same, eventually the &lt;tt class="docutils literal"&gt;positions&lt;/tt&gt;
list elements will return to their initial state. In this case, that happened
on the 41st movement, making for a &lt;tt class="docutils literal"&gt;possibilities&lt;/tt&gt; list of size 42. Armed
with &lt;a class="reference external" href="https://en.wikipedia.org/wiki/42_(number)#The_Hitchhiker's_Guide_to_the_Galaxy"&gt;the answer to the ultimate question of life, the universe, and everything&lt;/a&gt;,
it was much easier to determine the value of &lt;tt class="docutils literal"&gt;positions&lt;/tt&gt; at the one billionth
permutation:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;table class="highlighttable"&gt;&lt;tr&gt;&lt;td class="linenos"&gt;&lt;div class="linenodiv"&gt;&lt;pre&gt;&lt;span class="normal"&gt; 8&lt;/span&gt;
&lt;span class="normal"&gt; 9&lt;/span&gt;
&lt;span class="normal"&gt;10&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;div&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="n"&gt;pos&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1000000000&lt;/span&gt;
&lt;span class="n"&gt;answer_key&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pos&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pos&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;possibilities&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;possibilities&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;possibilities&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;answer_key&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;  &lt;span class="c1"&gt;# 1,000,000,000!&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/div&gt;
&lt;p&gt;This all takes a little over half a second, as opposed to the 133 days of
processing that would be needed to run all one billion permutations. Phew!&lt;/p&gt;

&lt;/div&gt;
&lt;div class="section" id="day-17-collections-deque"&gt;
&lt;h2&gt;Day 17: collections.deque()&lt;/h2&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;Challenge: &lt;a class="reference external" href="http://adventofcode.com/2017/day/17"&gt;Day 17: Spinlock&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Solution: &lt;a class="reference external" href="https://github.com/cdubz/advent-of-code-2017/tree/master/17"&gt;advent-of-code-2017/17&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Speed is the key once again with this challenge. After optimizing my initial
solution as best I could, my program was still facing an 18 hour execution time
to find the correct answer. Eventually I found my way to &lt;a class="reference external" href="https://docs.python.org/3.6/library/collections.html#collections.deque"&gt;collections.deque&lt;/a&gt; to
speed things up tremendously. Python's documentation sums up the difference
between a &lt;tt class="docutils literal"&gt;deque&lt;/tt&gt; and &lt;tt class="docutils literal"&gt;list&lt;/tt&gt; like so:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Deques are a generalization of stacks and queues (the name is pronounced
&amp;quot;deck&amp;quot; and is short for &amp;quot;double-ended queue&amp;quot;). Deques support thread-safe,
memory efficient appends and pops from either side of the deque with
approximately the same O(1) performance in either direction.&lt;/p&gt;
&lt;p&gt;Though list objects support similar operations, they are optimized for fast
fixed-length operations and incur O(n) memory movement costs for pop(0) and
insert(0, v) operations which change both the size and position of the
underlying data representation.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Simply changing the list, which goes through 50,000,000 (&lt;strong&gt;50 million&lt;/strong&gt;)
&lt;tt class="docutils literal"&gt;insert&lt;/tt&gt; operations, to a deque cut the execution time down a bit, but the
big time saver ended up being &lt;a class="reference external" href="https://docs.python.org/3.6/library/collections.html#collections.deque.rotate"&gt;deque.rotate&lt;/a&gt;. Using &lt;tt class="docutils literal"&gt;rotate&lt;/tt&gt; allowed the
&lt;tt class="docutils literal"&gt;insert&lt;/tt&gt; operations to be replaced with &lt;em&gt;much&lt;/em&gt; fast &lt;tt class="docutils literal"&gt;append&lt;/tt&gt; operations.
These two changes cut the execution time to about &lt;strong&gt;three minutes&lt;/strong&gt; (from 18
hours).&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="day-18-negative-numbers-and-str-isdigit"&gt;
&lt;h2&gt;Day 18: Negative Numbers and str.isdigit()&lt;/h2&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;Challenge: &lt;a class="reference external" href="http://adventofcode.com/2017/day/18"&gt;Day 18: Duet&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Solution: &lt;a class="reference external" href="https://github.com/cdubz/advent-of-code-2017/tree/master/18"&gt;advent-of-code-2017/18&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I was stuck with a bug while working on a piece of this challenge that had to
identify the difference between an alpha and numeric string. Checking for alpha
with &lt;a class="reference external" href="https://docs.python.org/3/library/stdtypes.html#str.isalpha"&gt;str.isalpha()&lt;/a&gt; worked well enough but for some reason I wasn't having the
same luck with &lt;a class="reference external" href="https://docs.python.org/3/library/stdtypes.html#str.isdigit"&gt;str.isdigit()&lt;/a&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;table class="highlighttable"&gt;&lt;tr&gt;&lt;td class="linenos"&gt;&lt;div class="linenodiv"&gt;&lt;pre&gt;&lt;span class="normal"&gt;0&lt;/span&gt;
&lt;span class="normal"&gt;1&lt;/span&gt;
&lt;span class="normal"&gt;2&lt;/span&gt;
&lt;span class="normal"&gt;3&lt;/span&gt;
&lt;span class="normal"&gt;4&lt;/span&gt;
&lt;span class="normal"&gt;5&lt;/span&gt;
&lt;span class="normal"&gt;6&lt;/span&gt;
&lt;span class="normal"&gt;7&lt;/span&gt;
&lt;span class="normal"&gt;8&lt;/span&gt;
&lt;span class="normal"&gt;9&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;div&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;A&amp;#39;&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;isalpha&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="kc"&gt;True&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;b&amp;#39;&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;isalpha&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="kc"&gt;True&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;1&amp;#39;&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;isdigit&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="kc"&gt;True&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;0&amp;#39;&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;isdigit&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="kc"&gt;True&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;-1&amp;#39;&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;isdigit&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="kc"&gt;False&lt;/span&gt;  &lt;span class="c1"&gt;# Oh no!&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/div&gt;
&lt;p&gt;What these examples illustrate is that &lt;tt class="docutils literal"&gt;str.isdigit()&lt;/tt&gt; does not return
&lt;tt class="docutils literal"&gt;True&lt;/tt&gt; for &lt;em&gt;negative&lt;/em&gt; numbers. The Python documentation explains why (emphasis
mine):&lt;/p&gt;
&lt;blockquote&gt;
Return true if &lt;strong&gt;all characters&lt;/strong&gt; in the string are digits [...]&lt;/blockquote&gt;
&lt;p&gt;The result for negative number is &lt;tt class="docutils literal"&gt;False&lt;/tt&gt; because the negative sign itself
(-) is not a digit.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="day-19-the-internet-is-not-a-big-truck"&gt;
&lt;h2&gt;Day 19: The Internet is Not a Big Truck&lt;/h2&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;Challenge: &lt;a class="reference external" href="http://adventofcode.com/2017/day/19"&gt;Day 19: A Series of Tubes&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Solution: &lt;a class="reference external" href="https://github.com/cdubz/advent-of-code-2017/tree/master/19"&gt;advent-of-code-2017/19&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This challenge didn't uncover any major new Python discoveries, but it was an
amusing reminder of the infamous words of &lt;a class="reference external" href="https://en.wikipedia.org/wiki/Ted_Stevens"&gt;Ted Stevens&lt;/a&gt;:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;I just the other day got, an internet was sent by my staff at 10 o'clock in
the morning on Friday and I just got it yesterday. Why?&lt;/p&gt;
&lt;p&gt;Because it got tangled up with all these things going on the internet
commercially...&lt;/p&gt;
&lt;p&gt;They want to deliver vast amounts of information over the internet. And
again, the internet is not something you just dump something on. It's not a
truck.&lt;/p&gt;
&lt;p&gt;It's a series of tubes.&lt;/p&gt;
&lt;p&gt;And if you don't understand those tubes can be filled and if they are
filled, when you put your message in, it gets in line and its going to be
delayed by anyone that puts into that tube enormous amounts of material,
enormous amounts of material.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Ouch.&lt;/p&gt;
&lt;div class="youtube youtube-16x9"&gt;&lt;iframe src="https://www.youtube.com/embed/_cZC67wXUTs" allowfullscreen seamless frameBorder="0"&gt;&lt;/iframe&gt;&lt;/div&gt;&lt;/div&gt;
&lt;div class="section" id="day-20-reading-from-a-file"&gt;
&lt;h2&gt;Day 20: Reading from a File&lt;/h2&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;Challenge: &lt;a class="reference external" href="http://adventofcode.com/2017/day/20"&gt;Day 20: Particle Swarm&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Solution: &lt;a class="reference external" href="https://github.com/cdubz/advent-of-code-2017/tree/master/20"&gt;advent-of-code-2017/20&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This solution makes use of earlier lessons about &lt;a class="reference external" href="https://www.chris-wells.net/articles/2017/12/05/advent-of-code-2017-days-1-5#day-3-manhattan-distance"&gt;Manhattan Distance&lt;/a&gt; and
&lt;a class="reference external" href="https://www.chris-wells.net/articles/2017/12/10/advent-of-code-2017-days-6-10#day-6-max-x-key-y"&gt;the key argument&lt;/a&gt; for &lt;tt class="docutils literal"&gt;min()&lt;/tt&gt; and &lt;tt class="docutils literal"&gt;max()&lt;/tt&gt;, but beyond that nothing much
new was introduced. One thing that has been important throughout this experience
is reading content from a file, as most challenges include some long form of
input data.&lt;/p&gt;
&lt;p&gt;Compound statements such as &lt;tt class="docutils literal"&gt;if&lt;/tt&gt; and &lt;tt class="docutils literal"&gt;for&lt;/tt&gt; are as popular and well known in
Python as probably any other language, but the other statement that has come in
handy for almost every challenge so far is the &lt;a class="reference external" href="https://docs.python.org/3/reference/compound_stmts.html#the-with-statement"&gt;with statement&lt;/a&gt;. When used with
a &lt;a class="reference external" href="https://docs.python.org/3/reference/datamodel.html#context-managers"&gt;context manager&lt;/a&gt; such as &lt;tt class="docutils literal"&gt;open()&lt;/tt&gt;, the &lt;tt class="docutils literal"&gt;with&lt;/tt&gt; statement makes it simple
to do things like handle files without worrying about things like closing the
file manually. The simplest example is to process each line of a file like so:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;table class="highlighttable"&gt;&lt;tr&gt;&lt;td class="linenos"&gt;&lt;div class="linenodiv"&gt;&lt;pre&gt;&lt;span class="normal"&gt;0&lt;/span&gt;
&lt;span class="normal"&gt;1&lt;/span&gt;
&lt;span class="normal"&gt;2&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;div&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="nb"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;file.txt&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;line&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;line&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/div&gt;
&lt;p&gt;This code opens a &lt;tt class="docutils literal"&gt;file.txt&lt;/tt&gt; and assigns it to the variable &lt;tt class="docutils literal"&gt;f&lt;/tt&gt;, uses a
&lt;tt class="docutils literal"&gt;for&lt;/tt&gt; loop to print each line of the file, and finally closes the file
(automatically!). All kinds of things can be done with each &lt;tt class="docutils literal"&gt;line&lt;/tt&gt; or the file
itself here.&lt;/p&gt;
&lt;p&gt;&lt;a class="reference external" href="https://jeffknupp.com/"&gt;Jeff Knupp&lt;/a&gt; provides a very insightful read on context managers in general:
&lt;a class="reference external" href="https://jeffknupp.com/blog/2016/03/07/python-with-context-managers/"&gt;Python with Context Managers&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;If all lines (or other elements) of a file need to be evaluated together, the
&lt;tt class="docutils literal"&gt;open&lt;/tt&gt; function can be used to cram things in to a list, dictionary, or other
structure easily:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;table class="highlighttable"&gt;&lt;tr&gt;&lt;td class="linenos"&gt;&lt;div class="linenodiv"&gt;&lt;pre&gt;&lt;span class="normal"&gt;0&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;div&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="n"&gt;lines&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;line&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;rstrip&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;line&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nb"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;input.txt&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;readlines&lt;/span&gt;&lt;span class="p"&gt;()]&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/div&gt;
&lt;p&gt;This simple statement will read each line of &lt;tt class="docutils literal"&gt;input.txt&lt;/tt&gt; in to a list,
applying &lt;tt class="docutils literal"&gt;rstrip&lt;/tt&gt; each time to remove trailing data (i.e. the line breaks).
Comprehensions like this can quickly accomplish basic data manipulation to turn
file lines in to more easily manageable structures.&lt;/p&gt;
&lt;/div&gt;
</content><category term="Technology"></category><category term="advent of code"></category><category term="python"></category></entry><entry><title>Advent of Code 2017: Days 11 - 15</title><link href="https://www.chris-wells.net/articles/2017/12/15/advent-of-code-2017-days-11-15" rel="alternate"></link><published>2017-12-15T08:00:00-04:00</published><updated>2017-12-15T08:00:00-04:00</updated><author><name>Christopher Charbonneau Wells</name></author><id>tag:www.chris-wells.net,2017-12-15:/articles/2017/12/15/advent-of-code-2017-days-11-15</id><summary type="html">
&lt;blockquote class="small"&gt;
    &lt;em&gt;
        This post is part of the series, &lt;a href="/articles/2017/11/30/advent-of-code-2017/"&gt;Advent of Code 2017&lt;/a&gt;,
        where I work my way through (part of) the &lt;a href="http://adventofcode.com/2017"&gt;2017 Advent of Code&lt;/a&gt;
        challenges in order to re-learn some of the basics of Python and
        reflect on some simple concepts and functionality. All my challenge
        code is available in &lt;a href="https://github.com/cdubz/advent-of-code-2017"&gt;cdubz/advent-of-code-2017&lt;/a&gt;
        on GitHub.
    &lt;/em&gt;
&lt;/blockquote&gt;
&lt;h2&gt;Day 11: Navigating a Hexagon Grid&lt;/h2&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;Challenge: &lt;a class="reference external" href="http://adventofcode.com/2017/day/11"&gt;Day 11: Hex Ed&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Solution: &lt;a class="reference external" href="https://github.com/cdubz/advent-of-code-2017/tree/master/11"&gt;advent-of-code-2017/11&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I fought with how to create and navigate a hexagon grid for a bit before I
stumbled on an extremely useful post by &lt;a class="reference external" href="http://keekerdc.com/"&gt;Chris Schetter&lt;/a&gt;:
&lt;a class="reference external" href="http://keekerdc.com/2011/03/hexagon-grids-coordinate-systems-and-distance-calculations/"&gt;Hexagon grids: coordinate systems and distance calculations&lt;/a&gt;.
Working in 2d grids (with four possible movement directions) in Python is
fairly easy with some dictionaries, but a hexagon grid means there are &lt;strong&gt;six&lt;/strong&gt;
potential directions and simple calculations of x,y coordinates won't quite get
the job done. What Chris explains so well is a simple concept: flip the grid and
add a z-axis.&lt;/p&gt;
&lt;p&gt;For my solution, I &amp;quot;flip&amp;quot; the grid by turning it to the right twice such that
north and south become east and west:&lt;/p&gt;
&lt;pre class="literal-block"&gt;
  \ n  /
nw +--+ ne              sw +--+ nw
  /    \                \ /    \ /
-+      +-    ----&amp;gt;    s +      + n
  \    /                / \    / \
sw +--+ se              se +--+ ne
  / s  \
&lt;/pre&gt;
&lt;p&gt;This puts north and south along the x-axis instead of the y-axis. Next, adding
the z-axis and shifting all axises 120 degrees means that each hex can be made
up of &lt;strong&gt;three&lt;/strong&gt; points instead of two. Again, I refer to Chris's post and this
image that illustrates the concept wonderfully:&lt;/p&gt;
&lt;p class="image-box center"&gt;&lt;img alt="Hex grid with coordinates" src="/static/images/2017/aoc-2017/HexGridLandscapeTriCoordinates.gif" /&gt; Credit &lt;a class="reference external" href="http://keekerdc.com/"&gt;Chris Schetter&lt;/a&gt; (&lt;a class="reference external" href="http://keekerdc.com/2011/03/hexagon-grids-coordinate-systems-and-distance-calculations/"&gt;Hexagon grids: coordinate systems and distance calculations&lt;/a&gt;)&lt;/p&gt;
</summary><content type="html">&lt;span class="target" id="top"&gt;&lt;/span&gt;
&lt;blockquote class="small"&gt;
    &lt;em&gt;
        This post is part of the series, &lt;a href="/articles/2017/11/30/advent-of-code-2017/"&gt;Advent of Code 2017&lt;/a&gt;,
        where I work my way through (part of) the &lt;a href="http://adventofcode.com/2017"&gt;2017 Advent of Code&lt;/a&gt;
        challenges in order to re-learn some of the basics of Python and
        reflect on some simple concepts and functionality. All my challenge
        code is available in &lt;a href="https://github.com/cdubz/advent-of-code-2017"&gt;cdubz/advent-of-code-2017&lt;/a&gt;
        on GitHub.
    &lt;/em&gt;
&lt;/blockquote&gt;&lt;div class="section" id="day-11-navigating-a-hexagon-grid"&gt;
&lt;h2&gt;Day 11: Navigating a Hexagon Grid&lt;/h2&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;Challenge: &lt;a class="reference external" href="http://adventofcode.com/2017/day/11"&gt;Day 11: Hex Ed&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Solution: &lt;a class="reference external" href="https://github.com/cdubz/advent-of-code-2017/tree/master/11"&gt;advent-of-code-2017/11&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I fought with how to create and navigate a hexagon grid for a bit before I
stumbled on an extremely useful post by &lt;a class="reference external" href="http://keekerdc.com/"&gt;Chris Schetter&lt;/a&gt;:
&lt;a class="reference external" href="http://keekerdc.com/2011/03/hexagon-grids-coordinate-systems-and-distance-calculations/"&gt;Hexagon grids: coordinate systems and distance calculations&lt;/a&gt;.
Working in 2d grids (with four possible movement directions) in Python is
fairly easy with some dictionaries, but a hexagon grid means there are &lt;strong&gt;six&lt;/strong&gt;
potential directions and simple calculations of x,y coordinates won't quite get
the job done. What Chris explains so well is a simple concept: flip the grid and
add a z-axis.&lt;/p&gt;
&lt;p&gt;For my solution, I &amp;quot;flip&amp;quot; the grid by turning it to the right twice such that
north and south become east and west:&lt;/p&gt;
&lt;pre class="literal-block"&gt;
  \ n  /
nw +--+ ne              sw +--+ nw
  /    \                \ /    \ /
-+      +-    ----&amp;gt;    s +      + n
  \    /                / \    / \
sw +--+ se              se +--+ ne
  / s  \
&lt;/pre&gt;
&lt;p&gt;This puts north and south along the x-axis instead of the y-axis. Next, adding
the z-axis and shifting all axises 120 degrees means that each hex can be made
up of &lt;strong&gt;three&lt;/strong&gt; points instead of two. Again, I refer to Chris's post and this
image that illustrates the concept wonderfully:&lt;/p&gt;
&lt;p class="image-box center"&gt;&lt;img alt="Hex grid with coordinates" src="/static/images/2017/aoc-2017/HexGridLandscapeTriCoordinates.gif" /&gt; Credit &lt;a class="reference external" href="http://keekerdc.com/"&gt;Chris Schetter&lt;/a&gt; (&lt;a class="reference external" href="http://keekerdc.com/2011/03/hexagon-grids-coordinate-systems-and-distance-calculations/"&gt;Hexagon grids: coordinate systems and distance calculations&lt;/a&gt;)&lt;/p&gt;

&lt;p&gt;Armed with three coordinates, it is now much easier to navigate the hex grid
&lt;em&gt;and&lt;/em&gt; calculate the difference between two locations with Python. First, two of
the three coordinates need to be modified for each type of movement:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;table class="highlighttable"&gt;&lt;tr&gt;&lt;td class="linenos"&gt;&lt;div class="linenodiv"&gt;&lt;pre&gt;&lt;span class="normal"&gt; 0&lt;/span&gt;
&lt;span class="normal"&gt; 1&lt;/span&gt;
&lt;span class="normal"&gt; 2&lt;/span&gt;
&lt;span class="normal"&gt; 3&lt;/span&gt;
&lt;span class="normal"&gt; 4&lt;/span&gt;
&lt;span class="normal"&gt; 5&lt;/span&gt;
&lt;span class="normal"&gt; 6&lt;/span&gt;
&lt;span class="normal"&gt; 7&lt;/span&gt;
&lt;span class="normal"&gt; 8&lt;/span&gt;
&lt;span class="normal"&gt; 9&lt;/span&gt;
&lt;span class="normal"&gt;10&lt;/span&gt;
&lt;span class="normal"&gt;11&lt;/span&gt;
&lt;span class="normal"&gt;12&lt;/span&gt;
&lt;span class="normal"&gt;13&lt;/span&gt;
&lt;span class="normal"&gt;14&lt;/span&gt;
&lt;span class="normal"&gt;15&lt;/span&gt;
&lt;span class="normal"&gt;16&lt;/span&gt;
&lt;span class="normal"&gt;17&lt;/span&gt;
&lt;span class="normal"&gt;18&lt;/span&gt;
&lt;span class="normal"&gt;19&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;div&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;move&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;z&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;direction&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;direction&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;n&amp;#39;&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="mi"&gt;1&lt;/span&gt;
        &lt;span class="n"&gt;z&lt;/span&gt; &lt;span class="o"&gt;-=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
    &lt;span class="k"&gt;elif&lt;/span&gt; &lt;span class="n"&gt;direction&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;ne&amp;#39;&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="mi"&gt;1&lt;/span&gt;
        &lt;span class="n"&gt;y&lt;/span&gt; &lt;span class="o"&gt;-=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
    &lt;span class="k"&gt;elif&lt;/span&gt; &lt;span class="n"&gt;direction&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;se&amp;#39;&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="mi"&gt;1&lt;/span&gt;
        &lt;span class="n"&gt;z&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
    &lt;span class="k"&gt;elif&lt;/span&gt; &lt;span class="n"&gt;direction&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;s&amp;#39;&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="mi"&gt;1&lt;/span&gt;
        &lt;span class="n"&gt;z&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
    &lt;span class="k"&gt;elif&lt;/span&gt; &lt;span class="n"&gt;direction&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;sw&amp;#39;&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="mi"&gt;1&lt;/span&gt;
        &lt;span class="n"&gt;y&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
    &lt;span class="k"&gt;elif&lt;/span&gt; &lt;span class="n"&gt;direction&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;nw&amp;#39;&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="mi"&gt;1&lt;/span&gt;
        &lt;span class="n"&gt;z&lt;/span&gt; &lt;span class="o"&gt;-=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;z&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/div&gt;
&lt;p&gt;This function takes the current position (&lt;tt class="docutils literal"&gt;x&lt;/tt&gt;, &lt;tt class="docutils literal"&gt;y&lt;/tt&gt;, &lt;tt class="docutils literal"&gt;z&lt;/tt&gt;) and returns
modified coordinates based on the &lt;tt class="docutils literal"&gt;direction&lt;/tt&gt; being moved. After all movement
is a complete, a simple formula can be used to calculate the shortest number of
movements between two points on the hex grid.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;table class="highlighttable"&gt;&lt;tr&gt;&lt;td class="linenos"&gt;&lt;div class="linenodiv"&gt;&lt;pre&gt;&lt;span class="normal"&gt;0&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;div&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="nb"&gt;max&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;from_x&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;to_x&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;from_y&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;to_y&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;from_z&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;to_z&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/div&gt;
&lt;p&gt;That's all there is to it!&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="day-12-recursion"&gt;
&lt;h2&gt;Day 12: Recursion&lt;/h2&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;Challenge: &lt;a class="reference external" href="http://adventofcode.com/2017/day/12"&gt;Day 12: Digital Plumber&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Solution: &lt;a class="reference external" href="https://github.com/cdubz/advent-of-code-2017/tree/master/12"&gt;advent-of-code-2017/12&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;When faced with a recursion problem, I always tend to struggle a bit to build a
mental model of what my code needs to do. This challenge provided a number of
&amp;quot;programs&amp;quot; (identified by numbers) interconnected by &amp;quot;pipes&amp;quot; that could be used
to connect programs at any depth. Recursion is therefore necessary to figure out
&lt;em&gt;all&lt;/em&gt; available connections.&lt;/p&gt;
&lt;p&gt;I achieved this with a function that takes all the pipes, a &amp;quot;parent&amp;quot; program
(number) to evaluate (&amp;quot;plumb&amp;quot;), a list to record connections, and a list to
record already plumbed programs:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;table class="highlighttable"&gt;&lt;tr&gt;&lt;td class="linenos"&gt;&lt;div class="linenodiv"&gt;&lt;pre&gt;&lt;span class="normal"&gt;0&lt;/span&gt;
&lt;span class="normal"&gt;1&lt;/span&gt;
&lt;span class="normal"&gt;2&lt;/span&gt;
&lt;span class="normal"&gt;3&lt;/span&gt;
&lt;span class="normal"&gt;4&lt;/span&gt;
&lt;span class="normal"&gt;5&lt;/span&gt;
&lt;span class="normal"&gt;6&lt;/span&gt;
&lt;span class="normal"&gt;7&lt;/span&gt;
&lt;span class="normal"&gt;8&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;div&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;plumb&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pipes&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;parent&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;connections&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;plumbed&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;connections&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;parent&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;plumbed&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;parent&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;child&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;pipes&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;parent&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;child&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;plumbed&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;connections&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;child&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="n"&gt;plumbed&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;child&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="n"&gt;plumb&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pipes&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;child&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;connections&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;plumbed&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;connections&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/div&gt;
&lt;p&gt;The recursion is triggered on line 7, but only if the program being evaluated
(&lt;tt class="docutils literal"&gt;child&lt;/tt&gt;) has not already been plumbed. This is tracked by passing the
&lt;tt class="docutils literal"&gt;connections&lt;/tt&gt; and &lt;tt class="docutils literal"&gt;plumbed&lt;/tt&gt; lists in to each new execution of &lt;tt class="docutils literal"&gt;plumb&lt;/tt&gt; in
order to maintain an ongoing list and prevent infinite recursion.&lt;/p&gt;
&lt;p&gt;While this approach worked, I am inclined to think there are probably better
ways to deal with recursion problems like this in Python - a challenge for
another day.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="day-13-shallow-vs-deep-copy"&gt;
&lt;h2&gt;Day 13: Shallow vs. Deep Copy&lt;/h2&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;Challenge: &lt;a class="reference external" href="http://adventofcode.com/2017/day/13"&gt;Day 13: Packet Scanners&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Solution: &lt;a class="reference external" href="https://github.com/cdubz/advent-of-code-2017/tree/master/13"&gt;advent-of-code-2017/13&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Warning: my solution to this challenge is horribly inefficient.&lt;/strong&gt; I hope to
return to this challenge some day when I have a little more time (I'm looking
after my infant son right now so time is pretty tight) to figure out a more
efficient way to do it. My solution took &lt;strong&gt;an hour and four minutes&lt;/strong&gt; to
complete. By contrast, the fastest finisher for the day completed the challenge
(including reading the problem and writing the code) in about five minutes...&lt;/p&gt;
&lt;p&gt;The most interesting discovery I made while working on this was about the
difference between a &amp;quot;deep&amp;quot; and &amp;quot;shallow&amp;quot; copy in Python. Python, unlike other
languages such as PHP (where most of my experience is), uses &amp;quot;names&amp;quot; instead of
&amp;quot;variables&amp;quot; when assigning values. I already had a vague understanding of this,
but this challenge quickly revealed just how vague. The concept is thoroughly
explained by &lt;a class="reference external" href="https://mathieularose.com/"&gt;Mathieu Larose's&lt;/a&gt; post: &lt;a class="reference external" href="https://mathieularose.com/python-variables/"&gt;Understanding Python Variables&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;This was as issue when I was trying to modify a &lt;em&gt;copy&lt;/em&gt; of a dictionary so I
could reuse the original later in the code. Here is a basic example of what
happens in Python:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;table class="highlighttable"&gt;&lt;tr&gt;&lt;td class="linenos"&gt;&lt;div class="linenodiv"&gt;&lt;pre&gt;&lt;span class="normal"&gt;0&lt;/span&gt;
&lt;span class="normal"&gt;1&lt;/span&gt;
&lt;span class="normal"&gt;2&lt;/span&gt;
&lt;span class="normal"&gt;3&lt;/span&gt;
&lt;span class="normal"&gt;4&lt;/span&gt;
&lt;span class="normal"&gt;5&lt;/span&gt;
&lt;span class="normal"&gt;6&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;div&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;7&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;8&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;7&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/div&gt;
&lt;p&gt;In assigning &lt;tt class="docutils literal"&gt;b = a&lt;/tt&gt;, Python actually links names &lt;tt class="docutils literal"&gt;a&lt;/tt&gt; and &lt;tt class="docutils literal"&gt;b&lt;/tt&gt; to the
&lt;em&gt;same&lt;/em&gt; dictionary (&lt;tt class="docutils literal"&gt;{1:2, 3:4, 5:6}&lt;/tt&gt;, initially). When I ran in to this
problem, my approach was to copy the dictionary using &lt;tt class="docutils literal"&gt;dict.copy()&lt;/tt&gt;. This
process, called a &lt;em&gt;shallow copy&lt;/em&gt;, still actually creates references for each
item in the dictionary, so &lt;em&gt;changes to existing dictionary items&lt;/em&gt; continue to
happen in both places:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;table class="highlighttable"&gt;&lt;tr&gt;&lt;td class="linenos"&gt;&lt;div class="linenodiv"&gt;&lt;pre&gt;&lt;span class="normal"&gt;0&lt;/span&gt;
&lt;span class="normal"&gt;1&lt;/span&gt;
&lt;span class="normal"&gt;2&lt;/span&gt;
&lt;span class="normal"&gt;3&lt;/span&gt;
&lt;span class="normal"&gt;4&lt;/span&gt;
&lt;span class="normal"&gt;5&lt;/span&gt;
&lt;span class="normal"&gt;6&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;div&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;copy&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;a&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;0&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/div&gt;
&lt;p&gt;The alternative is a &lt;em&gt;deep copy&lt;/em&gt;, which copies the dictionary &lt;em&gt;and&lt;/em&gt; all of it's
items to a new dictionary:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;table class="highlighttable"&gt;&lt;tr&gt;&lt;td class="linenos"&gt;&lt;div class="linenodiv"&gt;&lt;pre&gt;&lt;span class="normal"&gt;0&lt;/span&gt;
&lt;span class="normal"&gt;1&lt;/span&gt;
&lt;span class="normal"&gt;2&lt;/span&gt;
&lt;span class="normal"&gt;3&lt;/span&gt;
&lt;span class="normal"&gt;4&lt;/span&gt;
&lt;span class="normal"&gt;5&lt;/span&gt;
&lt;span class="normal"&gt;6&lt;/span&gt;
&lt;span class="normal"&gt;7&lt;/span&gt;
&lt;span class="normal"&gt;8&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;div&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;deepcopy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;a&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;0&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/div&gt;
&lt;p&gt;There we go! &lt;a class="reference external" href="https://docs.python.org/3.6/library/copy.html"&gt;Python's &amp;quot;copy&amp;quot; documentation&lt;/a&gt;
explains this all in very clear terms and ultimately it seems to be something
that a developer like me, coming from PHP, needs to learn and relearn a few
times.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="day-14-hexadecimal-to-binary"&gt;
&lt;h2&gt;Day 14: Hexadecimal to Binary&lt;/h2&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;Challenge: &lt;a class="reference external" href="http://adventofcode.com/2017/day/14"&gt;Day 14: Disk Defragmentation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Solution: &lt;a class="reference external" href="https://github.com/cdubz/advent-of-code-2017/tree/master/14"&gt;advent-of-code-2017/14&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;A key aspect of this challenge is to convert a group of &lt;a class="reference external" href="https://en.wikipedia.org/wiki/Hexadecimal"&gt;hexadecimal&lt;/a&gt; digits
to their &lt;a class="reference external" href="https://en.wikipedia.org/wiki/Binary_number"&gt;binary&lt;/a&gt; representations with zero-prefixed padding. A simple way to
do this in Python is as follows:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;table class="highlighttable"&gt;&lt;tr&gt;&lt;td class="linenos"&gt;&lt;div class="linenodiv"&gt;&lt;pre&gt;&lt;span class="normal"&gt;0&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;div&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="n"&gt;binary&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;bin&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;digit&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;16&lt;/span&gt;&lt;span class="p"&gt;))[&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;:]&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;zfill&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;4&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;digit&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;hex_digits&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/div&gt;
&lt;p&gt;The &lt;tt class="docutils literal"&gt;[x for x in y]&lt;/tt&gt; structure should look very familiar to Python users -
this is a &lt;a class="reference external" href="https://docs.python.org/3.6/tutorial/datastructures.html#list-comprehensions"&gt;list comprehension&lt;/a&gt; that will perform operations on each item in a
&lt;tt class="docutils literal"&gt;for&lt;/tt&gt;-loop and create a &lt;tt class="docutils literal"&gt;list&lt;/tt&gt; structure containing the results.&lt;/p&gt;
&lt;p&gt;The first operation being performed in this case is an &lt;tt class="docutils literal"&gt;int()&lt;/tt&gt; on each
&lt;tt class="docutils literal"&gt;digit&lt;/tt&gt; in &lt;tt class="docutils literal"&gt;hex_digits&lt;/tt&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;table class="highlighttable"&gt;&lt;tr&gt;&lt;td class="linenos"&gt;&lt;div class="linenodiv"&gt;&lt;pre&gt;&lt;span class="normal"&gt;0&lt;/span&gt;
&lt;span class="normal"&gt;1&lt;/span&gt;
&lt;span class="normal"&gt;2&lt;/span&gt;
&lt;span class="normal"&gt;3&lt;/span&gt;
&lt;span class="normal"&gt;4&lt;/span&gt;
&lt;span class="normal"&gt;5&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;div&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;digit&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;1&amp;#39;&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;digit&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;16&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="s1"&gt;&amp;#39;1&amp;#39;&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;digit&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;f&amp;#39;&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;digit&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;16&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="s1"&gt;&amp;#39;15&amp;#39;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/div&gt;
&lt;p&gt;The second parameter for this &lt;tt class="docutils literal"&gt;int()&lt;/tt&gt; operation sets the base (or &lt;a class="reference external" href="https://en.wikipedia.org/wiki/Radix"&gt;radix&lt;/a&gt;)
for the operation. Hexadecimal is base 16, meaning it contains 16 digits:
0 - 9 and a - f, so &lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;init('f',&lt;/span&gt; 16)&lt;/tt&gt; results in 15.&lt;/p&gt;
&lt;p&gt;Next, &lt;tt class="docutils literal"&gt;bin()&lt;/tt&gt; converts this result to a binary string:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;table class="highlighttable"&gt;&lt;tr&gt;&lt;td class="linenos"&gt;&lt;div class="linenodiv"&gt;&lt;pre&gt;&lt;span class="normal"&gt;0&lt;/span&gt;
&lt;span class="normal"&gt;1&lt;/span&gt;
&lt;span class="normal"&gt;2&lt;/span&gt;
&lt;span class="normal"&gt;3&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;div&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;bin&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="s1"&gt;&amp;#39;0b1&amp;#39;&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;bin&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;15&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# &amp;#39;f&amp;#39;&lt;/span&gt;
&lt;span class="s1"&gt;&amp;#39;0b1111&amp;#39;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/div&gt;
&lt;p&gt;This challenge does not use the &lt;tt class="docutils literal"&gt;0b&lt;/tt&gt; prefix that &lt;tt class="docutils literal"&gt;bin()&lt;/tt&gt; returns, so a
&lt;a class="reference external" href="https://docs.python.org/3.6/library/functions.html?#slice"&gt;slice&lt;/a&gt; is used to exclude the first two characters: &lt;tt class="docutils literal"&gt;[2:]&lt;/tt&gt;.&lt;/p&gt;
&lt;p&gt;Lastly, all binary string representations need to be four characters so
&lt;a class="reference external" href="https://docs.python.org/2/library/string.html#string.zfill"&gt;string.zfill()&lt;/a&gt; is used to pad the string with zeros:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;table class="highlighttable"&gt;&lt;tr&gt;&lt;td class="linenos"&gt;&lt;div class="linenodiv"&gt;&lt;pre&gt;&lt;span class="normal"&gt;0&lt;/span&gt;
&lt;span class="normal"&gt;1&lt;/span&gt;
&lt;span class="normal"&gt;2&lt;/span&gt;
&lt;span class="normal"&gt;3&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;div&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;1&amp;#39;&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;zfill&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="s1"&gt;&amp;#39;0001&amp;#39;&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;1111&amp;#39;&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;zfill&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# &amp;#39;f&amp;#39;&lt;/span&gt;
&lt;span class="s1"&gt;&amp;#39;1111&amp;#39;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/div&gt;
&lt;p&gt;The result of all this is a list of binary representations of each hexadecimal
digit:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;table class="highlighttable"&gt;&lt;tr&gt;&lt;td class="linenos"&gt;&lt;div class="linenodiv"&gt;&lt;pre&gt;&lt;span class="normal"&gt; 0&lt;/span&gt;
&lt;span class="normal"&gt; 1&lt;/span&gt;
&lt;span class="normal"&gt; 2&lt;/span&gt;
&lt;span class="normal"&gt; 3&lt;/span&gt;
&lt;span class="normal"&gt; 4&lt;/span&gt;
&lt;span class="normal"&gt; 5&lt;/span&gt;
&lt;span class="normal"&gt; 6&lt;/span&gt;
&lt;span class="normal"&gt; 7&lt;/span&gt;
&lt;span class="normal"&gt; 8&lt;/span&gt;
&lt;span class="normal"&gt; 9&lt;/span&gt;
&lt;span class="normal"&gt;10&lt;/span&gt;
&lt;span class="normal"&gt;11&lt;/span&gt;
&lt;span class="normal"&gt;12&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;div&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;hex_digits&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;218f88a3f546f710c0aabb767316aebd&amp;#39;&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;binary&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;bin&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;digit&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;16&lt;/span&gt;&lt;span class="p"&gt;))[&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;:]&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;zfill&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;4&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;digit&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;hex_digits&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;binary&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="s1"&gt;&amp;#39;0010&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;0001&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;1000&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;1111&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s1"&gt;&amp;#39;1000&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;1000&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;1010&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;0011&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s1"&gt;&amp;#39;1111&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;0101&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;0100&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;0110&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s1"&gt;&amp;#39;1111&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;0111&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;0001&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;0000&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s1"&gt;&amp;#39;1100&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;0000&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;1010&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;1010&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s1"&gt;&amp;#39;1011&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;1011&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;0111&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;0110&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s1"&gt;&amp;#39;0111&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;0011&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;0001&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;0110&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s1"&gt;&amp;#39;1010&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;1110&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;1011&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;1101&amp;#39;&lt;/span&gt;
&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;div class="section" id="day-15-progressbar2"&gt;
&lt;h2&gt;Day 15: progressbar2&lt;/h2&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;Challenge: &lt;a class="reference external" href="http://adventofcode.com/2017/day/15"&gt;Day 15: Dueling Generators&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Solution: &lt;a class="reference external" href="https://github.com/cdubz/advent-of-code-2017/tree/master/15"&gt;advent-of-code-2017/15&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This challenge led me to &lt;a class="reference external" href="https://pypi.python.org/pypi/progressbar2"&gt;progressbar2&lt;/a&gt;, a simple Python utility for quickly
creating text-based progress bars from standard loops. While not terribly
valuable for expanding my Python skills, it is certainly a nice utility to know
about. My original code included:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;table class="highlighttable"&gt;&lt;tr&gt;&lt;td class="linenos"&gt;&lt;div class="linenodiv"&gt;&lt;pre&gt;&lt;span class="normal"&gt;0&lt;/span&gt;
&lt;span class="normal"&gt;1&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;div&gt;&lt;pre&gt;&lt;span&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="nb"&gt;range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;5000000&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="c1"&gt;# do stuff&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/div&gt;
&lt;p&gt;This executes &lt;strong&gt;five million&lt;/strong&gt; loops and, with the actual code, takes a little
over 30 seconds to run. As I was fighting with an odd bug in my code, it
became more and more annoying to stare at a blank terminal for 30+ seconds
while the code executed. During one of those 30 second waits, I went searching
for how to create a progress bar in Python.&lt;/p&gt;
&lt;p&gt;Lucky for me, it is incredibly simple! After installing &lt;a class="reference external" href="https://pypi.python.org/pypi/progressbar2"&gt;progressbar2&lt;/a&gt; via pip
(&lt;tt class="docutils literal"&gt;pip install progressbar2&lt;/tt&gt;), I only had to make a few small changes to get
a result:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;table class="highlighttable"&gt;&lt;tr&gt;&lt;td class="linenos"&gt;&lt;div class="linenodiv"&gt;&lt;pre&gt;&lt;span class="normal"&gt;0&lt;/span&gt;
&lt;span class="normal"&gt;1&lt;/span&gt;
&lt;span class="normal"&gt;2&lt;/span&gt;
&lt;span class="normal"&gt;3&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;div&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;progressbar&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;ProgressBar&lt;/span&gt;
&lt;span class="n"&gt;bar&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ProgressBar&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="n"&gt;bar&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;5000000&lt;/span&gt;&lt;span class="p"&gt;)):&lt;/span&gt;
    &lt;span class="c1"&gt;# do stuff&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/div&gt;
&lt;p&gt;Now, as the code executes, I see a simple text progress bar that updates with
each pass of the loop and doesn't add any significant time to the execution.
Here is an example of the output:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;table class="highlighttable"&gt;&lt;tr&gt;&lt;td class="linenos"&gt;&lt;div class="linenodiv"&gt;&lt;pre&gt;&lt;span class="normal"&gt;0&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;div&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="mi"&gt;36&lt;/span&gt;&lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1815386&lt;/span&gt; &lt;span class="n"&gt;of&lt;/span&gt; &lt;span class="mi"&gt;5000000&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="c1"&gt;##############                          | Elapsed Time: 0:00:12 ETA:  0:00:21&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/div&gt;
&lt;p&gt;I only wish I had made this discovery on &lt;a class="reference external" href="http://adventofcode.com/2017/day/13"&gt;Day 13&lt;/a&gt; (my unoptimized solution
took over an hour to run)...&lt;/p&gt;
&lt;/div&gt;
</content><category term="Technology"></category><category term="advent of code"></category><category term="python"></category></entry><entry><title>Advent of Code 2017: Days 6 - 10</title><link href="https://www.chris-wells.net/articles/2017/12/10/advent-of-code-2017-days-6-10" rel="alternate"></link><published>2017-12-10T12:30:00-04:00</published><updated>2017-12-10T12:30:00-04:00</updated><author><name>Christopher Charbonneau Wells</name></author><id>tag:www.chris-wells.net,2017-12-10:/articles/2017/12/10/advent-of-code-2017-days-6-10</id><summary type="html">
&lt;blockquote class="small"&gt;
    &lt;em&gt;
        This post is part of the series, &lt;a href="/articles/2017/11/30/advent-of-code-2017/"&gt;Advent of Code 2017&lt;/a&gt;,
        where I work my way through (part of) the &lt;a href="http://adventofcode.com/2017"&gt;2017 Advent of Code&lt;/a&gt;
        challenges in order to re-learn some of the basics of Python and
        reflect on some simple concepts and functionality. All my challenge
        code is available in &lt;a href="https://github.com/cdubz/advent-of-code-2017"&gt;cdubz/advent-of-code-2017&lt;/a&gt;
        on GitHub.
    &lt;/em&gt;
&lt;/blockquote&gt;
&lt;h2&gt;Day 6: max(x, key=y)&lt;/h2&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;Challenge: &lt;a class="reference external" href="http://adventofcode.com/2017/day/6"&gt;Day 6: Memory Reallocation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Solution: &lt;a class="reference external" href="https://github.com/cdubz/advent-of-code-2017/tree/master/06"&gt;advent-of-code-2017/06&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Another simple revelation on built-ins from this challenge: the &lt;tt class="docutils literal"&gt;key&lt;/tt&gt; argument
can be used to modify what is evaluated by the &lt;tt class="docutils literal"&gt;max&lt;/tt&gt; function. This concept is
explained in detail as part of the accepted answer to this Stack Overflow post:
&lt;a class="reference external" href="https://stackoverflow.com/a/18296814"&gt;python max function using 'key' and lambda expression&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;For Python dictionaries, this means that the &lt;tt class="docutils literal"&gt;get()&lt;/tt&gt; method can be used to
return the &lt;em&gt;key&lt;/em&gt; of the maximum value in a dictionary. This works because the
&lt;tt class="docutils literal"&gt;max&lt;/tt&gt; function sends each of the &lt;tt class="docutils literal"&gt;dict&lt;/tt&gt; &lt;em&gt;keys&lt;/em&gt; to the &lt;tt class="docutils literal"&gt;dict.get()&lt;/tt&gt; method
and evaluates that result instead of the key itself. Without this argument,
&lt;tt class="docutils literal"&gt;max&lt;/tt&gt; will actually evaluate the dictionary's &lt;em&gt;keys&lt;/em&gt;, which isn't terribly
useful:&lt;/p&gt;

&lt;span class="normal"&gt;1&lt;/span&gt;
&lt;span class="normal"&gt;2&lt;/span&gt;
&lt;span class="normal"&gt;3&lt;/span&gt;
&lt;span class="normal"&gt;4&lt;/span&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class="code"&gt;
&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;max&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;d&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="mi"&gt;3&lt;/span&gt;  &lt;span class="c1"&gt;# This is the *maximum value of the dictionary&amp;#39;s keys*.&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;max&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;d&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;d&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="mi"&gt;2&lt;/span&gt;  &lt;span class="c1"&gt;# This is the *key of the maximum value in the dictionary*.&lt;/span&gt;
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
</summary><content type="html">&lt;span class="target" id="top"&gt;&lt;/span&gt;
&lt;blockquote class="small"&gt;
    &lt;em&gt;
        This post is part of the series, &lt;a href="/articles/2017/11/30/advent-of-code-2017/"&gt;Advent of Code 2017&lt;/a&gt;,
        where I work my way through (part of) the &lt;a href="http://adventofcode.com/2017"&gt;2017 Advent of Code&lt;/a&gt;
        challenges in order to re-learn some of the basics of Python and
        reflect on some simple concepts and functionality. All my challenge
        code is available in &lt;a href="https://github.com/cdubz/advent-of-code-2017"&gt;cdubz/advent-of-code-2017&lt;/a&gt;
        on GitHub.
    &lt;/em&gt;
&lt;/blockquote&gt;&lt;div class="section" id="day-6-max-x-key-y"&gt;
&lt;h2&gt;Day 6: max(x, key=y)&lt;/h2&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;Challenge: &lt;a class="reference external" href="http://adventofcode.com/2017/day/6"&gt;Day 6: Memory Reallocation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Solution: &lt;a class="reference external" href="https://github.com/cdubz/advent-of-code-2017/tree/master/06"&gt;advent-of-code-2017/06&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Another simple revelation on built-ins from this challenge: the &lt;tt class="docutils literal"&gt;key&lt;/tt&gt; argument
can be used to modify what is evaluated by the &lt;tt class="docutils literal"&gt;max&lt;/tt&gt; function. This concept is
explained in detail as part of the accepted answer to this Stack Overflow post:
&lt;a class="reference external" href="https://stackoverflow.com/a/18296814"&gt;python max function using 'key' and lambda expression&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;For Python dictionaries, this means that the &lt;tt class="docutils literal"&gt;get()&lt;/tt&gt; method can be used to
return the &lt;em&gt;key&lt;/em&gt; of the maximum value in a dictionary. This works because the
&lt;tt class="docutils literal"&gt;max&lt;/tt&gt; function sends each of the &lt;tt class="docutils literal"&gt;dict&lt;/tt&gt; &lt;em&gt;keys&lt;/em&gt; to the &lt;tt class="docutils literal"&gt;dict.get()&lt;/tt&gt; method
and evaluates that result instead of the key itself. Without this argument,
&lt;tt class="docutils literal"&gt;max&lt;/tt&gt; will actually evaluate the dictionary's &lt;em&gt;keys&lt;/em&gt;, which isn't terribly
useful:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;table class="highlighttable"&gt;&lt;tr&gt;&lt;td class="linenos"&gt;&lt;div class="linenodiv"&gt;&lt;pre&gt;&lt;span class="normal"&gt;0&lt;/span&gt;
&lt;span class="normal"&gt;1&lt;/span&gt;
&lt;span class="normal"&gt;2&lt;/span&gt;
&lt;span class="normal"&gt;3&lt;/span&gt;
&lt;span class="normal"&gt;4&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;div&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;d&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;max&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;d&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="mi"&gt;3&lt;/span&gt;  &lt;span class="c1"&gt;# This is the *maximum value of the dictionary&amp;#39;s keys*.&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;max&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;d&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;d&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="mi"&gt;2&lt;/span&gt;  &lt;span class="c1"&gt;# This is the *key of the maximum value in the dictionary*.&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/div&gt;

&lt;/div&gt;
&lt;div class="section" id="day-7-runtimeerror-dictionary-changed-size-during-iteration"&gt;
&lt;h2&gt;Day 7: RuntimeError: dictionary changed size during iteration&lt;/h2&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;Challenge: &lt;a class="reference external" href="http://adventofcode.com/2017/day/7"&gt;Day 7: Recursion&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Solution: &lt;a class="reference external" href="https://github.com/cdubz/advent-of-code-2017/tree/master/07"&gt;advent-of-code-2017/07&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;My initial approach to this problem was to organize all &amp;quot;programs&amp;quot; in to a
dictionary and whittle away at any program that wasn't a child of another
program:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;table class="highlighttable"&gt;&lt;tr&gt;&lt;td class="linenos"&gt;&lt;div class="linenodiv"&gt;&lt;pre&gt;&lt;span class="normal"&gt;0&lt;/span&gt;
&lt;span class="normal"&gt;1&lt;/span&gt;
&lt;span class="normal"&gt;2&lt;/span&gt;
&lt;span class="normal"&gt;3&lt;/span&gt;
&lt;span class="normal"&gt;4&lt;/span&gt;
&lt;span class="normal"&gt;5&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;div&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="n"&gt;programs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;&amp;lt;name&amp;gt;&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;weight&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;children&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&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="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;program&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;programs&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;items&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;child&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;children&amp;#39;&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;child&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;programs&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;del&lt;/span&gt; &lt;span class="n"&gt;programs&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;child&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;list&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;programs&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;/pre&gt;&lt;/div&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/div&gt;
&lt;p&gt;The end result should be a single entry in &lt;tt class="docutils literal"&gt;programs&lt;/tt&gt; that does not appear in
the &lt;tt class="docutils literal"&gt;children&lt;/tt&gt; list of any other program - aka the base of the recursion.&lt;/p&gt;
&lt;p&gt;After running this successfully a few times and submitting my answer, I did some
other things before coming back for part two. To my surprise, when I tried to
run the same code again, it produced an error:
&lt;tt class="docutils literal"&gt;RuntimeError: dictionary changed size during iteration&lt;/tt&gt;&lt;/p&gt;
&lt;p&gt;While trying to figure out why this was happening all of a sudden, I realized
that I had, in the course of doing other things, changed to an environment with
Python 3 as the default &lt;tt class="docutils literal"&gt;python&lt;/tt&gt;, whereas my original environment used Python
2.&lt;/p&gt;
&lt;p&gt;This error happens because &lt;tt class="docutils literal"&gt;dict.items()&lt;/tt&gt; is a simple list in Python 2, but it
is a &lt;a class="reference external" href="https://docs.python.org/3.3/library/stdtypes.html#dict-views"&gt;view&lt;/a&gt; in Python 3. If my loop had not been &lt;em&gt;modifying&lt;/em&gt; the contents of
the dictionary, this would not have been a problem because either type can be
iterated in the same way. However, since I was modifying the dictionary in
place, Python 3 raises the &lt;tt class="docutils literal"&gt;RuntimError&lt;/tt&gt; because the actual dictionary object
(not the list copy of its items, as with Python 2) is being modified
&lt;em&gt;while the code is still in the loop&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;The quick fix for this issue is to make a copy of the dictionary and iterate
over that instead. E.g. by changing the for-loop like so:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;table class="highlighttable"&gt;&lt;tr&gt;&lt;td class="linenos"&gt;&lt;div class="linenodiv"&gt;&lt;pre&gt;&lt;span class="normal"&gt;1&lt;/span&gt;
&lt;span class="normal"&gt;2&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;div&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;program&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nb"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;programs&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;items&lt;/span&gt;&lt;span class="p"&gt;():&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;/pre&gt;&lt;/div&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;div class="section" id="day-8-regular-expressions"&gt;
&lt;h2&gt;Day 8: Regular Expressions&lt;/h2&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;Challenge: &lt;a class="reference external" href="http://adventofcode.com/2017/day/8"&gt;Day 8: I Heard You Like Registers&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Solution: &lt;a class="reference external" href="https://github.com/cdubz/advent-of-code-2017/tree/master/08"&gt;advent-of-code-2017/08&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I have used regular expressions in the past two challenges. Every time I need a
bit of regex I think to myself, &amp;quot;I should learn more about this in depth&amp;quot;. I
have yet to scratch that particular itch, but my general knowledge about them
came in handy for this challenge.&lt;/p&gt;
&lt;p&gt;The challenge provides line-by-line &lt;em&gt;string&lt;/em&gt; instructions for modifying values,
like this example (from the challenge):&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;table class="highlighttable"&gt;&lt;tr&gt;&lt;td class="linenos"&gt;&lt;div class="linenodiv"&gt;&lt;pre&gt;&lt;span class="normal"&gt;0&lt;/span&gt;
&lt;span class="normal"&gt;1&lt;/span&gt;
&lt;span class="normal"&gt;2&lt;/span&gt;
&lt;span class="normal"&gt;3&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;div&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="n"&gt;b&lt;/span&gt; &lt;span class="n"&gt;inc&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
&lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="n"&gt;inc&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;
&lt;span class="n"&gt;c&lt;/span&gt; &lt;span class="n"&gt;dec&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
&lt;span class="n"&gt;c&lt;/span&gt; &lt;span class="n"&gt;inc&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;20&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/div&gt;
&lt;p&gt;Looking at this, there are four important pieces of information:&lt;/p&gt;
&lt;ol class="arabic simple"&gt;
&lt;li&gt;The &amp;quot;variable&amp;quot; to be modified (a, b, c, etc...).&lt;/li&gt;
&lt;li&gt;How to modify that variable (&amp;quot;inc&amp;quot; for increase, &amp;quot;dec&amp;quot; for decrease).&lt;/li&gt;
&lt;li&gt;The amount to modify the variable by.&lt;/li&gt;
&lt;li&gt;An expression to decide whether or not to perform the actual modification.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Regular expressions to the rescue! The first thing I tend to do when trying to
write a regular expression is pull up one of the many useful websites that will
evaluate expressions in real time. In this case I used &lt;a class="reference external" href="https://regex101.com/"&gt;regex101.com&lt;/a&gt;, which
will take the text to be searched, highlight the relevant parts, and provide
explanations &lt;em&gt;as you type&lt;/em&gt; the expression. I ended up with the following
expression for the challenge:&lt;/p&gt;
&lt;p&gt;&lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;([a-z]+)&lt;/span&gt; (inc|dec) &lt;span class="pre"&gt;(-?\d+)&lt;/span&gt; if &lt;span class="pre"&gt;([a-z]+)&lt;/span&gt; &lt;span class="pre"&gt;(.+)&lt;/span&gt; &lt;span class="pre"&gt;(-?\d+)&lt;/span&gt;&lt;/tt&gt;&lt;/p&gt;
&lt;p&gt;This regex, the example input, and all of the useful context can be viewed at
the following URL: &lt;a class="reference external" href="https://regex101.com/r/O3xLTL/2"&gt;https://regex101.com/r/O3xLTL/2&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Using Python's &lt;tt class="docutils literal"&gt;re&lt;/tt&gt; module and some small modifications to the expression, the
relevant capture groups can be organized in to more descriptive variables:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;table class="highlighttable"&gt;&lt;tr&gt;&lt;td class="linenos"&gt;&lt;div class="linenodiv"&gt;&lt;pre&gt;&lt;span class="normal"&gt;0&lt;/span&gt;
&lt;span class="normal"&gt;1&lt;/span&gt;
&lt;span class="normal"&gt;2&lt;/span&gt;
&lt;span class="normal"&gt;3&lt;/span&gt;
&lt;span class="normal"&gt;4&lt;/span&gt;
&lt;span class="normal"&gt;5&lt;/span&gt;
&lt;span class="normal"&gt;6&lt;/span&gt;
&lt;span class="normal"&gt;7&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;div&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;re&lt;/span&gt;

&lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="nb"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;input.txt&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;line&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;re&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;search&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;(?P&amp;lt;register&amp;gt;[a-z]+) (?P&amp;lt;op&amp;gt;inc|dec) (?P&amp;lt;amt&amp;gt;-?\d+) &amp;#39;&lt;/span&gt;
                           &lt;span class="s1"&gt;&amp;#39;if (?P&amp;lt;cond_register&amp;gt;[a-z]+) (?P&amp;lt;cond&amp;gt;.+) &amp;#39;&lt;/span&gt;
                           &lt;span class="s1"&gt;&amp;#39;(?P&amp;lt;cond_amt&amp;gt;-?\d+)&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;line&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;strip&lt;/span&gt;&lt;span class="p"&gt;())&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;/pre&gt;&lt;/div&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/div&gt;
&lt;p&gt;Python's &lt;tt class="docutils literal"&gt;re.search&lt;/tt&gt; uses the syntax &lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;(?P=&amp;lt;name&amp;gt;[expression])&lt;/span&gt;&lt;/tt&gt; to enable
this grouping. &lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;result.group('register')&lt;/span&gt;&lt;/tt&gt; run on the first line produces &amp;quot;b&amp;quot;,
&lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;result.group('cond')&lt;/span&gt;&lt;/tt&gt; run on the second line produces &amp;quot;&amp;lt;&amp;quot;, and so on. This
grouping further enhances the usefulness of regular expressions and makes
building expressions (and evaluating functionality in general) a lot of fun.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="day-9-lookahead-lookbehind"&gt;
&lt;h2&gt;Day 9: Lookahead, Lookbehind&lt;/h2&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;Challenge: &lt;a class="reference external" href="http://adventofcode.com/2017/day/9"&gt;Day 9: Stream Processing&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Solution: &lt;a class="reference external" href="https://github.com/cdubz/advent-of-code-2017/tree/master/09"&gt;advent-of-code-2017/09&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Regular expressions to the rescue again! Part one of this challenge was fairly
straightforward and easy to complete with two passes of expressions to remove
characters from a long string.&lt;/p&gt;
&lt;p&gt;Part two was a bit more difficult because it was necessary to &lt;em&gt;count&lt;/em&gt; the
removed characters. I wanted to build on part one's character removal by
comparing the string length before and after instead of trying to significantly
modify the expression to use (and add up) grouping. The problem with this
approach is that it requires matching strings that &lt;em&gt;shouldn't&lt;/em&gt; be included in
the count of removed characters because the task is to remove characters
between brackets (&amp;lt; and &amp;gt;):&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;table class="highlighttable"&gt;&lt;tr&gt;&lt;td class="linenos"&gt;&lt;div class="linenodiv"&gt;&lt;pre&gt;&lt;span class="normal"&gt;0&lt;/span&gt;
&lt;span class="normal"&gt;1&lt;/span&gt;
&lt;span class="normal"&gt;2&lt;/span&gt;
&lt;span class="normal"&gt;3&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;div&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;re&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;string&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;&amp;lt;characters&amp;gt;&amp;#39;&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;re&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sub&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;r&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&amp;lt;[^&amp;gt;]+&amp;gt;&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;string&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
   &lt;span class="c1"&gt;# (an empty string)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/div&gt;
&lt;p&gt;How can the expression &lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;&amp;lt;[^&amp;gt;]+&amp;gt;&lt;/span&gt;&lt;/tt&gt; be changed to capture what is inside the
brackets, but not the brackets themselves? With &lt;a class="reference external" href="https://www.regular-expressions.info/lookaround.html"&gt;lookaround assertions&lt;/a&gt;! A
&lt;strong&gt;lookahead&lt;/strong&gt; assertion has the format &lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;(?=...)&lt;/span&gt;&lt;/tt&gt; for positive assertions and
&lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;(?!...)&lt;/span&gt;&lt;/tt&gt; for negative assertions and a &lt;strong&gt;lookbehind&lt;/strong&gt; uses &lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;(?&amp;lt;=...)&lt;/span&gt;&lt;/tt&gt; and
&lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;(?&amp;lt;!...)&lt;/span&gt;&lt;/tt&gt; respectively.&lt;/p&gt;
&lt;p&gt;Replacing the surrounding brackets with a single lookbehind solves the problem
of capturing the brackets:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;table class="highlighttable"&gt;&lt;tr&gt;&lt;td class="linenos"&gt;&lt;div class="linenodiv"&gt;&lt;pre&gt;&lt;span class="normal"&gt;0&lt;/span&gt;
&lt;span class="normal"&gt;1&lt;/span&gt;
&lt;span class="normal"&gt;2&lt;/span&gt;
&lt;span class="normal"&gt;3&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;div&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;re&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;string&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;&amp;lt;characters&amp;gt;&amp;#39;&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;re&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sub&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;r&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;(?&amp;lt;=&amp;lt;)[^&amp;gt;]+&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;string&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="o"&gt;&amp;lt;&amp;gt;&lt;/span&gt;  &lt;span class="c1"&gt;# only the brackets remain&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/div&gt;
&lt;p&gt;Now the expression produces the desired effect - it removes all characters
inside brackets, but not brackets themselves. This works because the positive
lookbehind assertion (&lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;(?&amp;lt;=&amp;lt;)&lt;/span&gt;&lt;/tt&gt;) ensures that the pattern matches, but does
not actually match it. The remaining portion of the expression (&lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;[^&amp;gt;]+&lt;/span&gt;&lt;/tt&gt;)
does the actual matching of any character other than &lt;tt class="docutils literal"&gt;&amp;gt;&lt;/tt&gt; (aka, everything
&lt;em&gt;inside&lt;/em&gt; the brackets!).&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="day-10-functools-reduce"&gt;
&lt;h2&gt;Day 10: functools.reduce()&lt;/h2&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;Challenge: &lt;a class="reference external" href="http://adventofcode.com/2017/day/10"&gt;Day 10: Knot Hash&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Solution: &lt;a class="reference external" href="https://github.com/cdubz/advent-of-code-2017/tree/master/10"&gt;advent-of-code-2017/10&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Part two of this challenge dealt in part with performing operations on each
pair of two items &lt;em&gt;cumulatively&lt;/em&gt; in a list of items. For example, multiplying
all values in the list &lt;tt class="docutils literal"&gt;[1, 2, 3, 4, 5]&lt;/tt&gt; should yield 120 because 1 * 2 = 2 *
3 = 6 * 4 = 24 * 5 = 120. An easy way to do this in Python is to iterate the
list and deal with each element one-by-one:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;table class="highlighttable"&gt;&lt;tr&gt;&lt;td class="linenos"&gt;&lt;div class="linenodiv"&gt;&lt;pre&gt;&lt;span class="normal"&gt;0&lt;/span&gt;
&lt;span class="normal"&gt;1&lt;/span&gt;
&lt;span class="normal"&gt;2&lt;/span&gt;
&lt;span class="normal"&gt;3&lt;/span&gt;
&lt;span class="normal"&gt;4&lt;/span&gt;
&lt;span class="normal"&gt;5&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;div&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;l&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;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;l&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;number&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;l&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;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;number&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&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;120&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/div&gt;
&lt;p&gt;While this method certainly is not complicated, the code would have to be
modified to work with other iterables and in many cases may not be so clean. As
an alternative, &lt;a class="reference external" href="https://docs.python.org/3.6/library/functools.html?#functools.reduce"&gt;functools.reduce()&lt;/a&gt; can perform the same action in a single
line, on &lt;em&gt;any iterable&lt;/em&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;table class="highlighttable"&gt;&lt;tr&gt;&lt;td class="linenos"&gt;&lt;div class="linenodiv"&gt;&lt;pre&gt;&lt;span class="normal"&gt;0&lt;/span&gt;
&lt;span class="normal"&gt;1&lt;/span&gt;
&lt;span class="normal"&gt;2&lt;/span&gt;
&lt;span class="normal"&gt;3&lt;/span&gt;
&lt;span class="normal"&gt;4&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;div&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;functools&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;reduce&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;operator&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;mul&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;l&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;reduce&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;mul&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;l&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="mi"&gt;120&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/div&gt;
&lt;p&gt;This example uses &lt;a class="reference external" href="https://docs.python.org/3.6/library/operator.html?#operator.mul"&gt;operator.mul&lt;/a&gt; for a simple multiplication function. But
&lt;tt class="docutils literal"&gt;reduce()&lt;/tt&gt; will work with any function that accepts two arguments (including
lambdas!):&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;table class="highlighttable"&gt;&lt;tr&gt;&lt;td class="linenos"&gt;&lt;div class="linenodiv"&gt;&lt;pre&gt;&lt;span class="normal"&gt;0&lt;/span&gt;
&lt;span class="normal"&gt;1&lt;/span&gt;
&lt;span class="normal"&gt;2&lt;/span&gt;
&lt;span class="normal"&gt;3&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;div&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;functools&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;reduce&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;l&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;reduce&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;lambda&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;j&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;j&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;l&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="mi"&gt;120&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/div&gt;
&lt;/div&gt;
</content><category term="Technology"></category><category term="advent of code"></category><category term="python"></category></entry><entry><title>Advent of Code 2017: Days 1 - 5</title><link href="https://www.chris-wells.net/articles/2017/12/05/advent-of-code-2017-days-1-5" rel="alternate"></link><published>2017-12-05T10:00:00-04:00</published><updated>2017-12-05T10:00:00-04:00</updated><author><name>Christopher Charbonneau Wells</name></author><id>tag:www.chris-wells.net,2017-12-05:/articles/2017/12/05/advent-of-code-2017-days-1-5</id><summary type="html">
&lt;blockquote class="small"&gt;
    &lt;em&gt;
        This post is part of the series, &lt;a href="/articles/2017/11/30/advent-of-code-2017/"&gt;Advent of Code 2017&lt;/a&gt;,
        where I work my way through (part of) the &lt;a href="http://adventofcode.com/2017"&gt;2017 Advent of Code&lt;/a&gt;
        challenges in order to re-learn some of the basics of Python and
        reflect on some simple concepts and functionality. All my challenge
        code is available in &lt;a href="https://github.com/cdubz/advent-of-code-2017"&gt;cdubz/advent-of-code-2017&lt;/a&gt;
        on GitHub.
    &lt;/em&gt;
&lt;/blockquote&gt;
&lt;h2&gt;Day 1: zip()&lt;/h2&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;Challenge: &lt;a class="reference external" href="http://adventofcode.com/2017/day/1"&gt;Day 1: Inverse Captcha&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Solution: &lt;a class="reference external" href="https://github.com/cdubz/advent-of-code-2017/tree/master/01"&gt;advent-of-code-2017/01&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This challenge taught me about Python's built-in &lt;a class="reference external" href="https://docs.python.org/3.6/library/functions.html#zip"&gt;zip&lt;/a&gt; function. The basic
goal is to compare values in a list with specific positional relationships to
other values (e.g. next to, X steps from, etc.) in the same list. &lt;tt class="docutils literal"&gt;zip&lt;/tt&gt;
assists with this task by combining multiple lists in to tuples. My initial
solution used a construct similar to:&lt;/p&gt;

&lt;span class="normal"&gt;1&lt;/span&gt;
&lt;span class="normal"&gt;2&lt;/span&gt;
&lt;span class="normal"&gt;3&lt;/span&gt;
&lt;span class="normal"&gt;4&lt;/span&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class="code"&gt;
&lt;span class="n"&gt;total&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nb"&gt;zip&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;digits&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;digits&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;1&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;a&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;total&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt;
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
</summary><content type="html">&lt;span class="target" id="top"&gt;&lt;/span&gt;
&lt;blockquote class="small"&gt;
    &lt;em&gt;
        This post is part of the series, &lt;a href="/articles/2017/11/30/advent-of-code-2017/"&gt;Advent of Code 2017&lt;/a&gt;,
        where I work my way through (part of) the &lt;a href="http://adventofcode.com/2017"&gt;2017 Advent of Code&lt;/a&gt;
        challenges in order to re-learn some of the basics of Python and
        reflect on some simple concepts and functionality. All my challenge
        code is available in &lt;a href="https://github.com/cdubz/advent-of-code-2017"&gt;cdubz/advent-of-code-2017&lt;/a&gt;
        on GitHub.
    &lt;/em&gt;
&lt;/blockquote&gt;&lt;div class="section" id="day-1-zip"&gt;
&lt;h2&gt;Day 1: zip()&lt;/h2&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;Challenge: &lt;a class="reference external" href="http://adventofcode.com/2017/day/1"&gt;Day 1: Inverse Captcha&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Solution: &lt;a class="reference external" href="https://github.com/cdubz/advent-of-code-2017/tree/master/01"&gt;advent-of-code-2017/01&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This challenge taught me about Python's built-in &lt;a class="reference external" href="https://docs.python.org/3.6/library/functions.html#zip"&gt;zip&lt;/a&gt; function. The basic
goal is to compare values in a list with specific positional relationships to
other values (e.g. next to, X steps from, etc.) in the same list. &lt;tt class="docutils literal"&gt;zip&lt;/tt&gt;
assists with this task by combining multiple lists in to tuples. My initial
solution used a construct similar to:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;table class="highlighttable"&gt;&lt;tr&gt;&lt;td class="linenos"&gt;&lt;div class="linenodiv"&gt;&lt;pre&gt;&lt;span class="normal"&gt;0&lt;/span&gt;
&lt;span class="normal"&gt;1&lt;/span&gt;
&lt;span class="normal"&gt;2&lt;/span&gt;
&lt;span class="normal"&gt;3&lt;/span&gt;
&lt;span class="normal"&gt;4&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;div&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="n"&gt;digits&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="n"&gt;total&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nb"&gt;zip&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;digits&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;digits&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;1&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;a&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;total&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;This code uses &lt;tt class="docutils literal"&gt;zip&lt;/tt&gt; and Python's list indexing to evaluate each element in
the &lt;tt class="docutils literal"&gt;digits&lt;/tt&gt; list against the next element in the list by creating and
combining two lists: one starting from the first digit in &lt;tt class="docutils literal"&gt;digits&lt;/tt&gt; and one
starting from the second digital in &lt;tt class="docutils literal"&gt;digits&lt;/tt&gt;. Here are what the different
parts of this process look like:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;table class="highlighttable"&gt;&lt;tr&gt;&lt;td class="linenos"&gt;&lt;div class="linenodiv"&gt;&lt;pre&gt;&lt;span class="normal"&gt;0&lt;/span&gt;
&lt;span class="normal"&gt;1&lt;/span&gt;
&lt;span class="normal"&gt;2&lt;/span&gt;
&lt;span class="normal"&gt;3&lt;/span&gt;
&lt;span class="normal"&gt;4&lt;/span&gt;
&lt;span class="normal"&gt;5&lt;/span&gt;
&lt;span class="normal"&gt;6&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;div&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;digits&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;digits&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="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;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;digits&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;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;list&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;zip&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;digits&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;digits&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;1&lt;/span&gt;&lt;span class="p"&gt;]))&lt;/span&gt;
&lt;span class="p"&gt;[(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;div class="section" id="day-2-itertools-combinations"&gt;
&lt;h2&gt;Day 2: itertools.combinations()&lt;/h2&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;Challenge: &lt;a class="reference external" href="http://adventofcode.com/2017/day/2"&gt;Day 2: Corruption Checksum&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Solution: &lt;a class="reference external" href="https://github.com/cdubz/advent-of-code-2017/tree/master/02"&gt;advent-of-code-2017/02&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I have been a bit slow to pick up on Python's powerful iteration tools
(&lt;a class="reference external" href="https://docs.python.org/3.6/library/itertools.html"&gt;itertools&lt;/a&gt;) so it took me a while (and a bit of searching, of course) to come
up with a solution for this challenge. The basic goal is to evaluate all
possible combinations of integers in a list. While I had some vague
understanding of an efficient way to do this, my initial solution was extremely
basic and &lt;em&gt;inefficient&lt;/em&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;table class="highlighttable"&gt;&lt;tr&gt;&lt;td class="linenos"&gt;&lt;div class="linenodiv"&gt;&lt;pre&gt;&lt;span class="normal"&gt;0&lt;/span&gt;
&lt;span class="normal"&gt;1&lt;/span&gt;
&lt;span class="normal"&gt;2&lt;/span&gt;
&lt;span class="normal"&gt;3&lt;/span&gt;
&lt;span class="normal"&gt;4&lt;/span&gt;
&lt;span class="normal"&gt;5&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;div&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="n"&gt;digits&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;9&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="n"&gt;total&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&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="n"&gt;digits&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;j&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;digits&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;i&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="n"&gt;j&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="n"&gt;j&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;total&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;j&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/div&gt;
&lt;p&gt;This code has two major issues:&lt;/p&gt;
&lt;p&gt;1. It compares every number against every other number, regardless of whether
or not those numbers have already been compared. In this case, 2 is compared to
4 and 4 is compared to 2 - two comparisons where only one is needed. This issue
will compound and cause greater inefficiencies with a larger list.&lt;/p&gt;
&lt;p&gt;1. It assumes that no digit will be repeated. With, for example,
&lt;tt class="docutils literal"&gt;digits = [2, 2, 3]&lt;/tt&gt;, the two initial values would never be evaluated because
they are equal. While this wouldn't actually cause any problems with the
original list provided, it is a pretty silly way to do things.&lt;/p&gt;
&lt;p&gt;&lt;a class="reference external" href="https://docs.python.org/3.6/library/itertools.html#itertools.combinations"&gt;itertools.combinations&lt;/a&gt; can make this process more efficient without creating
particularly complex code. This method takes a list and returns all possible
&lt;em&gt;unique&lt;/em&gt; combinations based on element &lt;em&gt;position&lt;/em&gt; instead of value - meaning it
solves both of the above issues. A refactoring of the above code using this
method looks like this:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;table class="highlighttable"&gt;&lt;tr&gt;&lt;td class="linenos"&gt;&lt;div class="linenodiv"&gt;&lt;pre&gt;&lt;span class="normal"&gt;0&lt;/span&gt;
&lt;span class="normal"&gt;1&lt;/span&gt;
&lt;span class="normal"&gt;2&lt;/span&gt;
&lt;span class="normal"&gt;3&lt;/span&gt;
&lt;span class="normal"&gt;4&lt;/span&gt;
&lt;span class="normal"&gt;5&lt;/span&gt;
&lt;span class="normal"&gt;6&lt;/span&gt;
&lt;span class="normal"&gt;7&lt;/span&gt;
&lt;span class="normal"&gt;8&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;div&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;itertools&lt;/span&gt;

&lt;span class="n"&gt;digits&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;9&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="n"&gt;total&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
&lt;span class="k"&gt;for&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;j&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;itertools&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;combinations&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;digits&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&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;i&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="n"&gt;j&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;total&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;j&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;j&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;total&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="n"&gt;j&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/div&gt;
&lt;p&gt;Now, instead of a nested loop over the same list, there is only a single loop
that evaluates list elements based on position instead of value.&lt;/p&gt;
&lt;p&gt;This was also a valuable reminder about &lt;a class="reference external" href="https://en.wikipedia.org/wiki/Big_O_notation"&gt;big O notation&lt;/a&gt;. The original code
above is O(N^2), meaning it grows in complexity exponentially as the size of
the list increases. The revised code, on the other hand, is O(N), so its
complexity is only linearly related to the size of the list.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="day-3-manhattan-distance"&gt;
&lt;h2&gt;Day 3: Manhattan Distance&lt;/h2&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;Challenge: &lt;a class="reference external" href="http://adventofcode.com/2017/day/3"&gt;Day 3: Spiral Memory&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Solution: &lt;a class="reference external" href="https://github.com/cdubz/advent-of-code-2017/tree/master/03"&gt;advent-of-code-2017/03&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;When I originally started this challenge I did not realize that
&lt;a class="reference external" href="https://en.wikipedia.org/wikti/Taxicab_geometry"&gt;Manhattan Distance&lt;/a&gt; was a link in the prompt. I had never heard this term
before and a StackExchange question,
&lt;a class="reference external" href="https://math.stackexchange.com/questions/139600/how-to-calculate-the-euclidean-and-manhattan-distance"&gt;How to calculate the Euclidean and Manhattan distance?&lt;/a&gt;, clued me in to the
process for calculating the distance in a matrix. I thought about involving
&lt;a class="reference external" href="http://www.numpy.org/"&gt;numpy&lt;/a&gt; to make things easier on myself, but decided to try solving it first
without any external libraries.&lt;/p&gt;
&lt;p&gt;The solution to this problem is very simple once the (x,y) coordinates of the
number being evaluated are known (because it is being compared to 0,0). To do
this I basically just brute-forced my way through creating a dictionary full of
dictionaries, one for each column of the matrix, by checking for existing data
in the N, S, E, and W directions of the &amp;quot;2D&amp;quot; array. With the matrix built, the
solution is calculated by using the &lt;tt class="docutils literal"&gt;abs(x2 - x1) - abs(y2 - y1)&lt;/tt&gt; formula.&lt;/p&gt;
&lt;p&gt;Completing this challenge was mostly just a reminder that I need to dive in to
numpy eventually. My solution is fairly clean, but horribly inefficient and I'm
sure it could be improved upon greatly.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="day-4-set"&gt;
&lt;h2&gt;Day 4: set()&lt;/h2&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;Challenge: &lt;a class="reference external" href="http://adventofcode.com/2017/day/4"&gt;Day 4: High-Entropy Passphrases&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Solution: &lt;a class="reference external" href="https://github.com/cdubz/advent-of-code-2017/tree/master/04"&gt;advent-of-code-2017/04&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This challenge was another reminder about a simple and useful Python built-in
type: &lt;a class="reference external" href="https://docs.python.org/3.6/library/stdtypes.html#set-types-set-frozenset"&gt;set()&lt;/a&gt;. &lt;tt class="docutils literal"&gt;set()&lt;/tt&gt; can be used to quickly determine if a list has
duplicate items by comparing the length of the original list and its set
because a set is a collection of &lt;em&gt;distinct&lt;/em&gt; items:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;table class="highlighttable"&gt;&lt;tr&gt;&lt;td class="linenos"&gt;&lt;div class="linenodiv"&gt;&lt;pre&gt;&lt;span class="normal"&gt;0&lt;/span&gt;
&lt;span class="normal"&gt;1&lt;/span&gt;
&lt;span class="normal"&gt;2&lt;/span&gt;
&lt;span class="normal"&gt;3&lt;/span&gt;
&lt;span class="normal"&gt;4&lt;/span&gt;
&lt;span class="normal"&gt;5&lt;/span&gt;
&lt;span class="normal"&gt;6&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;div&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;digits&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;digits&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;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;digits&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;digits&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;digits&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="kc"&gt;False&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/div&gt;
&lt;p&gt;Note that &lt;tt class="docutils literal"&gt;set&lt;/tt&gt; is a class separate from &lt;tt class="docutils literal"&gt;list&lt;/tt&gt; and &lt;tt class="docutils literal"&gt;dict&lt;/tt&gt;. I originally
attempted to check for &lt;em&gt;equality&lt;/em&gt; between a &lt;tt class="docutils literal"&gt;list&lt;/tt&gt; and &lt;tt class="docutils literal"&gt;set&lt;/tt&gt; but this did
not work, even with elements in the same order, because the two classes are not
the same:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;table class="highlighttable"&gt;&lt;tr&gt;&lt;td class="linenos"&gt;&lt;div class="linenodiv"&gt;&lt;pre&gt;&lt;span class="normal"&gt; 0&lt;/span&gt;
&lt;span class="normal"&gt; 1&lt;/span&gt;
&lt;span class="normal"&gt; 2&lt;/span&gt;
&lt;span class="normal"&gt; 3&lt;/span&gt;
&lt;span class="normal"&gt; 4&lt;/span&gt;
&lt;span class="normal"&gt; 5&lt;/span&gt;
&lt;span class="normal"&gt; 6&lt;/span&gt;
&lt;span class="normal"&gt; 7&lt;/span&gt;
&lt;span class="normal"&gt; 8&lt;/span&gt;
&lt;span class="normal"&gt; 9&lt;/span&gt;
&lt;span class="normal"&gt;10&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;div&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;list1&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;list2&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;list1&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;list2&lt;/span&gt;
&lt;span class="kc"&gt;True&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;list1&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;set1&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;list1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;set1&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;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;list1&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;set1&lt;/span&gt;
&lt;span class="kc"&gt;False&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;div class="section" id="day-5-profiling"&gt;
&lt;h2&gt;Day 5: Profiling&lt;/h2&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;Challenge: &lt;a class="reference external" href="http://adventofcode.com/2017/day/5"&gt;Day 5: A Maze of Twisty Trampolines, All Alike&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Solution: &lt;a class="reference external" href="https://github.com/cdubz/advent-of-code-2017/tree/master/05"&gt;advent-of-code-2017/05&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The challenge of jumping around a &lt;tt class="docutils literal"&gt;dict&lt;/tt&gt; and updating its values was fairly
simple. The execution of the script for part one took no time at all, but part
two complicated the task and running the script ends up taking 15 - 20 seconds
or more (on my very old laptop).&lt;/p&gt;
&lt;p&gt;Curious to see if there was anything I could do to improve this, I first turned
to Python's built-in profiler &lt;a class="reference external" href="https://docs.python.org/3.6/library/profile.html"&gt;cProfile&lt;/a&gt; by running the suggested
&lt;tt class="docutils literal"&gt;python &lt;span class="pre"&gt;-m&lt;/span&gt; cProfile &amp;lt;script&amp;gt;&lt;/tt&gt; command:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;table class="highlighttable"&gt;&lt;tr&gt;&lt;td class="linenos"&gt;&lt;div class="linenodiv"&gt;&lt;pre&gt;&lt;span class="normal"&gt; 0&lt;/span&gt;
&lt;span class="normal"&gt; 1&lt;/span&gt;
&lt;span class="normal"&gt; 2&lt;/span&gt;
&lt;span class="normal"&gt; 3&lt;/span&gt;
&lt;span class="normal"&gt; 4&lt;/span&gt;
&lt;span class="normal"&gt; 5&lt;/span&gt;
&lt;span class="normal"&gt; 6&lt;/span&gt;
&lt;span class="normal"&gt; 7&lt;/span&gt;
&lt;span class="normal"&gt; 8&lt;/span&gt;
&lt;span class="normal"&gt; 9&lt;/span&gt;
&lt;span class="normal"&gt;10&lt;/span&gt;
&lt;span class="normal"&gt;11&lt;/span&gt;
&lt;span class="normal"&gt;12&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;div&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="n"&gt;python&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;m&lt;/span&gt; &lt;span class="n"&gt;cProfile&lt;/span&gt; &lt;span class="n"&gt;maze_part_two&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;py&lt;/span&gt;
&lt;span class="mi"&gt;26395586&lt;/span&gt;
     &lt;span class="mi"&gt;6&lt;/span&gt; &lt;span class="n"&gt;function&lt;/span&gt; &lt;span class="n"&gt;calls&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="mf"&gt;18.451&lt;/span&gt; &lt;span class="n"&gt;seconds&lt;/span&gt;

&lt;span class="n"&gt;Ordered&lt;/span&gt; &lt;span class="n"&gt;by&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;standard&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt;

&lt;span class="n"&gt;ncalls&lt;/span&gt;  &lt;span class="n"&gt;tottime&lt;/span&gt;  &lt;span class="n"&gt;percall&lt;/span&gt;  &lt;span class="n"&gt;cumtime&lt;/span&gt;  &lt;span class="n"&gt;percall&lt;/span&gt; &lt;span class="n"&gt;filename&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;lineno&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;function&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="mi"&gt;1&lt;/span&gt;   &lt;span class="mf"&gt;18.450&lt;/span&gt;   &lt;span class="mf"&gt;18.450&lt;/span&gt;   &lt;span class="mf"&gt;18.451&lt;/span&gt;   &lt;span class="mf"&gt;18.451&lt;/span&gt; &lt;span class="n"&gt;maze_part_two&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;py&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;11&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;module&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="mi"&gt;1&lt;/span&gt;    &lt;span class="mf"&gt;0.001&lt;/span&gt;    &lt;span class="mf"&gt;0.001&lt;/span&gt;    &lt;span class="mf"&gt;0.001&lt;/span&gt;    &lt;span class="mf"&gt;0.001&lt;/span&gt; &lt;span class="n"&gt;maze_part_two&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;py&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;12&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;dictcomp&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="mi"&gt;1&lt;/span&gt;    &lt;span class="mf"&gt;0.000&lt;/span&gt;    &lt;span class="mf"&gt;0.000&lt;/span&gt;    &lt;span class="mf"&gt;0.000&lt;/span&gt;    &lt;span class="mf"&gt;0.000&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;method&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;disable&amp;#39;&lt;/span&gt; &lt;span class="n"&gt;of&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;_lsprof.Profiler&amp;#39;&lt;/span&gt; &lt;span class="n"&gt;objects&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="mi"&gt;1&lt;/span&gt;    &lt;span class="mf"&gt;0.000&lt;/span&gt;    &lt;span class="mf"&gt;0.000&lt;/span&gt;    &lt;span class="mf"&gt;0.000&lt;/span&gt;    &lt;span class="mf"&gt;0.000&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;method&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;read&amp;#39;&lt;/span&gt; &lt;span class="n"&gt;of&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;file&amp;#39;&lt;/span&gt; &lt;span class="n"&gt;objects&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="mi"&gt;1&lt;/span&gt;    &lt;span class="mf"&gt;0.000&lt;/span&gt;    &lt;span class="mf"&gt;0.000&lt;/span&gt;    &lt;span class="mf"&gt;0.000&lt;/span&gt;    &lt;span class="mf"&gt;0.000&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;method&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;split&amp;#39;&lt;/span&gt; &lt;span class="n"&gt;of&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;str&amp;#39;&lt;/span&gt; &lt;span class="n"&gt;objects&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="mi"&gt;1&lt;/span&gt;    &lt;span class="mf"&gt;0.000&lt;/span&gt;    &lt;span class="mf"&gt;0.000&lt;/span&gt;    &lt;span class="mf"&gt;0.000&lt;/span&gt;    &lt;span class="mf"&gt;0.000&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nb"&gt;open&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/div&gt;
&lt;p&gt;This didn't tell me much of anything because cProfile works at the &lt;strong&gt;function&lt;/strong&gt;
level. Since my solution is a simple script that does not use any functions, all
this really says is: &amp;quot;the script took 18.451 seconds to run&amp;quot;. This would
definitely be a very useful tool to quickly identify problem areas in a larger
code base, but is not helpful here.&lt;/p&gt;
&lt;p&gt;To find more detail, I turned to the &lt;a class="reference external" href="https://pypi.python.org/pypi/line_profiler"&gt;line_profiler&lt;/a&gt; module. As the name
implies, line_profiler will provide line-by-line profiling for a Python script.
I still had to refactor the code slightly to put it in a function that would
allow me to use line_profiler's decorator:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;table class="highlighttable"&gt;&lt;tr&gt;&lt;td class="linenos"&gt;&lt;div class="linenodiv"&gt;&lt;pre&gt;&lt;span class="normal"&gt;0&lt;/span&gt;
&lt;span class="normal"&gt;1&lt;/span&gt;
&lt;span class="normal"&gt;2&lt;/span&gt;
&lt;span class="normal"&gt;3&lt;/span&gt;
&lt;span class="normal"&gt;4&lt;/span&gt;
&lt;span class="normal"&gt;5&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;div&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="nd"&gt;@profile&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="p"&gt;[&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="vm"&gt;__name__&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;__main__&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;run&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/div&gt;
&lt;p&gt;The &lt;tt class="docutils literal"&gt;&amp;#64;profile&lt;/tt&gt; decorator is what tells line_profiler to take a closer look at
the function. The evaluation is a two step process - the &lt;tt class="docutils literal"&gt;kernprof&lt;/tt&gt; command
generates an &lt;tt class="docutils literal"&gt;lprof&lt;/tt&gt; file that line_profiler can then translate in to useful
output:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;table class="highlighttable"&gt;&lt;tr&gt;&lt;td class="linenos"&gt;&lt;div class="linenodiv"&gt;&lt;pre&gt;&lt;span class="normal"&gt;0&lt;/span&gt;
&lt;span class="normal"&gt;1&lt;/span&gt;
&lt;span class="normal"&gt;2&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;div&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="n"&gt;kernprof&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;l&lt;/span&gt; &lt;span class="n"&gt;maze_part_two&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;py&lt;/span&gt;
&lt;span class="mi"&gt;26395586&lt;/span&gt;
&lt;span class="n"&gt;Wrote&lt;/span&gt; &lt;span class="n"&gt;profile&lt;/span&gt; &lt;span class="n"&gt;results&lt;/span&gt; &lt;span class="n"&gt;to&lt;/span&gt; &lt;span class="n"&gt;maze_part_two&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;py&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;lprof&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/div&gt;
&lt;p&gt;This took about three minutes to run because of the detailed evaluation. Next,
line_profiler can translate the &lt;tt class="docutils literal"&gt;lprof&lt;/tt&gt; file using python's &lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;-m&lt;/span&gt;&lt;/tt&gt; flag:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;table class="highlighttable"&gt;&lt;tr&gt;&lt;td class="linenos"&gt;&lt;div class="linenodiv"&gt;&lt;pre&gt;&lt;span class="normal"&gt; 0&lt;/span&gt;
&lt;span class="normal"&gt; 1&lt;/span&gt;
&lt;span class="normal"&gt; 2&lt;/span&gt;
&lt;span class="normal"&gt; 3&lt;/span&gt;
&lt;span class="normal"&gt; 4&lt;/span&gt;
&lt;span class="normal"&gt; 5&lt;/span&gt;
&lt;span class="normal"&gt; 6&lt;/span&gt;
&lt;span class="normal"&gt; 7&lt;/span&gt;
&lt;span class="normal"&gt; 8&lt;/span&gt;
&lt;span class="normal"&gt; 9&lt;/span&gt;
&lt;span class="normal"&gt;10&lt;/span&gt;
&lt;span class="normal"&gt;11&lt;/span&gt;
&lt;span class="normal"&gt;12&lt;/span&gt;
&lt;span class="normal"&gt;13&lt;/span&gt;
&lt;span class="normal"&gt;14&lt;/span&gt;
&lt;span class="normal"&gt;15&lt;/span&gt;
&lt;span class="normal"&gt;16&lt;/span&gt;
&lt;span class="normal"&gt;17&lt;/span&gt;
&lt;span class="normal"&gt;18&lt;/span&gt;
&lt;span class="normal"&gt;19&lt;/span&gt;
&lt;span class="normal"&gt;20&lt;/span&gt;
&lt;span class="normal"&gt;21&lt;/span&gt;
&lt;span class="normal"&gt;22&lt;/span&gt;
&lt;span class="normal"&gt;23&lt;/span&gt;
&lt;span class="normal"&gt;24&lt;/span&gt;
&lt;span class="normal"&gt;25&lt;/span&gt;
&lt;span class="normal"&gt;26&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;div&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="n"&gt;python&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;m&lt;/span&gt; &lt;span class="n"&gt;line_profiler&lt;/span&gt; &lt;span class="n"&gt;maze_part_two&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;py&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;lprof&lt;/span&gt;
&lt;span class="n"&gt;Timer&lt;/span&gt; &lt;span class="n"&gt;unit&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;1e-06&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt;

&lt;span class="n"&gt;Total&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;191.125&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt;
&lt;span class="n"&gt;File&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;maze_part_two&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;py&lt;/span&gt;
&lt;span class="n"&gt;Function&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;run&lt;/span&gt; &lt;span class="n"&gt;at&lt;/span&gt; &lt;span class="n"&gt;line&lt;/span&gt; &lt;span class="mi"&gt;13&lt;/span&gt;

&lt;span class="n"&gt;Line&lt;/span&gt; &lt;span class="c1"&gt;#      Hits         Time  Per Hit   % Time  Line Contents&lt;/span&gt;
&lt;span class="o"&gt;==============================================================&lt;/span&gt;
    &lt;span class="mi"&gt;13&lt;/span&gt;                                           &lt;span class="nd"&gt;@profile&lt;/span&gt;
    &lt;span class="mi"&gt;14&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="mi"&gt;15&lt;/span&gt;         &lt;span class="mi"&gt;1&lt;/span&gt;          &lt;span class="mi"&gt;886&lt;/span&gt;    &lt;span class="mf"&gt;886.0&lt;/span&gt;      &lt;span class="mf"&gt;0.0&lt;/span&gt;      &lt;span class="n"&gt;maze&lt;/span&gt; &lt;span class="o"&gt;=&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="mi"&gt;16&lt;/span&gt;         &lt;span class="mi"&gt;1&lt;/span&gt;            &lt;span class="mi"&gt;2&lt;/span&gt;      &lt;span class="mf"&gt;2.0&lt;/span&gt;      &lt;span class="mf"&gt;0.0&lt;/span&gt;      &lt;span class="n"&gt;pos&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
    &lt;span class="mi"&gt;17&lt;/span&gt;         &lt;span class="mi"&gt;1&lt;/span&gt;            &lt;span class="mi"&gt;1&lt;/span&gt;      &lt;span class="mf"&gt;1.0&lt;/span&gt;      &lt;span class="mf"&gt;0.0&lt;/span&gt;      &lt;span class="n"&gt;steps&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
    &lt;span class="mi"&gt;18&lt;/span&gt;         &lt;span class="mi"&gt;1&lt;/span&gt;            &lt;span class="mi"&gt;1&lt;/span&gt;      &lt;span class="mf"&gt;1.0&lt;/span&gt;      &lt;span class="mf"&gt;0.0&lt;/span&gt;      &lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="mi"&gt;19&lt;/span&gt;  &lt;span class="mi"&gt;26395587&lt;/span&gt;     &lt;span class="mi"&gt;24430262&lt;/span&gt;      &lt;span class="mf"&gt;0.9&lt;/span&gt;     &lt;span class="mf"&gt;12.8&lt;/span&gt;          &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="mi"&gt;20&lt;/span&gt;  &lt;span class="mi"&gt;26395587&lt;/span&gt;     &lt;span class="mi"&gt;29073318&lt;/span&gt;      &lt;span class="mf"&gt;1.1&lt;/span&gt;     &lt;span class="mf"&gt;15.2&lt;/span&gt;              &lt;span class="n"&gt;move&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;maze&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;pos&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="mi"&gt;21&lt;/span&gt;         &lt;span class="mi"&gt;1&lt;/span&gt;            &lt;span class="mi"&gt;3&lt;/span&gt;      &lt;span class="mf"&gt;3.0&lt;/span&gt;      &lt;span class="mf"&gt;0.0&lt;/span&gt;          &lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="ne"&gt;KeyError&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="mi"&gt;22&lt;/span&gt;         &lt;span class="mi"&gt;1&lt;/span&gt;           &lt;span class="mi"&gt;72&lt;/span&gt;     &lt;span class="mf"&gt;72.0&lt;/span&gt;      &lt;span class="mf"&gt;0.0&lt;/span&gt;              &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;steps&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="mi"&gt;23&lt;/span&gt;         &lt;span class="mi"&gt;1&lt;/span&gt;            &lt;span class="mi"&gt;3&lt;/span&gt;      &lt;span class="mf"&gt;3.0&lt;/span&gt;      &lt;span class="mf"&gt;0.0&lt;/span&gt;              &lt;span class="k"&gt;break&lt;/span&gt;
    &lt;span class="mi"&gt;24&lt;/span&gt;  &lt;span class="mi"&gt;26395586&lt;/span&gt;     &lt;span class="mi"&gt;24881530&lt;/span&gt;      &lt;span class="mf"&gt;0.9&lt;/span&gt;     &lt;span class="mf"&gt;13.0&lt;/span&gt;          &lt;span class="n"&gt;last_pos&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pos&lt;/span&gt;
    &lt;span class="mi"&gt;25&lt;/span&gt;  &lt;span class="mi"&gt;26395586&lt;/span&gt;     &lt;span class="mi"&gt;26706364&lt;/span&gt;      &lt;span class="mf"&gt;1.0&lt;/span&gt;     &lt;span class="mf"&gt;14.0&lt;/span&gt;          &lt;span class="n"&gt;pos&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;last_pos&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;move&lt;/span&gt;
    &lt;span class="mi"&gt;26&lt;/span&gt;  &lt;span class="mi"&gt;26395586&lt;/span&gt;     &lt;span class="mi"&gt;26374692&lt;/span&gt;      &lt;span class="mf"&gt;1.0&lt;/span&gt;     &lt;span class="mf"&gt;13.8&lt;/span&gt;          &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;move&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="mi"&gt;27&lt;/span&gt;  &lt;span class="mi"&gt;13057988&lt;/span&gt;     &lt;span class="mi"&gt;16389704&lt;/span&gt;      &lt;span class="mf"&gt;1.3&lt;/span&gt;      &lt;span class="mf"&gt;8.6&lt;/span&gt;              &lt;span class="n"&gt;maze&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;last_pos&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;-=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
    &lt;span class="mi"&gt;28&lt;/span&gt;                                                   &lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="mi"&gt;29&lt;/span&gt;  &lt;span class="mi"&gt;13337598&lt;/span&gt;     &lt;span class="mi"&gt;15591510&lt;/span&gt;      &lt;span class="mf"&gt;1.2&lt;/span&gt;      &lt;span class="mf"&gt;8.2&lt;/span&gt;              &lt;span class="n"&gt;maze&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;last_pos&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
    &lt;span class="mi"&gt;30&lt;/span&gt;  &lt;span class="mi"&gt;26395586&lt;/span&gt;     &lt;span class="mi"&gt;27676460&lt;/span&gt;      &lt;span class="mf"&gt;1.0&lt;/span&gt;     &lt;span class="mf"&gt;14.5&lt;/span&gt;          &lt;span class="n"&gt;steps&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/div&gt;
&lt;p&gt;There we go! Much more information than cProfile provided. The &amp;quot;Hits&amp;quot; column
contains the number of times each line was run, &amp;quot;Time&amp;quot; the total time spent on
the line (in the timer unit (1e-06s in this case)), and the remaining columns
build on that data.&lt;/p&gt;
&lt;/div&gt;
</content><category term="Technology"></category><category term="advent of code"></category><category term="python"></category></entry><entry><title>Advent of Code 2017</title><link href="https://www.chris-wells.net/articles/2017/11/30/advent-of-code-2017" rel="alternate"></link><published>2017-11-30T19:45:00-04:00</published><updated>2017-11-30T19:45:00-04:00</updated><author><name>Christopher Charbonneau Wells</name></author><id>tag:www.chris-wells.net,2017-11-30:/articles/2017/11/30/advent-of-code-2017</id><summary type="html">
&lt;p&gt;I taught myself Python a few years ago by following the wonderful
&lt;a class="reference external" href="https://learnpythonthehardway.org/"&gt;Learn Python the Hard Way&lt;/a&gt; (LPTHW) series by &lt;a class="reference external" href="https://zedshaw.com/"&gt;Zed A. Shaw&lt;/a&gt;. Since then, I
have spent a decent amount of time in Python largely in the Django framework.
Django is a lot of fun to work with because it abstracts away much of the
complexities of developing a full-featured web application in Python. In this
way, however, it has also led me to forget some of the basic Python that I
learned through LPTHW.&lt;/p&gt;
&lt;p&gt;In order to recapture some of those early lessons (and maybe learn a few more
Python 3 specific ones), I worked through (part of) the 2017 &lt;a class="reference external" href="http://adventofcode.com/"&gt;Advent of Code&lt;/a&gt;,
a 25-day, language agnostic programming challenge series developed by
&lt;a class="reference external" href="http://was.tl/"&gt;Eric Watsl&lt;/a&gt;. I originally thought about using the series to learn a &lt;em&gt;new&lt;/em&gt;
language, but eventually realized that I still have a long way to go in Python.&lt;/p&gt;
&lt;p&gt;This series explores my (often very basic) revelations and lessons learned
while completing 20 of the 25 days of challenges (vacation travel cut the
series short for me). The code I used for each day is &lt;a class="reference external" href="https://github.com/cdubz/advent-of-code-2017"&gt;available on GitHub&lt;/a&gt;.
Also check out the &lt;a class="reference external" href="https://github.com/topics/advent-of-code"&gt;advent-of-code&lt;/a&gt; and &lt;a class="reference external" href="https://github.com/topics/advent-of-code-2017"&gt;advent-of-code-2017&lt;/a&gt; GitHub topics
to see the many solutions people have developed in various languages.&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;a class="reference external" href="https://www.chris-wells.net/articles/2017/12/05/advent-of-code-2017-days-1-5#day-1-zip"&gt;Day 1: zip()&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="reference external" href="https://www.chris-wells.net/articles/2017/12/05/advent-of-code-2017-days-1-5#day-2-itertools-combinations"&gt;Day 2: itertools.combinations()&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="reference external" href="https://www.chris-wells.net/articles/2017/12/05/advent-of-code-2017-days-1-5#day-3-manhattan-distance"&gt;Day 3: Manhattan Distance&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="reference external" href="https://www.chris-wells.net/articles/2017/12/05/advent-of-code-2017-days-1-5#day-4-set"&gt;Day 4: set()&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="reference external" href="https://www.chris-wells.net/articles/2017/12/05/advent-of-code-2017-days-1-5#day-5-profiling"&gt;Day 5: Profiling&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="reference external" href="https://www.chris-wells.net/articles/2017/12/10/advent-of-code-2017-days-6-10#day-6-max-x-key-y"&gt;Day 6: max(x, key=y)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="reference external" href="https://www.chris-wells.net/articles/2017/12/10/advent-of-code-2017-days-6-10#day-7-runtimeerror-dictionary-changed-size-during-iteration"&gt;Day 7: RuntimeError: dictionary changed size during iteration&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="reference external" href="https://www.chris-wells.net/articles/2017/12/10/advent-of-code-2017-days-6-10#day-8-regular-expressions"&gt;Day 8: Regular Expressions&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="reference external" href="https://www.chris-wells.net/articles/2017/12/10/advent-of-code-2017-days-6-10#day-9-lookahead-lookbehind"&gt;Day 9: Lookahead, Lookbehind&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="reference external" href="https://www.chris-wells.net/articles/2017/12/10/advent-of-code-2017-days-6-10#day-10-functools-reduce"&gt;Day 10: functools.reduce()&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="reference external" href="https://www.chris-wells.net/articles/2017/12/15/advent-of-code-2017-days-11-15#day-11-navigating-a-hexagon-grid"&gt;Day 11: Navigating a Hexagon Grid&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="reference external" href="https://www.chris-wells.net/articles/2017/12/15/advent-of-code-2017-days-11-15#day-12-recursion"&gt;Day 12: Recursion&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="reference external" href="https://www.chris-wells.net/articles/2017/12/15/advent-of-code-2017-days-11-15#day-13-shallow-vs-deep-copy"&gt;Day 13: Shallow vs. Deep Copy&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="reference external" href="https://www.chris-wells.net/articles/2017/12/15/advent-of-code-2017-days-11-15#day-14-hexadecimal-to-binary"&gt;Day 14: Hexadecimal to Binary&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="reference external" href="https://www.chris-wells.net/articles/2017/12/15/advent-of-code-2017-days-11-15#day-15-progressbar2"&gt;Day 15: progressbar2&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="reference external" href="https://www.chris-wells.net/articles/2017/12/20/advent-of-code-2017-days-16-20#day-16-one-billion-permutations-in-0-535-seconds"&gt;Day 16: One Billion Permutations in 0.535 Seconds&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="reference external" href="https://www.chris-wells.net/articles/2017/12/20/advent-of-code-2017-days-16-20#day-17-collections-deque"&gt;Day 17: collections.deque()&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="reference external" href="https://www.chris-wells.net/articles/2017/12/20/advent-of-code-2017-days-16-20#day-18-negative-numbers-and-str-isdigit"&gt;Day 18: Negative Numbers and str.isdigit()&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="reference external" href="https://www.chris-wells.net/articles/2017/12/20/advent-of-code-2017-days-16-20#day-19-the-internet-is-not-a-big-truck"&gt;Day 19: The Internet is Not a Big Truck&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="reference external" href="https://www.chris-wells.net/articles/2017/12/20/advent-of-code-2017-days-16-20#day-20-reading-from-a-file"&gt;Day 20: Reading from a File&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</summary><content type="html">&lt;span class="target" id="top"&gt;&lt;/span&gt;
&lt;p&gt;I taught myself Python a few years ago by following the wonderful
&lt;a class="reference external" href="https://learnpythonthehardway.org/"&gt;Learn Python the Hard Way&lt;/a&gt; (LPTHW) series by &lt;a class="reference external" href="https://zedshaw.com/"&gt;Zed A. Shaw&lt;/a&gt;. Since then, I
have spent a decent amount of time in Python largely in the Django framework.
Django is a lot of fun to work with because it abstracts away much of the
complexities of developing a full-featured web application in Python. In this
way, however, it has also led me to forget some of the basic Python that I
learned through LPTHW.&lt;/p&gt;
&lt;p&gt;In order to recapture some of those early lessons (and maybe learn a few more
Python 3 specific ones), I worked through (part of) the 2017 &lt;a class="reference external" href="http://adventofcode.com/"&gt;Advent of Code&lt;/a&gt;,
a 25-day, language agnostic programming challenge series developed by
&lt;a class="reference external" href="http://was.tl/"&gt;Eric Watsl&lt;/a&gt;. I originally thought about using the series to learn a &lt;em&gt;new&lt;/em&gt;
language, but eventually realized that I still have a long way to go in Python.&lt;/p&gt;
&lt;p&gt;This series explores my (often very basic) revelations and lessons learned
while completing 20 of the 25 days of challenges (vacation travel cut the
series short for me). The code I used for each day is &lt;a class="reference external" href="https://github.com/cdubz/advent-of-code-2017"&gt;available on GitHub&lt;/a&gt;.
Also check out the &lt;a class="reference external" href="https://github.com/topics/advent-of-code"&gt;advent-of-code&lt;/a&gt; and &lt;a class="reference external" href="https://github.com/topics/advent-of-code-2017"&gt;advent-of-code-2017&lt;/a&gt; GitHub topics
to see the many solutions people have developed in various languages.&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;a class="reference external" href="https://www.chris-wells.net/articles/2017/12/05/advent-of-code-2017-days-1-5#day-1-zip"&gt;Day 1: zip()&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="reference external" href="https://www.chris-wells.net/articles/2017/12/05/advent-of-code-2017-days-1-5#day-2-itertools-combinations"&gt;Day 2: itertools.combinations()&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="reference external" href="https://www.chris-wells.net/articles/2017/12/05/advent-of-code-2017-days-1-5#day-3-manhattan-distance"&gt;Day 3: Manhattan Distance&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="reference external" href="https://www.chris-wells.net/articles/2017/12/05/advent-of-code-2017-days-1-5#day-4-set"&gt;Day 4: set()&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="reference external" href="https://www.chris-wells.net/articles/2017/12/05/advent-of-code-2017-days-1-5#day-5-profiling"&gt;Day 5: Profiling&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="reference external" href="https://www.chris-wells.net/articles/2017/12/10/advent-of-code-2017-days-6-10#day-6-max-x-key-y"&gt;Day 6: max(x, key=y)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="reference external" href="https://www.chris-wells.net/articles/2017/12/10/advent-of-code-2017-days-6-10#day-7-runtimeerror-dictionary-changed-size-during-iteration"&gt;Day 7: RuntimeError: dictionary changed size during iteration&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="reference external" href="https://www.chris-wells.net/articles/2017/12/10/advent-of-code-2017-days-6-10#day-8-regular-expressions"&gt;Day 8: Regular Expressions&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="reference external" href="https://www.chris-wells.net/articles/2017/12/10/advent-of-code-2017-days-6-10#day-9-lookahead-lookbehind"&gt;Day 9: Lookahead, Lookbehind&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="reference external" href="https://www.chris-wells.net/articles/2017/12/10/advent-of-code-2017-days-6-10#day-10-functools-reduce"&gt;Day 10: functools.reduce()&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="reference external" href="https://www.chris-wells.net/articles/2017/12/15/advent-of-code-2017-days-11-15#day-11-navigating-a-hexagon-grid"&gt;Day 11: Navigating a Hexagon Grid&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="reference external" href="https://www.chris-wells.net/articles/2017/12/15/advent-of-code-2017-days-11-15#day-12-recursion"&gt;Day 12: Recursion&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="reference external" href="https://www.chris-wells.net/articles/2017/12/15/advent-of-code-2017-days-11-15#day-13-shallow-vs-deep-copy"&gt;Day 13: Shallow vs. Deep Copy&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="reference external" href="https://www.chris-wells.net/articles/2017/12/15/advent-of-code-2017-days-11-15#day-14-hexadecimal-to-binary"&gt;Day 14: Hexadecimal to Binary&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="reference external" href="https://www.chris-wells.net/articles/2017/12/15/advent-of-code-2017-days-11-15#day-15-progressbar2"&gt;Day 15: progressbar2&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="reference external" href="https://www.chris-wells.net/articles/2017/12/20/advent-of-code-2017-days-16-20#day-16-one-billion-permutations-in-0-535-seconds"&gt;Day 16: One Billion Permutations in 0.535 Seconds&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="reference external" href="https://www.chris-wells.net/articles/2017/12/20/advent-of-code-2017-days-16-20#day-17-collections-deque"&gt;Day 17: collections.deque()&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="reference external" href="https://www.chris-wells.net/articles/2017/12/20/advent-of-code-2017-days-16-20#day-18-negative-numbers-and-str-isdigit"&gt;Day 18: Negative Numbers and str.isdigit()&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="reference external" href="https://www.chris-wells.net/articles/2017/12/20/advent-of-code-2017-days-16-20#day-19-the-internet-is-not-a-big-truck"&gt;Day 19: The Internet is Not a Big Truck&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="reference external" href="https://www.chris-wells.net/articles/2017/12/20/advent-of-code-2017-days-16-20#day-20-reading-from-a-file"&gt;Day 20: Reading from a File&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</content><category term="Technology"></category><category term="advent of code"></category><category term="python"></category></entry><entry><title>Consistent Selenium Testing in Python</title><link href="https://www.chris-wells.net/articles/2017/09/01/consistent-selenium-testing" rel="alternate"></link><published>2017-09-01T10:10:00-04:00</published><updated>2017-09-01T10:10:00-04:00</updated><author><name>Christopher Charbonneau Wells</name></author><id>tag:www.chris-wells.net,2017-09-01:/articles/2017/09/01/consistent-selenium-testing</id><summary type="html">
&lt;p&gt;Back in April, I learned about &lt;a class="reference external" href="https://github.com/overshard/timestrap"&gt;Timestrap&lt;/a&gt;, a self-hostable, &lt;a class="reference external" href="https://www.djangoproject.com/"&gt;Django&lt;/a&gt;-based
time-tracking project from a &lt;a class="reference external" href="https://news.ycombinator.com/item?id=14180673"&gt;post on HackerNews&lt;/a&gt; by &lt;a class="reference external" href="http://isaacbythewood.com/"&gt;Isaac Bythewood&lt;/a&gt;. As I
have been learning Python in the past year or so, I reached out to Isaac and
started contributing to the project. After getting familiar with the core
application, I turned my attention to testing and eventually found my way to
&lt;a class="reference external" href="http://www.seleniumhq.org/"&gt;Selenium&lt;/a&gt;, a collection of browser automation tools used for frontend
testing.&lt;/p&gt;
&lt;p&gt;I had never worked with Selenium or other automated testing products, so it
struck me as a great opportunity to get my feet wet in something new. After
getting things up and running, we quickly learned that the test results were
quite inconsistent across development environments - even to a point that
occasionally tests would succeed when run individually, but fail with the full
test case.&lt;/p&gt;
&lt;p&gt;After much trial and error, we have settled on a (mostly) consistent setup for
testing with Selenium, Python and &lt;a class="reference external" href="https://saucelabs.com/"&gt;SauceLabs&lt;/a&gt;. This produces much better
results than testing in development environments and crossing fingers during
CI. Hopefully this primer will help others facing similar challenges (as we had
a lot of trouble finding good material on the subject).&lt;/p&gt;
</summary><content type="html">&lt;span class="target" id="top"&gt;&lt;/span&gt;
&lt;p&gt;Back in April, I learned about &lt;a class="reference external" href="https://github.com/overshard/timestrap"&gt;Timestrap&lt;/a&gt;, a self-hostable, &lt;a class="reference external" href="https://www.djangoproject.com/"&gt;Django&lt;/a&gt;-based
time-tracking project from a &lt;a class="reference external" href="https://news.ycombinator.com/item?id=14180673"&gt;post on HackerNews&lt;/a&gt; by &lt;a class="reference external" href="http://isaacbythewood.com/"&gt;Isaac Bythewood&lt;/a&gt;. As I
have been learning Python in the past year or so, I reached out to Isaac and
started contributing to the project. After getting familiar with the core
application, I turned my attention to testing and eventually found my way to
&lt;a class="reference external" href="http://www.seleniumhq.org/"&gt;Selenium&lt;/a&gt;, a collection of browser automation tools used for frontend
testing.&lt;/p&gt;
&lt;p&gt;I had never worked with Selenium or other automated testing products, so it
struck me as a great opportunity to get my feet wet in something new. After
getting things up and running, we quickly learned that the test results were
quite inconsistent across development environments - even to a point that
occasionally tests would succeed when run individually, but fail with the full
test case.&lt;/p&gt;
&lt;p&gt;After much trial and error, we have settled on a (mostly) consistent setup for
testing with Selenium, Python and &lt;a class="reference external" href="https://saucelabs.com/"&gt;SauceLabs&lt;/a&gt;. This produces much better
results than testing in development environments and crossing fingers during
CI. Hopefully this primer will help others facing similar challenges (as we had
a lot of trouble finding good material on the subject).&lt;/p&gt;

&lt;ul class="simple"&gt;
&lt;li&gt;&lt;a class="reference internal" href="#getting-started"&gt;Getting Started&lt;/a&gt;&lt;ul&gt;
&lt;li&gt;&lt;a class="reference internal" href="#installing-chromedriver"&gt;Installing Chromedriver&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="reference internal" href="#driving-chrome-with-python"&gt;Driving Chrome with Python&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="reference internal" href="#going-headless"&gt;Going Headless&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a class="reference internal" href="#key-selenium-functionality"&gt;Key Selenium Functionality&lt;/a&gt;&lt;ul&gt;
&lt;li&gt;&lt;a class="reference internal" href="#finding-elements"&gt;Finding Elements&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="reference internal" href="#sending-input"&gt;Sending Input&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="reference internal" href="#clearing-input"&gt;Clearing Input&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="reference internal" href="#waiting"&gt;Waiting&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a class="reference internal" href="#improving-consistency-with-saucelabs"&gt;Improving Consistency with SauceLabs&lt;/a&gt;&lt;ul&gt;
&lt;li&gt;&lt;a class="reference internal" href="#username-and-access-key"&gt;Username and Access Key&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="reference internal" href="#webdriver-remote"&gt;WebDriver.Remote&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="reference internal" href="#sauce-connect-proxy-client"&gt;Sauce Connect Proxy Client&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a class="reference internal" href="#putting-it-all-together"&gt;Putting It All Together&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="section" id="getting-started"&gt;
&lt;h2&gt;Getting Started&lt;/h2&gt;
&lt;p&gt;Use &lt;tt class="docutils literal"&gt;pip&lt;/tt&gt; to install the selenium package (perhaps in a virtual environment):&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="go"&gt;pip install selenium&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Selenium needs a &lt;a class="reference external" href="http://www.seleniumhq.org/projects/webdriver/"&gt;WebDriver&lt;/a&gt; before it can do anything useful. There are
currently drivers for &lt;a class="reference external" href="https://github.com/mozilla/geckodriver/releases"&gt;Firefox&lt;/a&gt;, &lt;a class="reference external" href="https://sites.google.com/a/chromium.org/chromedriver/downloads"&gt;Chrome&lt;/a&gt;, &lt;a class="reference external" href="https://developer.microsoft.com/en-us/microsoft-edge/tools/webdriver/"&gt;Edge&lt;/a&gt; and &lt;a class="reference external" href="https://webkit.org/blog/6900/webdriver-support-in-safari-10/"&gt;Safari&lt;/a&gt;. We
originally started out with Firefox's geckodriver, but in initial attempts to
fight inconsistency moved to chromedriver hoping for better results.
Ultimately, both seem to have their shortfalls but we have stuck with
chromedriver since the original change so that is what I will use in examples
here.&lt;/p&gt;
&lt;div class="section" id="installing-chromedriver"&gt;
&lt;h3&gt;Installing Chromedriver&lt;/h3&gt;
&lt;p&gt;The installation process is pretty simple, chromedriver just needs to be
executable on the development system so Selenium can interact with it during
testing. Check the &lt;a class="reference external" href="https://sites.google.com/a/chromium.org/chromedriver/downloads"&gt;ChromeDriver Downloads&lt;/a&gt; page for the latest version of the
driver to download and install. In Linux, this may look like this:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="go"&gt;curl -L https://chromedriver.storage.googleapis.com/2.32/chromedriver_linux64.zip -o chromedriver.zip&lt;/span&gt;
&lt;span class="go"&gt;sudo mkdir -p /usr/local/bin/&lt;/span&gt;
&lt;span class="go"&gt;sudo unzip chromedriver.zip -d /usr/local/bin/&lt;/span&gt;
&lt;span class="go"&gt;sudo chmod +x /usr/local/bin/chromedriver&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;The above set of commands&lt;/p&gt;
&lt;ol class="arabic simple"&gt;
&lt;li&gt;downloads chromedriver,&lt;/li&gt;
&lt;li&gt;places it in a common &lt;cite&gt;$PATH&lt;/cite&gt; location, and&lt;/li&gt;
&lt;li&gt;sets it to be executable.&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;
&lt;div class="section" id="driving-chrome-with-python"&gt;
&lt;h3&gt;Driving Chrome with Python&lt;/h3&gt;
&lt;p&gt;With chromedriver ready to go, all that is left is to import the WebDriver
package from Selenium and tell it to use chromedriver. E.g.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;table class="highlighttable"&gt;&lt;tr&gt;&lt;td class="linenos"&gt;&lt;div class="linenodiv"&gt;&lt;pre&gt;&lt;span class="normal"&gt;0&lt;/span&gt;
&lt;span class="normal"&gt;1&lt;/span&gt;
&lt;span class="normal"&gt;2&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;div&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;selenium&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;webdriver&lt;/span&gt;

&lt;span class="n"&gt;driver&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;webdriver&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Chrome&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/div&gt;
&lt;p&gt;Running this code should invoke a window in Chrome, but nothing will happen
because this example does not give the WebDriver any instruction.&lt;/p&gt;
&lt;p&gt;Let's try a simple task: getting the &amp;quot;word of the day&amp;quot; from Merriam-Webster's
website. A quick look at the source of &lt;a class="reference external" href="https://www.merriam-webster.com/word-of-the-day"&gt;M-W's word of the day&lt;/a&gt; page reveals
where the word can be found in markup:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;table class="highlighttable"&gt;&lt;tr&gt;&lt;td class="linenos"&gt;&lt;div class="linenodiv"&gt;&lt;pre&gt;&lt;span class="normal"&gt; 0&lt;/span&gt;
&lt;span class="normal"&gt; 1&lt;/span&gt;
&lt;span class="normal"&gt; 2&lt;/span&gt;
&lt;span class="normal"&gt; 3&lt;/span&gt;
&lt;span class="normal"&gt; 4&lt;/span&gt;
&lt;span class="normal"&gt; 5&lt;/span&gt;
&lt;span class="normal"&gt; 6&lt;/span&gt;
&lt;span class="normal"&gt; 7&lt;/span&gt;
&lt;span class="normal"&gt; 8&lt;/span&gt;
&lt;span class="normal"&gt; 9&lt;/span&gt;
&lt;span class="normal"&gt;10&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;div&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;article&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
...
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt; &lt;span class="na"&gt;class&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;quick-def-box&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt; &lt;span class="na"&gt;class&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;word-header&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt; &lt;span class="na"&gt;class&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;word-and-pronunciation&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
          &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;h1&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;confrere&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;h1&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
          ...
        &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
...
&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;article&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/div&gt;
&lt;p&gt;So the actual word of the day, &amp;quot;confrere&amp;quot; today, is found in a &lt;tt class="docutils literal"&gt;h1&lt;/tt&gt; child
of a &lt;tt class="docutils literal"&gt;div&lt;/tt&gt; element with the class &lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;word-and-pronunciation&lt;/span&gt;&lt;/tt&gt;. Searching the
page reveals that this class is unique, so it can be used by Selenium to
identify the element and get its content like so:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;table class="highlighttable"&gt;&lt;tr&gt;&lt;td class="linenos"&gt;&lt;div class="linenodiv"&gt;&lt;pre&gt;&lt;span class="normal"&gt;0&lt;/span&gt;
&lt;span class="normal"&gt;1&lt;/span&gt;
&lt;span class="normal"&gt;2&lt;/span&gt;
&lt;span class="normal"&gt;3&lt;/span&gt;
&lt;span class="normal"&gt;4&lt;/span&gt;
&lt;span class="normal"&gt;5&lt;/span&gt;
&lt;span class="normal"&gt;6&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;div&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;selenium&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;webdriver&lt;/span&gt;

&lt;span class="n"&gt;driver&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;webdriver&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Chrome&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="n"&gt;driver&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;https://www.merriam-webster.com/word-of-the-day&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;element&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;driver&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;find_element_by_css_selector&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;.word-and-pronunciation h1&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;element&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;driver&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;close&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/div&gt;
&lt;p&gt;Running the above should invoke a Chrome window that loads the word of the day
page and then closes. The Python script should output the word before exiting.
And there you have it! This example uses a CSS selector with Selenium's
&lt;tt class="docutils literal"&gt;find_element_by_css_selector&lt;/tt&gt; method, but there are many other
&lt;em&gt;find_element_by_*&lt;/em&gt; methods available for page &amp;quot;navigation&amp;quot;.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="going-headless"&gt;
&lt;h3&gt;Going Headless&lt;/h3&gt;
&lt;p&gt;For the examples ahead it will be useful to continue using chromedriver as is,
however for actual testing, &amp;quot;headless&amp;quot; mode will be the way to go. At the time
of this writing, headless requires &lt;a class="reference external" href="https://www.google.com/chrome/browser/beta.html"&gt;Chrome beta&lt;/a&gt; to be installed on the
development system. Let's modify the previous example a bit:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;table class="highlighttable"&gt;&lt;tr&gt;&lt;td class="linenos"&gt;&lt;div class="linenodiv"&gt;&lt;pre&gt;&lt;span class="normal"&gt;0&lt;/span&gt;
&lt;span class="normal"&gt;1&lt;/span&gt;
&lt;span class="normal"&gt;2&lt;/span&gt;
&lt;span class="normal"&gt;3&lt;/span&gt;
&lt;span class="normal"&gt;4&lt;/span&gt;
&lt;span class="normal"&gt;5&lt;/span&gt;
&lt;span class="normal"&gt;6&lt;/span&gt;
&lt;span class="normal"&gt;7&lt;/span&gt;
&lt;span class="normal"&gt;8&lt;/span&gt;
&lt;span class="normal"&gt;9&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;div&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;selenium&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;webdriver&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;selenium.webdriver.chrome.options&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Options&lt;/span&gt;

&lt;span class="n"&gt;options&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Options&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="n"&gt;add_argument&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;--headless&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;driver&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;webdriver&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Chrome&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;chrome_options&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;driver&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;https://www.merriam-webster.com/word-of-the-day&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;element&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;driver&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;find_element_by_css_selector&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;.word-and-pronunciation h1&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;element&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;driver&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;close&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/div&gt;
&lt;p&gt;Running this code should print out the word of the day, just as the previous
example did, &lt;em&gt;but&lt;/em&gt; this time no Chrome window will appear on screen. This is
because the new version uses the &lt;tt class="docutils literal"&gt;WebDriver.Chrome.options&lt;/tt&gt; class to provide
arguments to the Chrome (beta) binary using &lt;tt class="docutils literal"&gt;chrome_options&lt;/tt&gt;. The only option
passed here is &lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;--headless&lt;/span&gt;&lt;/tt&gt;, which tells Chrome to execute the actions
without rendering anything.&lt;/p&gt;
&lt;blockquote&gt;
Comparing the speed of these two examples wouldn't reveal much, but when
used in a real set of tests, with possibly hundreds of separate tests for
an app, the time savings will be incredible.&lt;/blockquote&gt;
&lt;p&gt;See also: &lt;a class="reference external" href="https://peter.sh/experiments/chromium-command-line-switches/"&gt;this very long list of Chrome command line switches&lt;/a&gt;.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="section" id="key-selenium-functionality"&gt;
&lt;h2&gt;Key Selenium Functionality&lt;/h2&gt;
&lt;p&gt;There are lots of important Selenium classes and methods that will be used
extensively for testing web pages. Here is a short list of some key
functionality to know about -&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;a class="reference internal" href="#finding-elements"&gt;Finding Elements&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="reference internal" href="#sending-input"&gt;Sending Input&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="reference internal" href="#clearing-input"&gt;Clearing Input&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="reference internal" href="#waiting"&gt;Waiting&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="section" id="finding-elements"&gt;
&lt;h3&gt;Finding Elements&lt;/h3&gt;
&lt;p&gt;The example above uses &lt;tt class="docutils literal"&gt;WebDriver.find_element_by_css_selector&lt;/tt&gt; and there are
eight of these methods in total (plus eight more in plural form):&lt;/p&gt;
&lt;ol class="arabic simple"&gt;
&lt;li&gt;&lt;tt class="docutils literal"&gt;find_element_by_class_name&lt;/tt&gt;&lt;/li&gt;
&lt;li&gt;&lt;tt class="docutils literal"&gt;find_element_by_css_selector&lt;/tt&gt;&lt;/li&gt;
&lt;li&gt;&lt;tt class="docutils literal"&gt;find_element_by_id&lt;/tt&gt;&lt;/li&gt;
&lt;li&gt;&lt;tt class="docutils literal"&gt;find_element_by_link_text&lt;/tt&gt;&lt;/li&gt;
&lt;li&gt;&lt;tt class="docutils literal"&gt;find_element_by_name&lt;/tt&gt;&lt;/li&gt;
&lt;li&gt;&lt;tt class="docutils literal"&gt;find_element_by_partial_link_text&lt;/tt&gt;&lt;/li&gt;
&lt;li&gt;&lt;tt class="docutils literal"&gt;find_element_by_tag_name&lt;/tt&gt;&lt;/li&gt;
&lt;li&gt;&lt;tt class="docutils literal"&gt;find_element_by_xpath&lt;/tt&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;All of these methods are pretty descriptive (and long), so a nice helper is the
&lt;tt class="docutils literal"&gt;WebDriver.common.By&lt;/tt&gt; class. &lt;tt class="docutils literal"&gt;By&lt;/tt&gt; can replace the longer form methods
with a simpler shorthand. The previous code example could be replaced with:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;table class="highlighttable"&gt;&lt;tr&gt;&lt;td class="linenos"&gt;&lt;div class="linenodiv"&gt;&lt;pre&gt;&lt;span class="normal"&gt;0&lt;/span&gt;
&lt;span class="normal"&gt;1&lt;/span&gt;
&lt;span class="normal"&gt;2&lt;/span&gt;
&lt;span class="normal"&gt;3&lt;/span&gt;
&lt;span class="normal"&gt;4&lt;/span&gt;
&lt;span class="normal"&gt;5&lt;/span&gt;
&lt;span class="normal"&gt;6&lt;/span&gt;
&lt;span class="normal"&gt;7&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;div&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;selenium&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;webdriver&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;selenium.webdriver.common.by&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;By&lt;/span&gt;

&lt;span class="n"&gt;driver&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;webdriver&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Chrome&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="n"&gt;driver&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;https://www.merriam-webster.com/word-of-the-day&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;element&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;driver&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;find_element&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="n"&gt;CSS_SELECTOR&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;.word-and-pronunciation h1&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;element&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;driver&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;close&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/div&gt;
&lt;p&gt;While this code is not necessarily &lt;em&gt;shorter&lt;/em&gt;, I suggest taking it a bit further
and creating a wrapper method for finding elements. This should significantly
reduce the effort of typing these methods out as test size and complexity
increases. Here is an example wrapper I have used in test cases:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;table class="highlighttable"&gt;&lt;tr&gt;&lt;td class="linenos"&gt;&lt;div class="linenodiv"&gt;&lt;pre&gt;&lt;span class="normal"&gt;0&lt;/span&gt;
&lt;span class="normal"&gt;1&lt;/span&gt;
&lt;span class="normal"&gt;2&lt;/span&gt;
&lt;span class="normal"&gt;3&lt;/span&gt;
&lt;span class="normal"&gt;4&lt;/span&gt;
&lt;span class="normal"&gt;5&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;div&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;find&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;by&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;elements&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;driver&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;find_elements&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;by&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;elements&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;elements&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="k"&gt;else&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;elements&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/div&gt;
&lt;p&gt;This uses the plural &lt;tt class="docutils literal"&gt;find_elements&lt;/tt&gt; method and returns either a list or a
single item depending on what is found. With this, I can use
&lt;tt class="docutils literal"&gt;find(By.ID, &lt;span class="pre"&gt;'my-id')&lt;/span&gt;&lt;/tt&gt; instead of &lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;driver.find_element_by_id('my-id')&lt;/span&gt;&lt;/tt&gt;.
This form should produce much cleaner code, particularly when jumping between
the various available find methods.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="sending-input"&gt;
&lt;h3&gt;Sending Input&lt;/h3&gt;
&lt;p&gt;Most web app projects will deal with some degree of input and Selenium can
support that fairly well. Every &lt;tt class="docutils literal"&gt;WebElement&lt;/tt&gt; class (the result of the various
&lt;cite&gt;find_element&lt;/cite&gt; methods) has a &lt;tt class="docutils literal"&gt;send_keys&lt;/tt&gt; method that can be used to simulate
typing in an element. Let's try to use this functionality to search &amp;quot;Python&amp;quot; on
Wikipedia -&lt;/p&gt;
&lt;p&gt;A quick look at Wikipedia's page source reveals that the search input element
uses the id &lt;tt class="docutils literal"&gt;searchInput&lt;/tt&gt;. With this, Selenium can find the element and send
some keys to it:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;table class="highlighttable"&gt;&lt;tr&gt;&lt;td class="linenos"&gt;&lt;div class="linenodiv"&gt;&lt;pre&gt;&lt;span class="normal"&gt;0&lt;/span&gt;
&lt;span class="normal"&gt;1&lt;/span&gt;
&lt;span class="normal"&gt;2&lt;/span&gt;
&lt;span class="normal"&gt;3&lt;/span&gt;
&lt;span class="normal"&gt;4&lt;/span&gt;
&lt;span class="normal"&gt;5&lt;/span&gt;
&lt;span class="normal"&gt;6&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;div&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;selenium&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;webdriver&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;selenium.webdriver.common.by&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;By&lt;/span&gt;

&lt;span class="n"&gt;driver&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;webdriver&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Chrome&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="n"&gt;driver&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;https://www.wikipedia.org/&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;el&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;driver&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;find_element&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="n"&gt;ID&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;searchInput&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;el&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;send_keys&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;Python&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/div&gt;
&lt;p&gt;The above code should result in an open Chrome window with the Wikipedia page
loaded and &amp;quot;Python&amp;quot; in the search input field. This windows stays open because
the code does not include the &lt;tt class="docutils literal"&gt;driver.close()&lt;/tt&gt; command that is used in
previous examples.&lt;/p&gt;
&lt;p&gt;There are a couple of different ways to actually &lt;em&gt;submit&lt;/em&gt; a form. In general I
have found no real difference between any of the options, but I tend to fall
back on locating and &amp;quot;clicking&amp;quot; the form's submit button when possible. Here
are some of the ways submission can be accomplished:&lt;/p&gt;
&lt;div class="section" id="submitting-the-form-element"&gt;
&lt;h4&gt;Submitting the form element&lt;/h4&gt;
&lt;p&gt;Taking another look at the Wikipedia source, the search form has a simple ID:
&lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;search-form&lt;/span&gt;&lt;/tt&gt;. This ID can be used with the &lt;tt class="docutils literal"&gt;WebElement.submit()&lt;/tt&gt; method to
submit the form.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Add&lt;/em&gt; the following to the previous example from &lt;strong&gt;Using Input&lt;/strong&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;table class="highlighttable"&gt;&lt;tr&gt;&lt;td class="linenos"&gt;&lt;div class="linenodiv"&gt;&lt;pre&gt;&lt;span class="normal"&gt;7&lt;/span&gt;
&lt;span class="normal"&gt;8&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;div&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="n"&gt;form&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;driver&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;find_element&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="n"&gt;ID&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;search-form&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;form&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;submit&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/div&gt;
&lt;p&gt;Running the code should leave you with a Chrome window open to
&lt;a class="reference external" href="https://en.wikipedia.org/wiki/Python"&gt;Wikipedia's results page for Python&lt;/a&gt;.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="clicking-a-form-submit-button"&gt;
&lt;h4&gt;Clicking a form submit button&lt;/h4&gt;
&lt;p&gt;The Wikiedpia search page includes a fancy, styled submit button for searching.
It does not have a unique ID, so the code will need to use some other method
to identify and &amp;quot;click&amp;quot; the button. It is the only &lt;tt class="docutils literal"&gt;button&lt;/tt&gt; element inside
&lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;search-form&lt;/span&gt;&lt;/tt&gt;, so it can be easily targeted with a CSS selector.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Add&lt;/em&gt; the following to the example from &lt;strong&gt;Using Input&lt;/strong&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;table class="highlighttable"&gt;&lt;tr&gt;&lt;td class="linenos"&gt;&lt;div class="linenodiv"&gt;&lt;pre&gt;&lt;span class="normal"&gt;7&lt;/span&gt;
&lt;span class="normal"&gt;8&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;div&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="n"&gt;button&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;driver&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;find_element&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="n"&gt;CSS_SELECTOR&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;#search-form button&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;button&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;click&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/div&gt;
&lt;p&gt;As above, this code should produce &lt;a class="reference external" href="https://en.wikipedia.org/wiki/Python"&gt;Wikipedia's results page for Python&lt;/a&gt;.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="pressing-the-enter-key"&gt;
&lt;h4&gt;Pressing the enter key&lt;/h4&gt;
&lt;p&gt;Lastly, Selenium has a set of key codes that can be used to simulate &amp;quot;special&amp;quot;
(non-alphanumeric) keys. These codes are found in &lt;tt class="docutils literal"&gt;WebDriver.common.keys&lt;/tt&gt;. In
order to submit the form, the code will need to use the &lt;em&gt;return&lt;/em&gt; (or &lt;em&gt;enter&lt;/em&gt;)
key, so a revised version of the Wikipedia search code looks like this:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;table class="highlighttable"&gt;&lt;tr&gt;&lt;td class="linenos"&gt;&lt;div class="linenodiv"&gt;&lt;pre&gt;&lt;span class="normal"&gt;0&lt;/span&gt;
&lt;span class="normal"&gt;1&lt;/span&gt;
&lt;span class="normal"&gt;2&lt;/span&gt;
&lt;span class="normal"&gt;3&lt;/span&gt;
&lt;span class="normal"&gt;4&lt;/span&gt;
&lt;span class="normal"&gt;5&lt;/span&gt;
&lt;span class="normal"&gt;6&lt;/span&gt;
&lt;span class="normal"&gt;7&lt;/span&gt;
&lt;span class="normal"&gt;8&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;div&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;selenium&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;webdriver&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;selenium.webdriver.common.by&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;By&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;selenium.webdriver.common.keys&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Keys&lt;/span&gt;

&lt;span class="n"&gt;driver&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;webdriver&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Chrome&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="n"&gt;driver&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;https://www.wikipedia.org/&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;el&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;driver&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;find_element&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="n"&gt;ID&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;searchInput&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;el&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;send_keys&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;Python&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;el&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;send_keys&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Keys&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;RETURN&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/div&gt;
&lt;p&gt;Just like the two previous examples, this script should exit leaving a Chrome
page open to the Wikipedia search results for &amp;quot;Python&amp;quot;.&lt;/p&gt;
&lt;blockquote&gt;
This is perhaps the cleanest way to get a form submitted because it doesn't
require finding other elements, but a thorough tester may want to consider
testing multiple submission methods to ensure functionality.&lt;/blockquote&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="section" id="clearing-input"&gt;
&lt;h3&gt;Clearing Input&lt;/h3&gt;
&lt;p&gt;While Selenium does offer a &lt;tt class="docutils literal"&gt;WebElement.clear()&lt;/tt&gt; method, its implementation
is inconsistent across browsers and its behavior can be defined differently
depending on the app and element being tested. For these reasons, I don't think
it should be used to clear form input fields. Instead, Selenium's &lt;tt class="docutils literal"&gt;Keys&lt;/tt&gt;
class can be used to simulate pressing the backspace key multiple times in a
field.&lt;/p&gt;
&lt;p&gt;Here is a simple function to handle this -&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;table class="highlighttable"&gt;&lt;tr&gt;&lt;td class="linenos"&gt;&lt;div class="linenodiv"&gt;&lt;pre&gt;&lt;span class="normal"&gt;0&lt;/span&gt;
&lt;span class="normal"&gt;1&lt;/span&gt;
&lt;span class="normal"&gt;2&lt;/span&gt;
&lt;span class="normal"&gt;3&lt;/span&gt;
&lt;span class="normal"&gt;4&lt;/span&gt;
&lt;span class="normal"&gt;5&lt;/span&gt;
&lt;span class="normal"&gt;6&lt;/span&gt;
&lt;span class="normal"&gt;7&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;div&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;selenium.webdriver.common.keys&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Keys&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;clear&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;element&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;value&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;element&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get_attribute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;value&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;value&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="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;char&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;element&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;send_keys&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Keys&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;BACK_SPACE&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/div&gt;
&lt;p&gt;This &lt;tt class="docutils literal"&gt;clear&lt;/tt&gt; function will take a &lt;tt class="docutils literal"&gt;WebElement&lt;/tt&gt;, get the length of its
&lt;tt class="docutils literal"&gt;value&lt;/tt&gt; attribute, and simulate hitting the &lt;tt class="docutils literal"&gt;BACK_SPACE&lt;/tt&gt; until all text is
removed from the field.&lt;/p&gt;
&lt;p&gt;Let's use Selenium to load Google and search for &amp;quot;selenium&amp;quot;. Google's search
input element does not have a unique ID or class, but it does use a &lt;tt class="docutils literal"&gt;name&lt;/tt&gt;
attribute with the value &amp;quot;q&amp;quot;. This can be used to find the element and send the
keys. Expanding on the previous example:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;table class="highlighttable"&gt;&lt;tr&gt;&lt;td class="linenos"&gt;&lt;div class="linenodiv"&gt;&lt;pre&gt;&lt;span class="normal"&gt; 0&lt;/span&gt;
&lt;span class="normal"&gt; 1&lt;/span&gt;
&lt;span class="normal"&gt; 2&lt;/span&gt;
&lt;span class="normal"&gt; 3&lt;/span&gt;
&lt;span class="normal"&gt; 4&lt;/span&gt;
&lt;span class="normal"&gt; 5&lt;/span&gt;
&lt;span class="normal"&gt; 6&lt;/span&gt;
&lt;span class="normal"&gt; 7&lt;/span&gt;
&lt;span class="normal"&gt; 8&lt;/span&gt;
&lt;span class="normal"&gt; 9&lt;/span&gt;
&lt;span class="normal"&gt;10&lt;/span&gt;
&lt;span class="normal"&gt;11&lt;/span&gt;
&lt;span class="normal"&gt;12&lt;/span&gt;
&lt;span class="normal"&gt;13&lt;/span&gt;
&lt;span class="normal"&gt;14&lt;/span&gt;
&lt;span class="normal"&gt;15&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;div&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;selenium&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;webdriver&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;selenium.webdriver.common.by&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;By&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;selenium.webdriver.common.keys&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Keys&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;clear&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;element&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;value&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;element&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get_attribute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;value&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;value&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="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;char&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;element&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;send_keys&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Keys&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;BACK_SPACE&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;driver&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;webdriver&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Chrome&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="n"&gt;driver&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;https://www.google.com/&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;el&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;driver&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;find_element&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="n"&gt;NAME&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;q&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;el&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;send_keys&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;selenium&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;el&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;send_keys&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Keys&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;RETURN&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/div&gt;
&lt;p&gt;This should produce the &lt;a class="reference external" href="https://www.google.com/search?q=selenium"&gt;Google search results page for &amp;quot;selenium&amp;quot;&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;On the results page, the search field still has a &lt;tt class="docutils literal"&gt;name&lt;/tt&gt; value of &amp;quot;q&amp;quot; and now
is pre-filled with &amp;quot;selenium&amp;quot; for a &lt;tt class="docutils literal"&gt;value&lt;/tt&gt;. Although the name has not
changed, Selenium will need to find the element again because the &lt;em&gt;page&lt;/em&gt; has
changed. Add the following to the code to locate the element and use the custom
&lt;tt class="docutils literal"&gt;clear()&lt;/tt&gt; function to clear it:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;table class="highlighttable"&gt;&lt;tr&gt;&lt;td class="linenos"&gt;&lt;div class="linenodiv"&gt;&lt;pre&gt;&lt;span class="normal"&gt;16&lt;/span&gt;
&lt;span class="normal"&gt;17&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;div&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="n"&gt;el&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;driver&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;find_element&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="n"&gt;NAME&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;q&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;clear&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;el&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/div&gt;
&lt;p&gt;And it's gone!&lt;/p&gt;
&lt;blockquote&gt;
Overall, this &lt;tt class="docutils literal"&gt;BACK_SPACE&lt;/tt&gt; should be much more reliable than the
&lt;tt class="docutils literal"&gt;WebElement.clear()&lt;/tt&gt; method.&lt;/blockquote&gt;
&lt;/div&gt;
&lt;div class="section" id="waiting"&gt;
&lt;h3&gt;Waiting&lt;/h3&gt;
&lt;p&gt;&amp;quot;Waiting&amp;quot; in Selenium can be a deceptively complex problem. Up to this point,
all examples have relied on Selenium's own ability to wait for a page to finish
loading before taking any particular action. For simple tests, this may be a
perfectly sufficient course. But as tests and applications become more complex
this method may not always do the job.&lt;/p&gt;
&lt;p&gt;Selenium provides some useful tools for addressing this issue -&lt;/p&gt;
&lt;div class="section" id="implicit-waits"&gt;
&lt;h4&gt;Implicit waits&lt;/h4&gt;
&lt;p&gt;The easiest way to add some wiggle room is the &lt;tt class="docutils literal"&gt;WebDriver.implicitly_wait()&lt;/tt&gt;
method. This method accepts an integer input that defines how many seconds to
wait when executing any of the &lt;em&gt;find_element&lt;/em&gt; methods.&lt;/p&gt;
&lt;p&gt;The default implicit wait is zero (or no wait), so if a particular element is
not found immediately Selenium will raise a &lt;tt class="docutils literal"&gt;NoSuchElementException&lt;/tt&gt;. Let's
try to find an element with a &lt;tt class="docutils literal"&gt;name&lt;/tt&gt; attribute &amp;quot;query&amp;quot; on GitHub (there isn't
one):&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;table class="highlighttable"&gt;&lt;tr&gt;&lt;td class="linenos"&gt;&lt;div class="linenodiv"&gt;&lt;pre&gt;&lt;span class="normal"&gt;0&lt;/span&gt;
&lt;span class="normal"&gt;1&lt;/span&gt;
&lt;span class="normal"&gt;2&lt;/span&gt;
&lt;span class="normal"&gt;3&lt;/span&gt;
&lt;span class="normal"&gt;4&lt;/span&gt;
&lt;span class="normal"&gt;5&lt;/span&gt;
&lt;span class="normal"&gt;6&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;div&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;selenium&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;webdriver&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;selenium.webdriver.common.by&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;By&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;selenium.webdriver.common.keys&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Keys&lt;/span&gt;

&lt;span class="n"&gt;driver&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;webdriver&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Chrome&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="n"&gt;driver&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;https://www.github.com/&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;el&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;driver&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;find_element&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="n"&gt;NAME&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;query&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/div&gt;
&lt;p&gt;This code should result in a &lt;tt class="docutils literal"&gt;NoSuchElementException&lt;/tt&gt; pretty quickly after
Chrome loads GitHub's homepage.&lt;/p&gt;
&lt;p&gt;Now, let's try the code below, which sets an implicit wait time of five
seconds for the same impossible task:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;table class="highlighttable"&gt;&lt;tr&gt;&lt;td class="linenos"&gt;&lt;div class="linenodiv"&gt;&lt;pre&gt;&lt;span class="normal"&gt;0&lt;/span&gt;
&lt;span class="normal"&gt;1&lt;/span&gt;
&lt;span class="normal"&gt;2&lt;/span&gt;
&lt;span class="normal"&gt;3&lt;/span&gt;
&lt;span class="normal"&gt;4&lt;/span&gt;
&lt;span class="normal"&gt;5&lt;/span&gt;
&lt;span class="normal"&gt;6&lt;/span&gt;
&lt;span class="normal"&gt;7&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;div&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;selenium&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;webdriver&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;selenium.webdriver.common.by&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;By&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;selenium.webdriver.common.keys&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Keys&lt;/span&gt;

&lt;span class="n"&gt;driver&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;webdriver&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Chrome&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="n"&gt;driver&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;implicitly_wait&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;driver&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;https://www.github.com/&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;el&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;driver&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;find_element&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="n"&gt;NAME&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;query&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/div&gt;
&lt;p&gt;This code will produce the exact same exception, but it will wait five seconds
before doing so.&lt;/p&gt;
&lt;p&gt;While these examples paint a very simple picture, the reality is that various
conditions of any test environment or application will impact Selenium's
ability to determine when a page is loaded or whether or not an element exists.&lt;/p&gt;
&lt;blockquote&gt;
I recommend all tests &lt;strong&gt;set a 10 second implicit wait time&lt;/strong&gt;. This should
help to prevent intermittent exceptions caused by issues with underlying
elements like network connection or buggy web servers.&lt;/blockquote&gt;
&lt;/div&gt;
&lt;div class="section" id="expected-conditions-explicit-waits"&gt;
&lt;h4&gt;Expected conditions (&lt;em&gt;explicit&lt;/em&gt; waits)&lt;/h4&gt;
&lt;p&gt;When implicit waits are not enough, &lt;strong&gt;expected conditions&lt;/strong&gt; are extremely
valuable. The &lt;tt class="docutils literal"&gt;WebDriverWait&lt;/tt&gt; class provides the &lt;tt class="docutils literal"&gt;until()&lt;/tt&gt; and
&lt;tt class="docutils literal"&gt;until_not()&lt;/tt&gt; methods that can be used with &lt;tt class="docutils literal"&gt;expected_conditions&lt;/tt&gt; to create
more complex and nuanced wait conditions.&lt;/p&gt;
&lt;p&gt;There are many &lt;a class="reference external" href="https://seleniumhq.github.io/selenium/docs/api/py/webdriver_support/selenium.webdriver.support.expected_conditions.html"&gt;expected conditions&lt;/a&gt; available, but the one that I have
frequently come back to in my testing is &lt;tt class="docutils literal"&gt;presence_of_element_located()&lt;/tt&gt;.&lt;/p&gt;
&lt;p&gt;&lt;tt class="docutils literal"&gt;presence_of_element_located()&lt;/tt&gt; will take an object describing a method and
locator and return true if the object exists in the DOM. This can be used with
&lt;tt class="docutils literal"&gt;WebDriverWait.until()&lt;/tt&gt; and a wait time (in seconds) like so:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;table class="highlighttable"&gt;&lt;tr&gt;&lt;td class="linenos"&gt;&lt;div class="linenodiv"&gt;&lt;pre&gt;&lt;span class="normal"&gt;0&lt;/span&gt;
&lt;span class="normal"&gt;1&lt;/span&gt;
&lt;span class="normal"&gt;2&lt;/span&gt;
&lt;span class="normal"&gt;3&lt;/span&gt;
&lt;span class="normal"&gt;4&lt;/span&gt;
&lt;span class="normal"&gt;5&lt;/span&gt;
&lt;span class="normal"&gt;6&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;div&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;selenium&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;webdriver&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;selenium.webdriver.common.by&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;By&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;selenium.webdriver.support.ui&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;WebDriverWait&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;selenium.webdriver.support&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;expected_conditions&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;ec&lt;/span&gt;

&lt;span class="n"&gt;driver&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;webdriver&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Chrome&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="n"&gt;WebDriverWait&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;driver&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;until&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ec&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;presence_of_element_located&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="n"&gt;ID&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;html-id&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/div&gt;
&lt;p&gt;For a real example, the website &lt;a class="reference external" href="http://www.webcountdown.net/"&gt;webcountdown.net&lt;/a&gt; creates a countdown timer
that creates a pop-up in the DOM when the timer finishes. Selenium can handle
this using the above template:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;table class="highlighttable"&gt;&lt;tr&gt;&lt;td class="linenos"&gt;&lt;div class="linenodiv"&gt;&lt;pre&gt;&lt;span class="normal"&gt;0&lt;/span&gt;
&lt;span class="normal"&gt;1&lt;/span&gt;
&lt;span class="normal"&gt;2&lt;/span&gt;
&lt;span class="normal"&gt;3&lt;/span&gt;
&lt;span class="normal"&gt;4&lt;/span&gt;
&lt;span class="normal"&gt;5&lt;/span&gt;
&lt;span class="normal"&gt;6&lt;/span&gt;
&lt;span class="normal"&gt;7&lt;/span&gt;
&lt;span class="normal"&gt;8&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;div&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;selenium&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;webdriver&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;selenium.webdriver.common.by&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;By&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;selenium.webdriver.support.ui&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;WebDriverWait&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;selenium.webdriver.support&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;expected_conditions&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;ec&lt;/span&gt;

&lt;span class="n"&gt;driver&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;webdriver&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Chrome&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="n"&gt;driver&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;http://www.webcountdown.net/?c=3&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# Starts a 3 second timer.&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;WebDriverWait&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;driver&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;until&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ec&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;presence_of_element_located&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="n"&gt;ID&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;popupiframe&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;))):&lt;/span&gt;
    &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;Popup located!&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/div&gt;
&lt;p&gt;The above code should open the countdown page, run a three second countdown
and print &amp;quot;Popup located!&amp;quot; after the three second countdown completes. This
works because WebDriver is told to wait for up to five seconds for the popup to
appear.&lt;/p&gt;
&lt;p&gt;If, for example, this were modified with a &lt;em&gt;two&lt;/em&gt; second timeout for the
&lt;tt class="docutils literal"&gt;WebDriverWait&lt;/tt&gt; class, Selenium would raise a
&lt;tt class="docutils literal"&gt;selenium.common.exceptions.TimeoutException&lt;/tt&gt; because the timer does not
finish (and therefore does not create the element with ID &amp;quot;popupiframe&amp;quot;) before
the two seconds are up.&lt;/p&gt;
&lt;blockquote&gt;
What is &lt;tt class="docutils literal"&gt;WebDriverWait&lt;/tt&gt; good for? Briefly - single page apps (SPAs).&lt;/blockquote&gt;
&lt;p&gt;Testing may require traversing an app's navigational elements and if the page
is not fully reloading, Selenium will need to use &lt;tt class="docutils literal"&gt;WebDriverWait&lt;/tt&gt; to do
things like wait for a new section or table of data to load after an AJAX-style
API call.&lt;/p&gt;
&lt;p&gt;Other &lt;a class="reference external" href="https://seleniumhq.github.io/selenium/docs/api/py/webdriver_support/selenium.webdriver.support.expected_conditions.html"&gt;expected conditions&lt;/a&gt; will follow pretty much the same syntax and
mostly have (very) verbose names. Two of the others that I have found useful
in practice are &lt;tt class="docutils literal"&gt;text_to_be_present_in_element()&lt;/tt&gt; and
&lt;tt class="docutils literal"&gt;element_to_be_clickable()&lt;/tt&gt;.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="time-waits"&gt;
&lt;h4&gt;Time waits&lt;/h4&gt;
&lt;p&gt;Lastly, I have also used a workaround method to do simple, explicit time-based
waits without any expected conditions. One area where this happened to come in
handy for me is testing the result of a Javascript-based &amp;quot;stop watch&amp;quot; that
updates in real time. As part of a test, I initiate the stop watch, wait for
two seconds and then verify the displayed time to be correct. To achieve this,
I created a method that essentially does an expected conditional wait that
times out intentionally:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;table class="highlighttable"&gt;&lt;tr&gt;&lt;td class="linenos"&gt;&lt;div class="linenodiv"&gt;&lt;pre&gt;&lt;span class="normal"&gt;0&lt;/span&gt;
&lt;span class="normal"&gt;1&lt;/span&gt;
&lt;span class="normal"&gt;2&lt;/span&gt;
&lt;span class="normal"&gt;3&lt;/span&gt;
&lt;span class="normal"&gt;4&lt;/span&gt;
&lt;span class="normal"&gt;5&lt;/span&gt;
&lt;span class="normal"&gt;6&lt;/span&gt;
&lt;span class="normal"&gt;7&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;div&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;selenium&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;webdriver&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;selenium.webdriver.support.ui&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;WebDriverWait&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;wait&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;seconds&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;WebDriverWait&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;driver&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;seconds&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;until&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;lambda&lt;/span&gt; &lt;span class="n"&gt;driver&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="n"&gt;TimeoutException&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;pass&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/div&gt;
&lt;p&gt;This method can be used, for example to wait five seconds by calling
&lt;tt class="docutils literal"&gt;wait(5)&lt;/tt&gt;. &lt;tt class="docutils literal"&gt;WebDriverWait&lt;/tt&gt; will raise an exception after five seconds
because the &lt;tt class="docutils literal"&gt;until()&lt;/tt&gt; argument is a simple lambda that will &lt;em&gt;always&lt;/em&gt; return
&lt;tt class="docutils literal"&gt;False&lt;/tt&gt;. By catching and passing on the exception, this method just waits
for the specified number of seconds and nothing else. Handy!&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="section" id="improving-consistency-with-saucelabs"&gt;
&lt;h2&gt;Improving Consistency with SauceLabs&lt;/h2&gt;
&lt;p&gt;These basics are enough to get things going in Selenium, but over time as test
complexities increase and multiple developer environments evolve, consistency
will become a considerable pain. In our experience developing Timestrap, there
were inconsistencies causing test failures based on development OS (Windows,
OS X, Linux flavors, etc.), web drivers (Firefox, Chrome, gecko, etc.), and
seemingly the phases of the moon.&lt;/p&gt;
&lt;p&gt;After trying many different things to stabilize environments, we eventually
found and started using &lt;a class="reference external" href="https://saucelabs.com/"&gt;SauceLabs&lt;/a&gt;. SauceLabs provides a number of services
related to testing and a few &lt;a class="reference external" href="https://saucelabs.com/open-source"&gt;free tiers for open source projects&lt;/a&gt;, including
&lt;a class="reference external" href="https://saucelabs.com/open-source#cross-browser-testing"&gt;Cross Browser Testing&lt;/a&gt;. Using this service can help bring stability and
consistency to Selenium tests regardless of the local development environment.&lt;/p&gt;
&lt;p&gt;To get started, SauceLabs requires an existing, publicly accessible open
source repository (e.g. on GitHub, GitLab, etc.). Use the &lt;a class="reference external" href="https://saucelabs.com/beta/signup/OSS/None"&gt;OSS Sign Up&lt;/a&gt; page
with the &amp;quot;Open Sauce&amp;quot; plan to get started. Once signed up and logged in, there
are a couple of different ways to take advantage of SauceLabs testing:&lt;/p&gt;
&lt;div class="section" id="manual-tests"&gt;
&lt;h3&gt;Manual Tests&lt;/h3&gt;
&lt;p&gt;If you have an Internet accessible project available, &lt;a class="reference external" href="https://saucelabs.com/beta/manual"&gt;Manual Tests&lt;/a&gt; can be
used to poke around and get a feel for the various environments supported. This
can serve as a wonderfully quick and easy way to do some prodding from a
virtual browser in iOS, Android, OS X, Windows, Linux using various versions of
Safari, Chrome, Firefox, Internet Explorer and Opera. Once a session is
complete, the &lt;a class="reference external" href="https://saucelabs.com/beta/dashboard/manual"&gt;dashboard&lt;/a&gt; will have a log with screenshots and videos
available to view or download.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="automated-tests"&gt;
&lt;h3&gt;Automated Tests&lt;/h3&gt;
&lt;p&gt;While manual testing is quick and convenient, automated testing is the
important feature necessary to improve the consistency of Selenium tests in
Python overall. Running Python's Selenium tests through SauceLabs requires
three key things:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;a class="reference internal" href="#username-and-access-key"&gt;Username and Access Key&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="reference internal" href="#webdriver-remote"&gt;WebDriver.Remote&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="reference internal" href="#sauce-connect-proxy-client"&gt;Sauce Connect Proxy Client&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="section" id="username-and-access-key"&gt;
&lt;h4&gt;Username and Access Key&lt;/h4&gt;
&lt;p&gt;From a logged in SauceLabs account, the access key can be found on the
&lt;a class="reference external" href="https://saucelabs.com/beta/user-settings"&gt;User Settings&lt;/a&gt; page. This key and the associated username will need to be
available in the local test environment in order to execute the Selenium-driven
tests on SauceLabs.&lt;/p&gt;
&lt;blockquote&gt;
I recommend getting used to using the environment variables
&lt;tt class="docutils literal"&gt;SAUCE_USERNAME&lt;/tt&gt; and &lt;tt class="docutils literal"&gt;SAUCE_ACCESS_KEY&lt;/tt&gt; as these will be used by the
&lt;a class="reference internal" href="#sauce-connect-proxy-client"&gt;Sauce Connect Proxy Client&lt;/a&gt; for local development testing.&lt;/blockquote&gt;
&lt;p&gt;On Linux this can be achieved with:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="go"&gt;export SAUCE_USERNAME={sauce-username}&lt;/span&gt;
&lt;span class="go"&gt;export SAUCE_ACCESS_KEY={sauce-access-key}&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;div class="section" id="webdriver-remote"&gt;
&lt;h4&gt;WebDriver.Remote&lt;/h4&gt;
&lt;p&gt;Selenium provides a &lt;tt class="docutils literal"&gt;WebDriver.Remote&lt;/tt&gt; class for interacting with a
command-based remote server running the &lt;a class="reference external" href="https://w3c.github.io/webdriver/webdriver-spec.html#protocol"&gt;WebDriver protocol&lt;/a&gt;.  The class must
be initialized with two arguments, &lt;tt class="docutils literal"&gt;command_executor&lt;/tt&gt;, a URL pointing to the
remote command point, and &lt;tt class="docutils literal"&gt;desired_capabilities&lt;/tt&gt;, a dictionary of settings
for the executor.&lt;/p&gt;
&lt;p&gt;For SauceLabs, the &lt;tt class="docutils literal"&gt;command_executor&lt;/tt&gt; should be set to
&lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;http://SAUCE_USERNAME:SAUCE_ACCESS_KEY&amp;#64;ondemand.saucelabs.com/wd/hub&lt;/span&gt;&lt;/tt&gt;
where &lt;tt class="docutils literal"&gt;SAUCE_USERNAME&lt;/tt&gt; and &lt;tt class="docutils literal"&gt;SAUCE_ACCESS_KEY&lt;/tt&gt; represent the properties
outlined in the previous section of this post.&lt;/p&gt;
&lt;p&gt;The &lt;tt class="docutils literal"&gt;desired_capabilities&lt;/tt&gt; dictionary is used to provide the environment
settings to SauceLabs. SauceLabs has a wonderful &lt;a class="reference external" href="https://wiki.saucelabs.com/display/DOCS/Platform+Configurator#/"&gt;Platform Configurator&lt;/a&gt; tool
for easily selecting from the available options.&lt;/p&gt;
&lt;p&gt;To use the example below, the local environment must provide two variables:
&lt;tt class="docutils literal"&gt;SAUCE_USERNAME&lt;/tt&gt; and &lt;tt class="docutils literal"&gt;SAUCE_ACCESS_KEY&lt;/tt&gt;. With these variables set, the
following code will create a remote WebDriver set up to access SauceLabs using
Chrome 48 on a PC running Linux:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;table class="highlighttable"&gt;&lt;tr&gt;&lt;td class="linenos"&gt;&lt;div class="linenodiv"&gt;&lt;pre&gt;&lt;span class="normal"&gt; 0&lt;/span&gt;
&lt;span class="normal"&gt; 1&lt;/span&gt;
&lt;span class="normal"&gt; 2&lt;/span&gt;
&lt;span class="normal"&gt; 3&lt;/span&gt;
&lt;span class="normal"&gt; 4&lt;/span&gt;
&lt;span class="normal"&gt; 5&lt;/span&gt;
&lt;span class="normal"&gt; 6&lt;/span&gt;
&lt;span class="normal"&gt; 7&lt;/span&gt;
&lt;span class="normal"&gt; 8&lt;/span&gt;
&lt;span class="normal"&gt; 9&lt;/span&gt;
&lt;span class="normal"&gt;10&lt;/span&gt;
&lt;span class="normal"&gt;11&lt;/span&gt;
&lt;span class="normal"&gt;12&lt;/span&gt;
&lt;span class="normal"&gt;13&lt;/span&gt;
&lt;span class="normal"&gt;14&lt;/span&gt;
&lt;span class="normal"&gt;15&lt;/span&gt;
&lt;span class="normal"&gt;16&lt;/span&gt;
&lt;span class="normal"&gt;17&lt;/span&gt;
&lt;span class="normal"&gt;18&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;div&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;os&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;selenium&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;webdriver&lt;/span&gt;

&lt;span class="c1"&gt;# Get the user name and access key from the environment.&lt;/span&gt;
&lt;span class="n"&gt;sauce_username&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;environ&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;SAUCE_USERNAME&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="n"&gt;sauce_access_key&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;environ&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;SAUCE_ACCESS_KEY&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

&lt;span class="c1"&gt;# Build the command executor URL.&lt;/span&gt;
&lt;span class="n"&gt;url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;http://&lt;/span&gt;&lt;span class="si"&gt;{}&lt;/span&gt;&lt;span class="s1"&gt;:&lt;/span&gt;&lt;span class="si"&gt;{}&lt;/span&gt;&lt;span class="s1"&gt;@ondemand.saucelabs.com/wd/hub&amp;#39;&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;format&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;sauce_username&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;sauce_access_key&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# Build the capabilities dictionary (from Platform Configurator).&lt;/span&gt;
&lt;span class="n"&gt;caps&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;browserName&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;chrome&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="n"&gt;caps&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;platform&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;Linux&amp;quot;&lt;/span&gt;
&lt;span class="n"&gt;caps&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;version&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;48.0&amp;quot;&lt;/span&gt;

&lt;span class="n"&gt;driver&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;webdriver&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Remote&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;command_executor&lt;/span&gt;&lt;span class="o"&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;desired_capabilities&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;caps&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;driver&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;https://www.google.com&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;driver&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;quit&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/div&gt;
&lt;p&gt;After executing the above sequence, the &lt;a class="reference external" href="https://saucelabs.com/beta/dashboard/tests"&gt;SauceLabs Dashboard&lt;/a&gt; should show a
new job with video and screenshots of Chrome on Linux loading Google. Neat!&lt;/p&gt;
&lt;p&gt;When using Chrome, a &lt;tt class="docutils literal"&gt;chromeOptions&lt;/tt&gt; dictionary can also be provided in the
&lt;tt class="docutils literal"&gt;desired_capabilities&lt;/tt&gt; dictionary with some &lt;a class="reference external" href="https://sites.google.com/a/chromium.org/chromedriver/capabilities"&gt;more specific settings&lt;/a&gt;. Within
&lt;em&gt;that&lt;/em&gt; dictionary, a &lt;tt class="docutils literal"&gt;prefs&lt;/tt&gt; dictionary can also be used to set further
preferences. For instance, if testing needs to be done on an app that requires
login, it may be helpful to use this &lt;tt class="docutils literal"&gt;chromeOptions&lt;/tt&gt; dictionary:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;table class="highlighttable"&gt;&lt;tr&gt;&lt;td class="linenos"&gt;&lt;div class="linenodiv"&gt;&lt;pre&gt;&lt;span class="normal"&gt;0&lt;/span&gt;
&lt;span class="normal"&gt;1&lt;/span&gt;
&lt;span class="normal"&gt;2&lt;/span&gt;
&lt;span class="normal"&gt;3&lt;/span&gt;
&lt;span class="normal"&gt;4&lt;/span&gt;
&lt;span class="normal"&gt;5&lt;/span&gt;
&lt;span class="normal"&gt;6&lt;/span&gt;
&lt;span class="normal"&gt;7&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;div&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="n"&gt;caps&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;chromeOptions&amp;#39;&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="s1"&gt;&amp;#39;prefs&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="s1"&gt;&amp;#39;credentials_enable_service&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;False&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="s1"&gt;&amp;#39;profile&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="s1"&gt;&amp;#39;password_manager_enabled&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;False&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;/pre&gt;&lt;/div&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/div&gt;
&lt;p&gt;Very simply, this prevents the &amp;quot;Do you want to save your password?&amp;quot; sort of
dialog box from appearing in all screenshots of a test session after login.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="sauce-connect-proxy-client"&gt;
&lt;h4&gt;Sauce Connect Proxy Client&lt;/h4&gt;
&lt;p&gt;All of this works great just as described... if the app being tested happens to
be available on the public Internet. If that is not the case (and it probably
isn't), SauceLabs provides the &lt;a class="reference external" href="https://wiki.saucelabs.com/display/DOCS/Sauce+Connect+Proxy"&gt;Sauce Connect Proxy&lt;/a&gt; to connect to your local
app.&lt;/p&gt;
&lt;p&gt;On Linux, for example, the proxy client can be installed like so:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="go"&gt;wget https://saucelabs.com/downloads/sc-4.4.9-linux.tar.gz&lt;/span&gt;
&lt;span class="go"&gt;sudo mkdir -p /usr/local/bin/&lt;/span&gt;
&lt;span class="go"&gt;tar xzf sc-4.4.9-linux.tar.gz&lt;/span&gt;
&lt;span class="go"&gt;mv sc-4.4.9-linux/bin/sc /usr/local/bin/&lt;/span&gt;
&lt;span class="go"&gt;sudo chmod +x /usr/local/bin/sc&lt;/span&gt;
&lt;span class="go"&gt;sc --version&lt;/span&gt;
&lt;span class="gp"&gt;#&lt;/span&gt;Sauce&lt;span class="w"&gt; &lt;/span&gt;Connect&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;4&lt;/span&gt;.4.9,&lt;span class="w"&gt; &lt;/span&gt;build&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;3688&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;098cbcf&lt;span class="w"&gt; &lt;/span&gt;-dirty
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;The &lt;tt class="docutils literal"&gt;sc&lt;/tt&gt; command will make use of the &lt;tt class="docutils literal"&gt;SAUCE_USERNAME&lt;/tt&gt; and
&lt;tt class="docutils literal"&gt;SAUCE_ACCESS_KEY&lt;/tt&gt; environment variables. When executed with no parameters,
the proxy client will run through some initialization leading to the message,
&lt;strong&gt;Sauce Connect is up, you may start your tests.&lt;/strong&gt; From here the client will
simply sit and listen for commands and the &lt;a class="reference external" href="https://saucelabs.com/beta/tunnels"&gt;SauceLabs Tunnels&lt;/a&gt; page should
show the client as active.&lt;/p&gt;
&lt;blockquote&gt;
With all of this in place, tests against a local development server can now
be proxied up to SauceLabs and run in a considerably more consistent
environment!&lt;/blockquote&gt;
&lt;p&gt;This method &lt;strong&gt;significantly&lt;/strong&gt; improved the test infrastructure for Timestrap
and allowed us to refocus on development instead of testing.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="section" id="putting-it-all-together"&gt;
&lt;h2&gt;Putting It All Together&lt;/h2&gt;
&lt;p&gt;We can bring all this together in one (admittedly somewhat complex) Python test
file:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;table class="highlighttable"&gt;&lt;tr&gt;&lt;td class="linenos"&gt;&lt;div class="linenodiv"&gt;&lt;pre&gt;&lt;span class="normal"&gt; 0&lt;/span&gt;
&lt;span class="normal"&gt; 1&lt;/span&gt;
&lt;span class="normal"&gt; 2&lt;/span&gt;
&lt;span class="normal"&gt; 3&lt;/span&gt;
&lt;span class="normal"&gt; 4&lt;/span&gt;
&lt;span class="normal"&gt; 5&lt;/span&gt;
&lt;span class="normal"&gt; 6&lt;/span&gt;
&lt;span class="normal"&gt; 7&lt;/span&gt;
&lt;span class="normal"&gt; 8&lt;/span&gt;
&lt;span class="normal"&gt; 9&lt;/span&gt;
&lt;span class="normal"&gt;10&lt;/span&gt;
&lt;span class="normal"&gt;11&lt;/span&gt;
&lt;span class="normal"&gt;12&lt;/span&gt;
&lt;span class="normal"&gt;13&lt;/span&gt;
&lt;span class="normal"&gt;14&lt;/span&gt;
&lt;span class="normal"&gt;15&lt;/span&gt;
&lt;span class="normal"&gt;16&lt;/span&gt;
&lt;span class="normal"&gt;17&lt;/span&gt;
&lt;span class="normal"&gt;18&lt;/span&gt;
&lt;span class="normal"&gt;19&lt;/span&gt;
&lt;span class="normal"&gt;20&lt;/span&gt;
&lt;span class="normal"&gt;21&lt;/span&gt;
&lt;span class="normal"&gt;22&lt;/span&gt;
&lt;span class="normal"&gt;23&lt;/span&gt;
&lt;span class="normal"&gt;24&lt;/span&gt;
&lt;span class="normal"&gt;25&lt;/span&gt;
&lt;span class="normal"&gt;26&lt;/span&gt;
&lt;span class="normal"&gt;27&lt;/span&gt;
&lt;span class="normal"&gt;28&lt;/span&gt;
&lt;span class="normal"&gt;29&lt;/span&gt;
&lt;span class="normal"&gt;30&lt;/span&gt;
&lt;span class="normal"&gt;31&lt;/span&gt;
&lt;span class="normal"&gt;32&lt;/span&gt;
&lt;span class="normal"&gt;33&lt;/span&gt;
&lt;span class="normal"&gt;34&lt;/span&gt;
&lt;span class="normal"&gt;35&lt;/span&gt;
&lt;span class="normal"&gt;36&lt;/span&gt;
&lt;span class="normal"&gt;37&lt;/span&gt;
&lt;span class="normal"&gt;38&lt;/span&gt;
&lt;span class="normal"&gt;39&lt;/span&gt;
&lt;span class="normal"&gt;40&lt;/span&gt;
&lt;span class="normal"&gt;41&lt;/span&gt;
&lt;span class="normal"&gt;42&lt;/span&gt;
&lt;span class="normal"&gt;43&lt;/span&gt;
&lt;span class="normal"&gt;44&lt;/span&gt;
&lt;span class="normal"&gt;45&lt;/span&gt;
&lt;span class="normal"&gt;46&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;div&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;os&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;threading&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;time&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;unittest&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;http.server&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;BaseHTTPRequestHandler&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;HTTPServer&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;selenium&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;webdriver&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;selenium.webdriver.common.by&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;By&lt;/span&gt;


&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;TestHandler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;BaseHTTPRequestHandler&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;do_GET&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;send_response&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;send_header&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;Content-type&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;text/html&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;end_headers&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;wfile&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;write&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;b&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;&amp;lt;html&amp;gt;&amp;lt;head&amp;gt;&amp;lt;title&amp;gt;Python Selenium!&amp;lt;/title&amp;gt;&amp;lt;/head&amp;gt;&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;wfile&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;write&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;b&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;&amp;lt;body&amp;gt;&amp;lt;div id=&amp;quot;main&amp;quot;&amp;gt;Hello!&amp;lt;/div&amp;gt;&amp;lt;/body&amp;gt;&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;wfile&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;write&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;b&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;&amp;lt;/body&amp;gt;&amp;lt;/html&amp;gt;&amp;#39;&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;TestRequest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;unittest&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;TestCase&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="nd"&gt;@classmethod&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;setUpClass&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;cls&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;server&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;HTTPServer&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;127.0.0.1&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;8000&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;TestHandler&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="bp"&gt;cls&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;server_thread&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;threading&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Thread&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;target&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;server&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;serve_forever&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;daemon&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="bp"&gt;cls&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;server_thread&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;start&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sleep&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;sauce_username&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;environ&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;SAUCE_USERNAME&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
        &lt;span class="n"&gt;sauce_access_key&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;environ&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;SAUCE_ACCESS_KEY&amp;#39;&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="s1"&gt;&amp;#39;http://&lt;/span&gt;&lt;span class="si"&gt;{}&lt;/span&gt;&lt;span class="s1"&gt;:&lt;/span&gt;&lt;span class="si"&gt;{}&lt;/span&gt;&lt;span class="s1"&gt;@ondemand.saucelabs.com/wd/hub&amp;#39;&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;format&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;sauce_username&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;sauce_access_key&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;caps&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;browserName&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;chrome&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="n"&gt;caps&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;platform&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;Linux&amp;quot;&lt;/span&gt;
        &lt;span class="n"&gt;caps&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;version&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;48.0&amp;quot;&lt;/span&gt;
        &lt;span class="bp"&gt;cls&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;driver&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;webdriver&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Remote&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;command_executor&lt;/span&gt;&lt;span class="o"&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;desired_capabilities&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;caps&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="nd"&gt;@classmethod&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;tearDownClass&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;cls&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="bp"&gt;cls&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;driver&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;quit&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;test_request&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;driver&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;http://127.0.0.1:8000&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;assertEqual&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;Hello!&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;driver&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;find_element&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="n"&gt;ID&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;main&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;


&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="vm"&gt;__name__&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;__main__&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;unittest&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/div&gt;
&lt;p&gt;&lt;tt class="docutils literal"&gt;TestHandler.do_GET()&lt;/tt&gt; is a very simple method for &lt;tt class="docutils literal"&gt;http.server&lt;/tt&gt; that
returns the following HTML:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;table class="highlighttable"&gt;&lt;tr&gt;&lt;td class="linenos"&gt;&lt;div class="linenodiv"&gt;&lt;pre&gt;&lt;span class="normal"&gt;0&lt;/span&gt;
&lt;span class="normal"&gt;1&lt;/span&gt;
&lt;span class="normal"&gt;2&lt;/span&gt;
&lt;span class="normal"&gt;3&lt;/span&gt;
&lt;span class="normal"&gt;4&lt;/span&gt;
&lt;span class="normal"&gt;5&lt;/span&gt;
&lt;span class="normal"&gt;6&lt;/span&gt;
&lt;span class="normal"&gt;7&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;div&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;html&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;head&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;title&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;Python Selenium!&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;title&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;head&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;body&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;main&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;Hello!&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;body&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;html&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/div&gt;
&lt;p&gt;&lt;tt class="docutils literal"&gt;TestRequest.setUpClass()&lt;/tt&gt; does three import things before running the
tests:&lt;/p&gt;
&lt;ol class="arabic simple"&gt;
&lt;li&gt;Establishes the &lt;tt class="docutils literal"&gt;HTTPServer&lt;/tt&gt; instance.&lt;/li&gt;
&lt;li&gt;Starts the HTTP server &lt;em&gt;in a thread&lt;/em&gt; (to prevent blocking).&lt;/li&gt;
&lt;li&gt;Establishes the &lt;tt class="docutils literal"&gt;WebDriver.Remote&lt;/tt&gt; instance using SauceLab as the command executor.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;tt class="docutils literal"&gt;TestRequest.tearDownClass()&lt;/tt&gt; simply shuts down the web driver.&lt;/p&gt;
&lt;p&gt;Lastly, &lt;tt class="docutils literal"&gt;TestRequest.test_request()&lt;/tt&gt; is the single test in this &amp;quot;suite&amp;quot;. It
simply loads the test server index page and asserts that the text &amp;quot;Hello!&amp;quot; is
present inside div#main (which it should be).&lt;/p&gt;
&lt;p&gt;Let's give it a try! Remember to set the &lt;tt class="docutils literal"&gt;SAUCE_USERNAME&lt;/tt&gt; and
&lt;tt class="docutils literal"&gt;SAUCE_ACCESS_KEY&lt;/tt&gt; environment variables, first:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="go"&gt;export SAUCE_USERNAME={sauce-username}&lt;/span&gt;
&lt;span class="go"&gt;export SAUCE_ACCESS_KEY={sauce-access-key}&lt;/span&gt;
&lt;span class="go"&gt;python tests.py&lt;/span&gt;
&lt;span class="gp"&gt;#&lt;/span&gt;&lt;span class="nv"&gt;E&lt;/span&gt;
&lt;span class="gp"&gt;#&lt;/span&gt;&lt;span class="o"&gt;======================================================================&lt;/span&gt;
&lt;span class="gp"&gt;#&lt;/span&gt;ERROR:&lt;span class="w"&gt; &lt;/span&gt;test_request&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;__main__.TestRequest&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="gp"&gt;#&lt;/span&gt;----------------------------------------------------------------------
&lt;span class="gp"&gt;#&lt;/span&gt;Traceback&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;most&lt;span class="w"&gt; &lt;/span&gt;recent&lt;span class="w"&gt; &lt;/span&gt;call&lt;span class="w"&gt; &lt;/span&gt;last&lt;span class="o"&gt;)&lt;/span&gt;:
&lt;span class="gp"&gt;#&lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;...&lt;span class="o"&gt;]&lt;/span&gt;
&lt;span class="gp"&gt;#&lt;/span&gt;selenium.common.exceptions.NoSuchElementException:&lt;span class="w"&gt; &lt;/span&gt;Message:&lt;span class="w"&gt; &lt;/span&gt;no&lt;span class="w"&gt; &lt;/span&gt;such&lt;span class="w"&gt; &lt;/span&gt;element:&lt;span class="w"&gt; &lt;/span&gt;Unable&lt;span class="w"&gt; &lt;/span&gt;to&lt;span class="w"&gt; &lt;/span&gt;locate&lt;span class="w"&gt; &lt;/span&gt;element:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;method&amp;quot;&lt;/span&gt;:&lt;span class="s2"&gt;&amp;quot;id&amp;quot;&lt;/span&gt;,&lt;span class="s2"&gt;&amp;quot;selector&amp;quot;&lt;/span&gt;:&lt;span class="s2"&gt;&amp;quot;main&amp;quot;&lt;/span&gt;&lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="gp"&gt;#  &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;Session&lt;span class="w"&gt; &lt;/span&gt;info:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;chrome&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;48&lt;/span&gt;.0.2564.97&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="gp"&gt;#  &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;Driver&lt;span class="w"&gt; &lt;/span&gt;info:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;chromedriver&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;2&lt;/span&gt;.21.371459&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;36d3d07f660ff2bc1bf28a75d1cdabed0983e7c4&lt;span class="o"&gt;)&lt;/span&gt;,platform&lt;span class="o"&gt;=&lt;/span&gt;Linux&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;3&lt;/span&gt;.13.0-83-generic&lt;span class="w"&gt; &lt;/span&gt;x86&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="gp"&gt;#&lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;...&lt;span class="o"&gt;]&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Oh no! What happened? The important bit in the traceback is this:
&lt;strong&gt;Message: no such element: Unable to locate element: {&amp;quot;method&amp;quot;:&amp;quot;id&amp;quot;,&amp;quot;selector&amp;quot;:&amp;quot;main&amp;quot;}&lt;/strong&gt;.
For some reason, Selenium was not able to find the div#main element. Since this
test ran in SauceLabs, the &lt;a class="reference external" href="https://saucelabs.com/beta/dashboard/tests"&gt;SauceLabs Dashboard&lt;/a&gt; has information and a replay
of the test session which reveals... oh... SauceLabs was trying to access
the local network (127.0.0.1) and we forgot to start the proxy client. Oops!&lt;/p&gt;
&lt;p&gt;Let's try that one more time, this time starting up the &lt;a class="reference external" href="https://wiki.saucelabs.com/display/DOCS/Sauce+Connect+Proxy"&gt;Sauce Connect Proxy&lt;/a&gt;
(&lt;tt class="docutils literal"&gt;sc&lt;/tt&gt;) before running the tests...&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="go"&gt;export SAUCE_USERNAME={sauce-username}&lt;/span&gt;
&lt;span class="go"&gt;export SAUCE_ACCESS_KEY={sauce-access-key}&lt;/span&gt;
&lt;span class="go"&gt;sc &amp;amp;&lt;/span&gt;
&lt;span class="gp"&gt;#&lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;...&lt;span class="o"&gt;]&lt;/span&gt;
&lt;span class="gp"&gt;#&lt;/span&gt;Sauce&lt;span class="w"&gt; &lt;/span&gt;Connect&lt;span class="w"&gt; &lt;/span&gt;is&lt;span class="w"&gt; &lt;/span&gt;up,&lt;span class="w"&gt; &lt;/span&gt;you&lt;span class="w"&gt; &lt;/span&gt;may&lt;span class="w"&gt; &lt;/span&gt;start&lt;span class="w"&gt; &lt;/span&gt;your&lt;span class="w"&gt; &lt;/span&gt;tests.
&lt;span class="go"&gt;python tests.py&lt;/span&gt;
&lt;span class="gp"&gt;#&lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;...&lt;span class="o"&gt;]&lt;/span&gt;
&lt;span class="gp"&gt;#&lt;/span&gt;.
&lt;span class="gp"&gt;#&lt;/span&gt;----------------------------------------------------------------------
&lt;span class="gp"&gt;#&lt;/span&gt;Ran&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;test&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;in&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;6&lt;/span&gt;.026s
&lt;span class="gp"&gt;#&lt;/span&gt;
&lt;span class="gp"&gt;#&lt;/span&gt;OK
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;&lt;em&gt;Note: Don't forget to kill the&lt;/em&gt; &lt;tt class="docutils literal"&gt;sc&lt;/tt&gt; &lt;em&gt;process with, for example,&lt;/em&gt; &lt;tt class="docutils literal"&gt;pkill &lt;span class="pre"&gt;-x&lt;/span&gt; sc&lt;/tt&gt;.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Hooray&lt;/strong&gt;! This time the test ran successfully because SauceLabs was able to
use the proxy client to access the local test server.&lt;/p&gt;
&lt;blockquote&gt;
This means that the local development environment can still be used for
testing without having to deploy between tests.&lt;/blockquote&gt;
&lt;p&gt;There you have it. With a local test server up and running, getting consistent
results from Selenium can be incredibly smooth and save many, many testing
headaches as the code base and developer contributions expand (hopefully!).&lt;/p&gt;
&lt;p class="center em"&gt;&lt;a class="reference external" href="https://news.ycombinator.com/item?id=15155412"&gt;Discussion at Hacker News&lt;/a&gt;&lt;/p&gt;
&lt;/div&gt;
</content><category term="Technology"></category><category term="django"></category><category term="python"></category><category term="saucelabs"></category><category term="selenium"></category><category term="testing"></category><category term="timestrap"></category></entry><entry><title>Buying Lotion on Amazon.com</title><link href="https://www.chris-wells.net/articles/2017/06/11/buying-lotion-amazon-com" rel="alternate"></link><published>2017-06-11T13:45:00-04:00</published><updated>2017-06-11T13:45:00-04:00</updated><author><name>Christopher Charbonneau Wells</name></author><id>tag:www.chris-wells.net,2017-06-11:/articles/2017/06/11/buying-lotion-amazon-com</id><summary type="html">
&lt;img alt="Amazon.com's Aveeno purchase options." class="float-right" src="/static/images/2017/amazoncom-aveeno-options.png" /&gt;
&lt;p&gt;I was recently shopping online for a specific type of lotion (my skin seems to
hate all other types). I am somewhat predisposed to avoid Amazon.com because
its size and increasing dominance of online shopping concerns me as it moves
closer and closer to &amp;quot;shopping&amp;quot; (in the sense that &amp;quot;searching&amp;quot; means Google to
most people). However, my biggest reason for avoiding Amazon.com is simpler:
it has become incredibly &lt;em&gt;confusing&lt;/em&gt; to shop there. Searching for just about
anything will yield thousands of results and it takes (me) a lot of effort to
determine which one is appropriate. This can be a great thing in a lot of
cases, but more often than not it feels more like navigating a minefield of
deceitful listings than comparing competing products.&lt;/p&gt;
&lt;p&gt;This lengthy post evaluates &lt;strong&gt;one&lt;/strong&gt; example - Aveeno &amp;quot;Daily Moisturizing&amp;quot;
lotion. As with most personal care products, when searching online I pull up a
couple of websites to compare prices. In this case I pulled up Amazon.com last,
searched for &amp;quot;Aveeno&amp;quot; and was impressed to see just what I was looking to
compare (the 18 oz., non-SPF version) as the first result. I clicked through
and, curious to figure out the price per ounce, looked to the &amp;quot;add to cart&amp;quot;
area. What are are my options?&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;Sizes select list with &lt;strong&gt;37 sizes&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Styles (??) select list with &lt;strong&gt;three styles&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;&amp;quot;Subscribe &amp;amp; Save&amp;quot; (pre-selected) and &amp;quot;One-time purchase&amp;quot; radio buttons.&lt;/li&gt;
&lt;li&gt;&amp;quot;Qty&amp;quot; select list.&lt;/li&gt;
&lt;li&gt;&amp;quot;Delivery every&amp;quot; select list (2 months pre-selected).&lt;/li&gt;
&lt;li&gt;&amp;quot;Subscribe now&amp;quot; button.&lt;/li&gt;
&lt;li&gt;&amp;quot;Add to List&amp;quot; button.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Three&lt;/strong&gt; &amp;quot;Add to Cart&amp;quot; buttons with &lt;em&gt;different prices&lt;/em&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Well, this is going to be complicated...&lt;/p&gt;
</summary><content type="html">&lt;span class="target" id="top"&gt;&lt;/span&gt;
&lt;img alt="Amazon.com's Aveeno purchase options." class="float-right" src="/static/images/2017/amazoncom-aveeno-options.png" /&gt;
&lt;p&gt;I was recently shopping online for a specific type of lotion (my skin seems to
hate all other types). I am somewhat predisposed to avoid Amazon.com because
its size and increasing dominance of online shopping concerns me as it moves
closer and closer to &amp;quot;shopping&amp;quot; (in the sense that &amp;quot;searching&amp;quot; means Google to
most people). However, my biggest reason for avoiding Amazon.com is simpler:
it has become incredibly &lt;em&gt;confusing&lt;/em&gt; to shop there. Searching for just about
anything will yield thousands of results and it takes (me) a lot of effort to
determine which one is appropriate. This can be a great thing in a lot of
cases, but more often than not it feels more like navigating a minefield of
deceitful listings than comparing competing products.&lt;/p&gt;
&lt;p&gt;This lengthy post evaluates &lt;strong&gt;one&lt;/strong&gt; example - Aveeno &amp;quot;Daily Moisturizing&amp;quot;
lotion. As with most personal care products, when searching online I pull up a
couple of websites to compare prices. In this case I pulled up Amazon.com last,
searched for &amp;quot;Aveeno&amp;quot; and was impressed to see just what I was looking to
compare (the 18 oz., non-SPF version) as the first result. I clicked through
and, curious to figure out the price per ounce, looked to the &amp;quot;add to cart&amp;quot;
area. What are are my options?&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;Sizes select list with &lt;strong&gt;37 sizes&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Styles (??) select list with &lt;strong&gt;three styles&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;&amp;quot;Subscribe &amp;amp; Save&amp;quot; (pre-selected) and &amp;quot;One-time purchase&amp;quot; radio buttons.&lt;/li&gt;
&lt;li&gt;&amp;quot;Qty&amp;quot; select list.&lt;/li&gt;
&lt;li&gt;&amp;quot;Delivery every&amp;quot; select list (2 months pre-selected).&lt;/li&gt;
&lt;li&gt;&amp;quot;Subscribe now&amp;quot; button.&lt;/li&gt;
&lt;li&gt;&amp;quot;Add to List&amp;quot; button.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Three&lt;/strong&gt; &amp;quot;Add to Cart&amp;quot; buttons with &lt;em&gt;different prices&lt;/em&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Well, this is going to be complicated...&lt;/p&gt;

&lt;div class="section" id="sizes"&gt;
&lt;h2&gt;Sizes&lt;/h2&gt;
&lt;p&gt;Let's get straight to it - there are 37 different &amp;quot;sizes&amp;quot; of this lotion, here
is a look at all of them, sorted by price per ounce.&lt;/p&gt;
&lt;table border="1" class="docutils"&gt;
&lt;caption&gt;37 Flavors (of Lotion)&lt;/caption&gt;
&lt;colgroup&gt;
&lt;col width="40%" /&gt;
&lt;col width="10%" /&gt;
&lt;col width="10%" /&gt;
&lt;col width="10%" /&gt;
&lt;col width="10%" /&gt;
&lt;col width="20%" /&gt;
&lt;/colgroup&gt;
&lt;thead valign="bottom"&gt;
&lt;tr&gt;&lt;th class="head stub"&gt;Size&lt;/th&gt;
&lt;th class="head"&gt;Price&lt;/th&gt;
&lt;th class="head"&gt;Units&lt;/th&gt;
&lt;th class="head"&gt;Oz./unit&lt;/th&gt;
&lt;th class="head"&gt;Total oz.&lt;/th&gt;
&lt;th class="head"&gt;Price/oz.&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody valign="top"&gt;
&lt;tr&gt;&lt;th class="stub"&gt;2 Pack 20 ounces each&lt;/th&gt;
&lt;td&gt;$17.68&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;20&lt;/td&gt;
&lt;td&gt;40&lt;/td&gt;
&lt;td&gt;$0.4420&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;th class="stub"&gt;18 Fluid Ounce&lt;/th&gt;
&lt;td&gt;$8.99&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;18&lt;/td&gt;
&lt;td&gt;18&lt;/td&gt;
&lt;td&gt;$0.4994&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;th class="stub"&gt;12 Fluid Ounce (Pack of 6)&lt;/th&gt;
&lt;td&gt;$52.53&lt;/td&gt;
&lt;td&gt;6&lt;/td&gt;
&lt;td&gt;12&lt;/td&gt;
&lt;td&gt;72&lt;/td&gt;
&lt;td&gt;$0.7296&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;th class="stub"&gt;12 Fluid Ounce&lt;/th&gt;
&lt;td&gt;$9.32&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;12&lt;/td&gt;
&lt;td&gt;12&lt;/td&gt;
&lt;td&gt;$0.7767&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;th class="stub"&gt;8 Fluid Ounce (Pack of 3)&lt;/th&gt;
&lt;td&gt;$23.05&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;8&lt;/td&gt;
&lt;td&gt;24&lt;/td&gt;
&lt;td&gt;$0.9604&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;th class="stub"&gt;1 Fluid Ounce (Pack of 36)&lt;/th&gt;
&lt;td&gt;$34.92&lt;/td&gt;
&lt;td&gt;36&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;36&lt;/td&gt;
&lt;td&gt;$0.9700&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;th class="stub"&gt;12 Fluid Ounce (Pack of 2)&lt;/th&gt;
&lt;td&gt;$27.56&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;12&lt;/td&gt;
&lt;td&gt;24&lt;/td&gt;
&lt;td&gt;$1.1483&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;th class="stub"&gt;2.5 Ounce (Pack of 2)&lt;/th&gt;
&lt;td&gt;$5.92&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;2.5&lt;/td&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;$1.1840&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;th class="stub"&gt;18 Ounce (Pack of 3)&lt;/th&gt;
&lt;td&gt;$99.99&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;18&lt;/td&gt;
&lt;td&gt;54&lt;/td&gt;
&lt;td&gt;$1.8517&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;th class="stub"&gt;4Pack (18 Ounce)&lt;/th&gt;
&lt;td&gt;$140.16&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;18&lt;/td&gt;
&lt;td&gt;72&lt;/td&gt;
&lt;td&gt;$1.9467&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;th class="stub"&gt;2Pack (18 Ounce)&lt;/th&gt;
&lt;td&gt;$70.11&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;18&lt;/td&gt;
&lt;td&gt;36&lt;/td&gt;
&lt;td&gt;$1.9475&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;th class="stub"&gt;5Pack (18 Ounce)&lt;/th&gt;
&lt;td&gt;$176.25&lt;/td&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;18&lt;/td&gt;
&lt;td&gt;90&lt;/td&gt;
&lt;td&gt;$1.9583&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;th class="stub"&gt;3Pack (18 Ounce)&lt;/th&gt;
&lt;td&gt;$108.19&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;18&lt;/td&gt;
&lt;td&gt;54&lt;/td&gt;
&lt;td&gt;$2.0035&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;th class="stub"&gt;5 Pack (6 Count 12 oz bottles)&lt;/th&gt;
&lt;td&gt;$731.35&lt;/td&gt;
&lt;td&gt;30&lt;/td&gt;
&lt;td&gt;12&lt;/td&gt;
&lt;td&gt;360&lt;/td&gt;
&lt;td&gt;$2.0315&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;th class="stub"&gt;4 Pack (6 Count 12 oz bottles)&lt;/th&gt;
&lt;td&gt;$585.13&lt;/td&gt;
&lt;td&gt;24&lt;/td&gt;
&lt;td&gt;12&lt;/td&gt;
&lt;td&gt;288&lt;/td&gt;
&lt;td&gt;$2.0317&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;th class="stub"&gt;2 Pack (6 Count 12 oz bottles)&lt;/th&gt;
&lt;td&gt;$292.58&lt;/td&gt;
&lt;td&gt;12&lt;/td&gt;
&lt;td&gt;12&lt;/td&gt;
&lt;td&gt;144&lt;/td&gt;
&lt;td&gt;$2.0318&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;th class="stub"&gt;4Pack 20 ounces&lt;/th&gt;
&lt;td&gt;$176.25&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;20&lt;/td&gt;
&lt;td&gt;80&lt;/td&gt;
&lt;td&gt;$2.2031&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;th class="stub"&gt;10Pack 20 ounces&lt;/th&gt;
&lt;td&gt;$450.21&lt;/td&gt;
&lt;td&gt;10&lt;/td&gt;
&lt;td&gt;20&lt;/td&gt;
&lt;td&gt;200&lt;/td&gt;
&lt;td&gt;$2.2511&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;th class="stub"&gt;8Pack 20 ounces&lt;/th&gt;
&lt;td&gt;$360.23&lt;/td&gt;
&lt;td&gt;8&lt;/td&gt;
&lt;td&gt;20&lt;/td&gt;
&lt;td&gt;160&lt;/td&gt;
&lt;td&gt;$2.2514&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;th class="stub"&gt;6Pack 20 ounces&lt;/th&gt;
&lt;td&gt;$270.18&lt;/td&gt;
&lt;td&gt;6&lt;/td&gt;
&lt;td&gt;20&lt;/td&gt;
&lt;td&gt;120&lt;/td&gt;
&lt;td&gt;$2.2515&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;th class="stub"&gt;3Pack (3 Count 8 oz bottles)&lt;/th&gt;
&lt;td&gt;$171.63&lt;/td&gt;
&lt;td&gt;9&lt;/td&gt;
&lt;td&gt;8&lt;/td&gt;
&lt;td&gt;72&lt;/td&gt;
&lt;td&gt;$2.3838&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;th class="stub"&gt;5Pack (3 Count 8 oz bottles)&lt;/th&gt;
&lt;td&gt;$292.62&lt;/td&gt;
&lt;td&gt;15&lt;/td&gt;
&lt;td&gt;8&lt;/td&gt;
&lt;td&gt;120&lt;/td&gt;
&lt;td&gt;$2.4385&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;th class="stub"&gt;4Pack (3 Count 8 oz bottles)&lt;/th&gt;
&lt;td&gt;$234.16&lt;/td&gt;
&lt;td&gt;12&lt;/td&gt;
&lt;td&gt;8&lt;/td&gt;
&lt;td&gt;96&lt;/td&gt;
&lt;td&gt;$2.4392&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;th class="stub"&gt;2Pack (3 Count 8 oz bottles)&lt;/th&gt;
&lt;td&gt;$117.19&lt;/td&gt;
&lt;td&gt;6&lt;/td&gt;
&lt;td&gt;8&lt;/td&gt;
&lt;td&gt;48&lt;/td&gt;
&lt;td&gt;$2.4415&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;th class="stub"&gt;2Pack (12 Ounce)&lt;/th&gt;
&lt;td&gt;$65.53&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;12&lt;/td&gt;
&lt;td&gt;24&lt;/td&gt;
&lt;td&gt;$2.7304&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;th class="stub"&gt;4Pack (12 Ounce)&lt;/th&gt;
&lt;td&gt;$131.18&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;12&lt;/td&gt;
&lt;td&gt;48&lt;/td&gt;
&lt;td&gt;$2.7329&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;th class="stub"&gt;5Pack (12 Ounce)&lt;/th&gt;
&lt;td&gt;$165.00&lt;/td&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;12&lt;/td&gt;
&lt;td&gt;60&lt;/td&gt;
&lt;td&gt;$2.7500&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;th class="stub"&gt;3Pack (12 Ounce)&lt;/th&gt;
&lt;td&gt;$101.28&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;12&lt;/td&gt;
&lt;td&gt;36&lt;/td&gt;
&lt;td&gt;$2.8133&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;th class="stub"&gt;5Pack (36 Count 1 oz bottles)&lt;/th&gt;
&lt;td&gt;$675.12&lt;/td&gt;
&lt;td&gt;180&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;180&lt;/td&gt;
&lt;td&gt;$3.7507&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;th class="stub"&gt;4Pack (36 Count 1 oz bottles)&lt;/th&gt;
&lt;td&gt;$540.10&lt;/td&gt;
&lt;td&gt;144&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;144&lt;/td&gt;
&lt;td&gt;$3.7507&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;th class="stub"&gt;3Pack (36 Count 1 oz bottles)&lt;/th&gt;
&lt;td&gt;$405.12&lt;/td&gt;
&lt;td&gt;108&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;108&lt;/td&gt;
&lt;td&gt;$3.7511&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;th class="stub"&gt;2Pack (36 Count 1 oz bottles)&lt;/th&gt;
&lt;td&gt;$270.09&lt;/td&gt;
&lt;td&gt;72&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;72&lt;/td&gt;
&lt;td&gt;$3.7513&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;th class="stub"&gt;4Pack (2 Count 2.5 oz bottles)&lt;/th&gt;
&lt;td&gt;$131.16&lt;/td&gt;
&lt;td&gt;8&lt;/td&gt;
&lt;td&gt;2.5&lt;/td&gt;
&lt;td&gt;20&lt;/td&gt;
&lt;td&gt;$6.5580&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;th class="stub"&gt;2Pack (2 Count 2.5 oz bottles)&lt;/th&gt;
&lt;td&gt;$65.69&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;2.5&lt;/td&gt;
&lt;td&gt;10&lt;/td&gt;
&lt;td&gt;$6.5690&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;th class="stub"&gt;5Pack (2 Count 2.5 oz bottles)&lt;/th&gt;
&lt;td&gt;$164.89&lt;/td&gt;
&lt;td&gt;10&lt;/td&gt;
&lt;td&gt;2.5&lt;/td&gt;
&lt;td&gt;25&lt;/td&gt;
&lt;td&gt;$6.5956&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;th class="stub"&gt;3Pack (2 Count 2.5 oz bottles)&lt;/th&gt;
&lt;td&gt;$101.39&lt;/td&gt;
&lt;td&gt;6&lt;/td&gt;
&lt;td&gt;2.5&lt;/td&gt;
&lt;td&gt;15&lt;/td&gt;
&lt;td&gt;$6.7593&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;th class="stub"&gt;2.5 Ounce (Pack of 4)&lt;/th&gt;
&lt;td&gt;$99.99&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;2.5&lt;/td&gt;
&lt;td&gt;10&lt;/td&gt;
&lt;td&gt;$9.9990&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;The &lt;em&gt;cheapest&lt;/em&gt; option is not really surprising, a two pack of 20 oz. bottles
comes out to &lt;strong&gt;$0.4420/oz&lt;/strong&gt;. The default selection, one 18 oz. bottle, is only
about five cents more at $0.4994/oz. The next entry jumps $0.23/oz. for, oddly,
a six pack of 12 oz. bottles. Where is this cheapest item (the 18 oz. two pack)
in the actual list? It's option #6 - the default is #30 (of course!), so it's
only necessary to scan up the long list and (hopefully) click the right one.
This option isn't sold from or shipped by Amazon.com, but is fulfilled by a
third-party seller.&lt;/p&gt;
&lt;p&gt;So, one can work all this out for each option - it took me about half an hour
with Firefox's developer tools, a text editor with regex search/replace, and a
spreadsheet - or take a wild stab and pay up to &lt;strong&gt;2,160%&lt;/strong&gt; extra (at $9.99/oz.)
than the cheapest (non-subscription) option.&lt;/p&gt;
&lt;p&gt;The variation and style of the &amp;quot;size&amp;quot; descriptions also is quite confusing in
itself. Is a &amp;quot;2Pack&amp;quot; two bottles of lotion? Well, it depends. In one case it is
indeed two, but in others it is four, six, or... &lt;strong&gt;72&lt;/strong&gt;! Having so many options
is not necessarily a bad thing - perhaps someone is hosting a skin care event
and wants to hand out mini bottles to hundreds of attendees - but the
inconsistency in how each size is described is silly.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Note: in the time since I began typing this post (an hour or so ago), the
number of size options increased to 38.&lt;/em&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="styles"&gt;
&lt;h2&gt;Styles&lt;/h2&gt;
&lt;p&gt;There are &lt;em&gt;only&lt;/em&gt; three style options (phew!). They are:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;Daily Moisturizing (the default)&lt;/li&gt;
&lt;li&gt;Daily Moisturizing, SPF 15 (ok, that fits)&lt;/li&gt;
&lt;li&gt;Lotion (what?)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Selecting &lt;em&gt;Daily Moisturizing, SPF 15&lt;/em&gt; redirects me to the &lt;strong&gt;2Pack (12 Ounce)&lt;/strong&gt;
size for $65.53 ($2.7304/oz.). Is this the cheapest option for the style?
Although it's not clear from the size names (surprise!), the list of options is
in fact a mix of SPF and non-SPF. To Amazon.com's credit, the list does sort of
indicate this with an &amp;quot;Available in (style)&amp;quot; note instead of price for relevant
entries. Anyway, it appears that the &lt;strong&gt;12 Fluid Ounce&lt;/strong&gt; option is the
cheapest at $9.32 ($0.7767/oz.) - &lt;strong&gt;71% cheaper&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;The &lt;em&gt;Lotion&lt;/em&gt; style really simplifies things (in a way) - the default size is
&lt;strong&gt;2.5 Ounce (Pack of 4)&lt;/strong&gt; for $99.99 ($9.99/oz. - aka the most expensive option
in the list). If that seems a bit pricey, just pay the same amount for the
only other size - &lt;strong&gt;18 Ounce (Pack of 3)&lt;/strong&gt; ($1.8517/oz.) - &lt;strong&gt;440% cheaper&lt;/strong&gt;,
woo! Both of these &amp;quot;sizes&amp;quot; come, unsurprisingly, from the same third-party
seller and begs the question - is this intentional deceit or a legitimate
accident? Well, the seller has 99% positive feedback in the last 12 months on
5,938 ratings so... is that good? It sounds good!&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="everything-else"&gt;
&lt;h2&gt;Everything Else&lt;/h2&gt;
&lt;p&gt;Assuming no changes are made to the Size or Style options, that leaves &lt;strong&gt;nine&lt;/strong&gt;
other fields to consider to get this item in a cart. There are two primary
options - &amp;quot;Subscribe &amp;amp; Save&amp;quot; or &amp;quot;One-time Purchase&amp;quot;.&lt;/p&gt;
&lt;div class="section" id="subscribe-save"&gt;
&lt;h3&gt;Subscribe &amp;amp; Save&lt;/h3&gt;
&lt;p&gt;The &amp;quot;Subscribe &amp;amp; Save&amp;quot; option is selected by default and can be used, in
combination with the &amp;quot;Delivery every&amp;quot; select list further down, to set up a
regular purchase of the item at intervals (1 - 6 months, default: 2) for an
additional 5% off. This 5% discount would decrease the price to $8.54
($0.4744/oz.), still not cheaper than the &lt;strong&gt;2 Pack 20 ounces each&lt;/strong&gt; size.
However, if one subscribes to five or more Amazon.com products, the discount is
bumped up to 15%! At that discount, this item would be $7.64 or $0.4245/oz.
&lt;strong&gt;This price beats the otherwise lowest cost size by about two cents per ounce.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Two other items are available with the &amp;quot;Subscribe &amp;amp; Save&amp;quot; option (ignoring the
other styles), but neither beats the new lowest price or even moves up much in
the price order:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;strong&gt;1 Fluid Ounce (Pack of 36)&lt;/strong&gt; is $29.68 ($0.8245/oz.) at 15% off.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;2.5 Ounce (Pack of 2)&lt;/strong&gt; is $5.03 ($1.0064/oz.) at 15% off.&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class="section" id="one-time-purchase"&gt;
&lt;h3&gt;One-time Purchase&lt;/h3&gt;
&lt;p&gt;Selecting &amp;quot;One-time Purchase&amp;quot; simplifies things a bit, the &amp;quot;Subscribe now&amp;quot;
button becomes an &amp;quot;Add to Cart&amp;quot; button and the default option is still the
third best deal of all combinations.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="other-sellers-on-amazon-com"&gt;
&lt;h3&gt;Other Sellers on Amazon.com&lt;/h3&gt;
&lt;p&gt;It is worth noting that, if one does not select the &amp;quot;One-time Purchase&amp;quot; option,
there are &lt;strong&gt;three&lt;/strong&gt; &amp;quot;Add to Cart&amp;quot; buttons on the screen. For a frequent user of
Amazon.com, it is probably clear enough what these options are (the same item
from different third-party sellers). However, it doesn't seem much of a stretch
to imagine a first-time shopper seeing and clicking the first available &amp;quot;Add to
Cart&amp;quot; button. In this case, the first (and cheapest) option in that list is
$13.09 ($0.7272/oz.). Hilariously, &lt;strong&gt;this is actually the third best price
available (fourth if the &amp;quot;Subscribe &amp;amp; Save&amp;quot; option is considered)&lt;/strong&gt;. Not a bad
deal for the confused shopper!&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="section" id="conclusion"&gt;
&lt;h2&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;I do not believe that Amazon.com is trying to be deceitful with all this cruft.
My best guess is that some third-party sellers try their best to game the
system and others just have to try to keep up.&lt;/p&gt;
&lt;blockquote&gt;
Amazon.com has the burden of policing this sort of activity and this is
just one example of a very deep and serious problem - &lt;strong&gt;a massive amount of
confusing options will drive people away&lt;/strong&gt;.&lt;/blockquote&gt;
&lt;p&gt;Half way through typing this up, I realized why I took such a great interest in
figuring out what the hell is going on with all the options. In a few months my
wife will give birth to our first child and we will likely be going out of our
minds (we already are a little bit!) trying to understand what we need to
purchase and how to gauge quality and price. &lt;strong&gt;Amazon.com makes researching
products very difficult&lt;/strong&gt; and I will try my hardest to avoid using it when
possible.&lt;/p&gt;
&lt;div class="section" id="bonus-conclusion"&gt;
&lt;h3&gt;Bonus Conclusion!&lt;/h3&gt;
&lt;p&gt;Anyone needing to buy Aveeno lotion without SPF now has all the details!
Although, given the addition of a 38th size option in the past few hours, I
imagine the value of this information won't last much more than a week or month
at best. Ultimately, I purchased elsewhere at a price of $0.3718/oz., and I
didn't have to subscribe to scheduled future purchases.&lt;/p&gt;
&lt;p class="center em"&gt;&lt;a class="reference external" href="https://news.ycombinator.com/item?id=14532840"&gt;Discussion at Hacker News&lt;/a&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
</content><category term="Asides"></category><category term="amazon.com"></category><category term="finance"></category><category term="online shopping"></category></entry><entry><title>Yes This Is A Really Long Request URL</title><link href="https://www.chris-wells.net/articles/2017/04/20/yesthisisareallylongrequesturl" rel="alternate"></link><published>2017-04-20T10:00:00-04:00</published><updated>2017-04-20T10:00:00-04:00</updated><author><name>Christopher Charbonneau Wells</name></author><id>tag:www.chris-wells.net,2017-04-20:/articles/2017/04/20/yesthisisareallylongrequesturl</id><summary type="html">
&lt;p&gt;Yesterday, while reviewing some logs I came across a curious entry in an Apache
error log:&lt;/p&gt;
&lt;pre class="code literal-block"&gt;
[Wed Apr 19 08:51:48.119666 2017] [core:error] [pid 29210] (36)File name
too long: [client 137.226.113.7:40907] AH00036: access to
/YesThisIsAReallyLongRequestURLbutWeAreDoingItOnPurposeWeAreScanningForR
esearchPurposePleaseHaveALookAtTheUserAgentTHXYesThisIsAReallyLongReques
tURLbutWeAreDoingItOnPurposeWeAreScanningForResearchPurposePleaseHaveALo
okAtTheUserAgentTHXYesThisIsAReallyLongRequestURLbutWeAreDoingItOnPurpos
eWeAreScanningForResearchPurposePleaseHaveALookAtTheUserAgentTHXYesThisI
sAReallyLongRequestURLbutWeAreDoingItOnPurposeWeAreScanningForResearchPu
rposePleaseHaveALookAtTheUserAgentTHXYesThisIsAReallyLongRequestURLbutWe
AreDoingItOnPurposeWeAreScanningForResearchPurposePleaseHaveALookAtTheUs
erAgentTHXYesThisIsAReallyLongRequestURLbutWeAreDoingItOnPurposeWeAreSca
nningForResearchPurposePleaseHaveALookAtTheUserAgentTHXYesThisIsAReallyL
ongRequestURLbutWeAreDoingItOnPurposeWeAreScanningForResearchPurposePlea
seHaveALookAtTheUserAgentTHXYesThisIsAReallyLongRequestURLbutWeAreDoingI
tOnPurposeWeAreScanningForResearchPurposePleaseHaveALookAtTheUserAgentTH
XYesThisIsAReallyLongRequestURLbutWeAreDoingItOnPurposeWeAreScann failed
(filesystem path '[...]')
&lt;/pre&gt;
&lt;p&gt;Formatted to plain English: &lt;cite&gt;Yes, this is a really long request URL but we are
doing it on purpose. We are scanning for research purpose. Please have a look
at the user agent. Thanks!&lt;/cite&gt;&lt;/p&gt;
&lt;p&gt;What does the user agent for this request have to say?&lt;/p&gt;
</summary><content type="html">&lt;span class="target" id="top"&gt;&lt;/span&gt;
&lt;p&gt;Yesterday, while reviewing some logs I came across a curious entry in an Apache
error log:&lt;/p&gt;
&lt;pre class="code literal-block"&gt;
[Wed Apr 19 08:51:48.119666 2017] [core:error] [pid 29210] (36)File name
too long: [client 137.226.113.7:40907] AH00036: access to
/YesThisIsAReallyLongRequestURLbutWeAreDoingItOnPurposeWeAreScanningForR
esearchPurposePleaseHaveALookAtTheUserAgentTHXYesThisIsAReallyLongReques
tURLbutWeAreDoingItOnPurposeWeAreScanningForResearchPurposePleaseHaveALo
okAtTheUserAgentTHXYesThisIsAReallyLongRequestURLbutWeAreDoingItOnPurpos
eWeAreScanningForResearchPurposePleaseHaveALookAtTheUserAgentTHXYesThisI
sAReallyLongRequestURLbutWeAreDoingItOnPurposeWeAreScanningForResearchPu
rposePleaseHaveALookAtTheUserAgentTHXYesThisIsAReallyLongRequestURLbutWe
AreDoingItOnPurposeWeAreScanningForResearchPurposePleaseHaveALookAtTheUs
erAgentTHXYesThisIsAReallyLongRequestURLbutWeAreDoingItOnPurposeWeAreSca
nningForResearchPurposePleaseHaveALookAtTheUserAgentTHXYesThisIsAReallyL
ongRequestURLbutWeAreDoingItOnPurposeWeAreScanningForResearchPurposePlea
seHaveALookAtTheUserAgentTHXYesThisIsAReallyLongRequestURLbutWeAreDoingI
tOnPurposeWeAreScanningForResearchPurposePleaseHaveALookAtTheUserAgentTH
XYesThisIsAReallyLongRequestURLbutWeAreDoingItOnPurposeWeAreScann failed
(filesystem path '[...]')
&lt;/pre&gt;
&lt;p&gt;Formatted to plain English: &lt;cite&gt;Yes, this is a really long request URL but we are
doing it on purpose. We are scanning for research purpose. Please have a look
at the user agent. Thanks!&lt;/cite&gt;&lt;/p&gt;
&lt;p&gt;What does the user agent for this request have to say?&lt;/p&gt;

&lt;p&gt;Here is the access log entry:&lt;/p&gt;
&lt;pre class="code literal-block"&gt;
137.226.113.7 - - [19/Apr/2017:08:51:48 -0400] &amp;quot;GET [...] HTTP/1.1&amp;quot; 403
1471 &amp;quot;-&amp;quot; &amp;quot;Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_1)
AppleWebKit/537.36 (KHTML, like Gecko) Chrome/46.0.2490.86 Safari/537.36
Scanning for research (researchscan.comsys.rwth-aachen.de)&amp;quot;
&lt;/pre&gt;
&lt;p&gt;The website referenced in the user agent, &lt;a class="reference external" href="http://researchscan.comsys.rwth-aachen.de/"&gt;researchscan.comsys.rwth-aachen.de&lt;/a&gt;,
explains that this request is part of a research project at
&lt;a class="reference external" href="http://www.rwth-aachen.de/"&gt;RWTH Aachen University&lt;/a&gt; in Germany and &lt;a class="reference external" href="https://rdap-explorer.com/137.226.113.7/results/"&gt;137.226.113.7&lt;/a&gt; is indeed a part of
the university's network.&lt;/p&gt;
&lt;p&gt;Interestingly, this is the first time I have seen such a request in any web
server log (I have had occasion to look through more than a few). The Wayback
Machine has an &lt;a class="reference external" href="https://web.archive.org/web/20151206170755/http://researchscan.comsys.rwth-aachen.de/"&gt;archive of the user agent page&lt;/a&gt; from 6 Dec 2015, so it seems
that the research has been going on for at least a year. I checked for the
&lt;tt class="docutils literal"&gt;YesThisIsAReallyLongRequestURL&lt;/tt&gt; string in (a lot of) logs for about 10
different websites of varrying size and did not find any other instances. I
wonder how they determine which sites to scan...&lt;/p&gt;
</content><category term="Asides"></category><category term="apache"></category><category term="logs"></category><category term="security"></category><category term="vulnerabilities"></category></entry><entry><title>Responding to Identity Theft</title><link href="https://www.chris-wells.net/articles/2017/03/23/responding-to-identity-theft" rel="alternate"></link><published>2017-03-23T22:00:00-04:00</published><updated>2017-03-23T22:00:00-04:00</updated><author><name>Christopher Charbonneau Wells</name></author><id>tag:www.chris-wells.net,2017-03-23:/articles/2017/03/23/responding-to-identity-theft</id><summary type="html">
&lt;p&gt;On Sunday, 19 March around 10AM I received an email with the subject line
&lt;strong&gt;IMPORTANT: ProtectMyID Surveillance Alert&lt;/strong&gt;. I was busy at the time so I
flagged the message and moved on with my day. I had received these alerts a few
times before when applying for credit cards and getting a home loan. While I
hadn't done either of those things lately, I still was not particularly alarmed
by the subject alone and had many other things on my mind. Going about the busy
day, I promptly forgot about the email.&lt;/p&gt;
&lt;p&gt;Before starting the work day the next morning, I noticed and remembered the
email in my inbox. Why do I even have ProtectMyId? Because my personal data
was part of a breach at some point. Which breach? I don't even recall - there
have been so many. I opened the email and logged in to find that a new credit
account with a provider I had never used had been opened in my name. Surprise!
I am a victim of identity theft.&lt;/p&gt;
&lt;p&gt;What do I do now?&lt;/p&gt;
</summary><content type="html">&lt;span class="target" id="top"&gt;&lt;/span&gt;
&lt;p&gt;On Sunday, 19 March around 10AM I received an email with the subject line
&lt;strong&gt;IMPORTANT: ProtectMyID Surveillance Alert&lt;/strong&gt;. I was busy at the time so I
flagged the message and moved on with my day. I had received these alerts a few
times before when applying for credit cards and getting a home loan. While I
hadn't done either of those things lately, I still was not particularly alarmed
by the subject alone and had many other things on my mind. Going about the busy
day, I promptly forgot about the email.&lt;/p&gt;
&lt;p&gt;Before starting the work day the next morning, I noticed and remembered the
email in my inbox. Why do I even have ProtectMyId? Because my personal data
was part of a breach at some point. Which breach? I don't even recall - there
have been so many. I opened the email and logged in to find that a new credit
account with a provider I had never used had been opened in my name. Surprise!
I am a victim of identity theft.&lt;/p&gt;
&lt;p&gt;What do I do now?&lt;/p&gt;

&lt;div class="section" id="credit-reporting-agencies"&gt;
&lt;h2&gt;Credit Reporting Agencies&lt;/h2&gt;
&lt;p&gt;First, a bit of background - &lt;a class="reference external" href="http://www.protectmyid.com/"&gt;ProtectMyID&lt;/a&gt; is a service available from
&lt;a class="reference external" href="http://www.experian.com/"&gt;Experian&lt;/a&gt;, one of the three Credit Reporting Agencies (CRAs) responsible for
monitoring and &amp;quot;scoring&amp;quot; credit for individuals (whether they know it or not).
The USA.gov website &lt;a class="reference external" href="https://www.usa.gov/credit-reports#item-35962"&gt;defines a CRA&lt;/a&gt; like so:&lt;/p&gt;
&lt;blockquote&gt;
A credit reporting agency (CRA) is a company that collects information about
where you live and work, how you pay your bills, whether or not you have
been sued, arrested, or filed for bankruptcy. All of this information is
combined together in a credit report. A CRA will then sell your credit
report to creditors, employers, insurers, and others. These companies will
use these reports to make decisions about extending credit, jobs, and
insurance policies to you.&lt;/blockquote&gt;
&lt;p&gt;Basically, these agencies collect, &amp;quot;score&amp;quot; and &lt;strong&gt;sell&lt;/strong&gt; your personal
information to companies &lt;em&gt;and&lt;/em&gt; &lt;strong&gt;sell&lt;/strong&gt; your own information back to you in the
form of credit reports and identity &amp;quot;protection&amp;quot; services. Not surprisingly, the
other two agencies (&lt;a class="reference external" href="https://www.equifax.com/"&gt;Equifax&lt;/a&gt; and &lt;a class="reference external" href="https://www.transunion.com/"&gt;TransUnion&lt;/a&gt;) have similar services.&lt;/p&gt;
&lt;p&gt;Want to monitor your information at all three agencies? That'll be about
&lt;em&gt;$50 per month&lt;/em&gt; for basic service. In fact, upon logging in to check this new
alert, the ProtectMyID website displayed a big warning box telling me that my
service is about to expire and I will need to pay if I want to continue using
it. Good timing!&lt;/p&gt;
&lt;p class="image-box center"&gt;&lt;img alt="ProtectMyID renewal alert" src="/static/images/2017/pmi-renew.png" /&gt; &lt;em&gt;Oh, a discounted rate!&lt;/em&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="an-attempt-to-dispute"&gt;
&lt;h2&gt;An Attempt to Dispute&lt;/h2&gt;
&lt;p&gt;Anyway, on to (what I guessed to be) step one: dispute this new account on my
credit report. Included with each alert on the ProtectMyID website is a big red
&lt;strong&gt;Dispute Item&lt;/strong&gt; button - great, this should be simple! I clicked the button for
the fraudulent account and was redirected to a page where, for some reason, I
had to click another button indicating that I wanted to dispute an item on my
Experian report.&lt;/p&gt;
&lt;p&gt;Next, I was asked for a &amp;quot;report number&amp;quot;. Taking a look at the help on the page,
I realized I did not have one because I didn't have an actual credit report,
just the ProtectMyID alert. I navigated my way back to the alert page and found
a &amp;quot;Credit Report&amp;quot; link. I clicked the link and was greeted with a blank page -
no error, no content, just blank. Upon doing this I recalled the exact same
thing happening multiple times in the past. At least once, I received an &amp;quot;error&amp;quot;
message explaining that this was not part of the free service.&lt;/p&gt;
&lt;blockquote&gt;
In fact, &lt;strong&gt;the actual credit report is not even part of the paid service&lt;/strong&gt;,
that costs extra!&lt;/blockquote&gt;
&lt;p&gt;Lacking an actual credit report, Experian provides another path - provide all my
personal information and answer a series of questions about the contents of my
report in order to access it and dispute the item. My options in this round,
each with a multiple choice list of answers including a NONE OF THE ABOVE,
included:&lt;/p&gt;
&lt;ol class="arabic simple"&gt;
&lt;li&gt;A current or previous phone number that is associated with you.&lt;/li&gt;
&lt;li&gt;The last four digits of your primary checking account number.&lt;/li&gt;
&lt;li&gt;A person you have lived with in the last 10 years.&lt;/li&gt;
&lt;li&gt;A city that you have previously resided in.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;The result?&lt;/p&gt;
&lt;p class="image-box center"&gt;&lt;img alt="Experian verification failed" src="/static/images/2017/experian-verify-fail.png" /&gt; &lt;em&gt;Nope.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;This has happened &lt;em&gt;every single time&lt;/em&gt; I have attempted to verify myself to
Experian in particular (Equifax and TransUnion have worked fine). I have not
always been entirely sure of the answers, but in this case (for this blog post)
I am actually 100% sure I answered them all correctly.&lt;/p&gt;
&lt;p&gt;So this leaves me with two options: print and mail a letter to Experian and wait
for the response or try something else. Let's try something else...&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="calling-around"&gt;
&lt;h2&gt;Calling Around&lt;/h2&gt;
&lt;p&gt;Included with the alert on ProtectMyID is a phone number for the reporting
company (bank, creditor, etc.) so I decided to check that for the fraudulent
record and reach out directly.&lt;/p&gt;
&lt;p&gt;The &amp;quot;phone&amp;quot; entry for the new account alert: &amp;quot;BYMAILONLY&amp;quot;. Helpful.&lt;/p&gt;
&lt;p&gt;I scrolled through the rest of my alerts and found the same bank again, along
with two &lt;em&gt;other&lt;/em&gt; unknown entries! All three of these entries were for &amp;quot;hard&amp;quot;
inquiries - meaning a company received my credit report at &amp;quot;my&amp;quot; request (see
also: &lt;a class="reference external" href="https://www.nerdwallet.com/blog/finance/credit-report-soft-hard-pull-difference/"&gt;Hard vs. Soft Inquiries&lt;/a&gt;), only I didn't make any of the requests and
all three occurred on &lt;em&gt;February&lt;/em&gt; 20, nearly a month prior to my alert!&lt;/p&gt;
&lt;p&gt;Why didn't I receive alerts about these hard inquiries from ProtectMyID? How has
this terrible infrastructure failed this time? It didn't, actually. The emails
went to an address I had recently migrated away from and I forgot to update my
address with ProtectMyID. I checked the old email and, sure enough, all three
alerts were there. Oops.&lt;/p&gt;
&lt;div class="section" id="bank-1"&gt;
&lt;h3&gt;Bank #1&lt;/h3&gt;
&lt;p&gt;The hard inquiry alert for Bank #1, unlike the open account alert, &lt;em&gt;does&lt;/em&gt;
contain a phone number. I gave that number a call and quickly got connected to
an agent who was very helpful - this was a nice relief when other avenues were
failing me. The first thing the rep asked for was my full name, then my address,
then my social security number... this conversation gave me a bit of pause, but
ultimately it's understandable that all this information is needed in order
to verify my identity. Nonetheless, it left a bad taste in my mouth to be
providing this sort of information over the phone &lt;em&gt;in response&lt;/em&gt; to its being
compromised and used fraudulently.&lt;/p&gt;
&lt;p&gt;It turned out that the account was opened, but immediately flagged for
suspicious activity and, luckily, no charges were made with the account. The rep
closed the account, initiated an internal investigation and provided me with
some ideas for next steps. Overall very positive.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="bank-2"&gt;
&lt;h3&gt;Bank #2&lt;/h3&gt;
&lt;p&gt;Next, I called the number for Bank #2 associated with its hard inquiry. Again,
I was able to get to a rep pretty quickly. Again, I was asked to provide my name
and address. However, in this case I was only asked for the last four digits of
my social security number. This was at least a bit more comforting. The rep
found the fraudulent application, denied it, started an internal investigation
and provided me with a reference number. Quick and easy.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="bank-3"&gt;
&lt;h3&gt;Bank #3&lt;/h3&gt;
&lt;p&gt;Here again no phone number is provided with the alert, so I went searching for a
number on the bank's website. Interestingly, they have a page dedicated to
identity theft which basically says &amp;quot;Don't call us, call the FTC&amp;quot;. Undeterred, I
dug up another number, gave them a call and quickly discovered that they have no
interest in to talking to anyone other than account holders...&lt;/p&gt;
&lt;p&gt;After about five minutes of responding to the same prompts with the same answers
and pounding keys, I managed to get an actual person on the phone. Here again I
had to provide all the same identifying information to get the issue addressed.
The rep found and closed the application, but did not say anything about
investigating or provide any reference number.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="section" id="getting-my-credit-reports"&gt;
&lt;h2&gt;Getting &lt;strong&gt;My&lt;/strong&gt; Credit Reports&lt;/h2&gt;
&lt;p&gt;Everyone is entitled to a &lt;a class="reference external" href="https://www.consumer.ftc.gov/articles/0155-free-credit-reports"&gt;free credit report&lt;/a&gt; once per year (how lucky!). I
was not at all surprised to find that searching for &amp;quot;free credit report&amp;quot; online
turns up more than a few phonies. The actual website associated with the law is
&lt;a class="reference external" href="https://www.annualcreditreport.com/index.action"&gt;AnnualCreditReport.com&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;I walked through the process and had relatively little trouble. Equifax and
TransUnion both provided sane verification questions and allowed me to download
the report immediately. Experian, on the other hand, once again provided some
odd questions and was not able to verify me. Apparently, I will receive a letter
and code (to access my report) via the mail within three weeks. Three weeks.&lt;/p&gt;
&lt;p&gt;I reviewed the Equifax and TransUnion reports closely and, despite finding a
number of things that surprised me, did not find any additional fraudulent
entries.&lt;/p&gt;
&lt;p&gt;While I am relieved that I was able to retrieve two of the three reports and
that nothing additional was found, I find it rather perplexing that this
information is kept so secret from the people that it &amp;quot;rates&amp;quot;. The fact that
these reports can only be retrieved once a year and otherwise must be &lt;em&gt;paid&lt;/em&gt; for
is terrible. If I had not happened to have a service provided because of a
breach at just the right time, it may have taken me years (and potentially a
denied credit application or worse) to find this or more serious damage.&lt;/p&gt;
&lt;blockquote&gt;
While these Credit Reporting Agencies maintain and &amp;quot;score&amp;quot; such deeply
personal and potential harmful information, &lt;strong&gt;protective services should be
provided to individuals free of charge&lt;/strong&gt;.&lt;/blockquote&gt;
&lt;/div&gt;
&lt;div class="section" id="credit-report-protections"&gt;
&lt;h2&gt;Credit Report Protections&lt;/h2&gt;
&lt;p&gt;With all this information in hand, I began thinking about the future and
wondering how on Earth I can prevent having to go through this headache again.
Eventually, I found that there are a couple of options to protect a credit
report in various ways. While not exactly what I was thinking, it seemed like a
good enough start. There are generally three options:&lt;/p&gt;
&lt;div class="section" id="fraud-alerts"&gt;
&lt;h3&gt;1. Fraud Alerts&lt;/h3&gt;
&lt;p&gt;This is a 90-day alert that adds a requirement of placing a phone call with a
number associated with the alert to verify any new inquiries or accounts. It can
fall off or be renewed after 90 days. This also, apparently, entitles you to a
free credit report, but I did not test this. (&lt;a class="reference external" href="https://www.consumer.ftc.gov/articles/0275-place-fraud-alert"&gt;Fraud Alerts&lt;/a&gt;)&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="extended-fraud-alerts"&gt;
&lt;h3&gt;2. Extended Fraud Alerts&lt;/h3&gt;
&lt;p&gt;This alert goes quite a bit further. It lasts &lt;strong&gt;seven years&lt;/strong&gt;, entitles you to
two free reports from each agency &lt;em&gt;and&lt;/em&gt; takes your name off pre-screened offer
marketing lists for five years. Each agency must be contacted separately to
enable this alert and it may require additional paperwork and proof of actual
identity theft. (&lt;a class="reference external" href="https://www.consumer.ftc.gov/articles/0279-extended-fraud-alerts-and-credit-freezes"&gt;Extended Fraud Alerts&lt;/a&gt;)&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="credit-freezes"&gt;
&lt;h3&gt;3. Credit Freezes&lt;/h3&gt;
&lt;p&gt;This most serious of the three options, a credit freeze prevents almost all
access to a credit report. The durations and rules for credit freezes vary from
state to state and, unlike fraud alerts, most credit freezes require paying a
fee (unless you have a police report, in some cases). Generally, except for
state-imposed limits, this type of protection lasts until you have it lifted.
(&lt;a class="reference external" href="https://www.consumer.ftc.gov/articles/0497-credit-freeze-faqs"&gt;Credit Freezes&lt;/a&gt;)&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="section" id="placing-a-fraud-alert"&gt;
&lt;h2&gt;Placing a Fraud Alert&lt;/h2&gt;
&lt;p&gt;I chose to place an initial 90-day fraud alert because the credit freeze option
seemed a bit extreme and the requirements of the extended alert, as indicated by
Experian, seemed daunting. I used Experian's &lt;a class="reference external" href="https://www.experian.com/fraud/center.html"&gt;Fraud Alert Center&lt;/a&gt; to walk
through instructions that eventually gave me a phone number to call. The number
was a fairly straight forward automated system that took a few pieces of
information, supplied me with a confirmation code and set the alert.&lt;/p&gt;
&lt;p&gt;How does this appear on a credit report? On my TransUnion report, in the form of
this simple note:&lt;/p&gt;
&lt;blockquote&gt;
&lt;strong&gt;SECURITY ALERT:&lt;/strong&gt;
Initial Fraud Alert: Action may be required under FCRA before opening or
modifying an account. Contact consumer at (xxx) xxx-xxxx.
(Note: This alert is set to expire in 07/2017.)&lt;/blockquote&gt;
&lt;p&gt;Why do I know this? Despite setting the fraud alert on 20 March, I received a
&lt;em&gt;new&lt;/em&gt; ProtectMyID alert email noting the addition of the same fraudulent account
to my TransUnion report. While I can accept some amount of lag between systems,
it greatly concerns me that this addition was not prevented &lt;strong&gt;three days&lt;/strong&gt; after
I placed fraud alert.&lt;/p&gt;
&lt;blockquote&gt;
The lack of urgency, shoddy technical systems and poor usability of these
CRA tools make the industry seem entirely out of touch with the feeling of
urgency I suspect most people have when something like this happens.&lt;/blockquote&gt;
&lt;/div&gt;
&lt;div class="section" id="alerting-the-ftc"&gt;
&lt;h2&gt;Alerting the FTC&lt;/h2&gt;
&lt;p&gt;As my final step (for now), I created an account and filed a report at
&lt;a class="reference external" href="https://www.identitytheft.gov/"&gt;IdentityTheft.gov&lt;/a&gt;. The guided questions leading to the sign up form were
actually quite useful in helping me remember &lt;em&gt;which&lt;/em&gt; breaches my data has been
caught up in.&lt;/p&gt;
&lt;p&gt;Side note - I attempted to verify one breach that I wasn't sure about and was
rather disappointed when, after submitting all my info (yet again), I landed on
a simple page explaining that I would receive a response by mail within some
weeks.&lt;/p&gt;
&lt;p&gt;Once signed up and logged in, the site provides a detailed checklist-style tool
for walking through all the basic response steps. Many of these things I had
done already, but I was happy for some extras such as links relating to
protection services offered by specific breached companies and ready-to-send
form letters for banks and other institutions.&lt;/p&gt;
&lt;p&gt;Ultimately, this was a vital tool and probably would have been a better starting
point for the entire ordeal. One particularly interesting fact I picked up from
reviewing things with this tool is that the identity theft &amp;quot;report&amp;quot; provided
&lt;em&gt;should&lt;/em&gt; be all I need to add an extended alert to my credit report. The &lt;a class="reference external" href="https://www.consumer.ftc.gov/articles/0279-extended-fraud-alerts-and-credit-freezes"&gt;FTC&lt;/a&gt;
puts it rather simply:&lt;/p&gt;
&lt;blockquote&gt;
If you’ve created an Identity Theft Report, you can get an extended fraud
alert on your credit file.&lt;/blockquote&gt;
&lt;p&gt;Compare that statement to this one from Experian's &lt;a class="reference external" href="https://www.experian.com/fraud/center.html"&gt;Fraud Alert Center&lt;/a&gt;:&lt;/p&gt;
&lt;blockquote&gt;
If you are a victim of identity theft and submit a copy of a valid identity
theft report &lt;strong&gt;that you have filed with a Federal, State or local law
enforcement agency&lt;/strong&gt;, then you may request an Extended Fraud Victim Alert,
which lasts for 7 years.&lt;/blockquote&gt;
&lt;p&gt;As it turns out, the FTC &lt;strong&gt;is&lt;/strong&gt; a federal law enforcement agency, so Experian's
statement is essentially true but perhaps misleading. If the IdentityTheft.org
report (which is basically a letter of attestation) is truly all that is
needed - why not simply link to the site? The statement as it stands would
likely deter anyone who hasn't suffered significant harm and &lt;em&gt;already&lt;/em&gt; filed a
local police report.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="next-steps"&gt;
&lt;h2&gt;Next Steps&lt;/h2&gt;
&lt;p&gt;I am currently waiting for two items in the mail:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;My Experian credit report&lt;/li&gt;
&lt;li&gt;A confirmation letter from a large breach&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Once those items arrive, I will review and take any necessary additional steps.
I will also probably make use of the extended fraud alert. Some information in
my Experian report is necessary for that so I have some time to think it over.&lt;/p&gt;
&lt;p&gt;If I were doing this all over again (and hey, maybe I will!), I would follow
these steps in this order:&lt;/p&gt;
&lt;ol class="arabic simple"&gt;
&lt;li&gt;File a report at &lt;a class="reference external" href="https://www.identitytheft.gov/"&gt;IdentityTheft.gov&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;Call the involved banks (or other institutions).&lt;/li&gt;
&lt;li&gt;Get a credit report from each CRA (either from &lt;a class="reference external" href="https://www.annualcreditreport.com/index.action"&gt;AnnualCreditReport.com&lt;/a&gt; or by adding initial fraud alerts).&lt;/li&gt;
&lt;li&gt;Add an extended fraud alert to each CRA's report.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Despite the fact that these agencies talk amongst each other and that the banks
claimed they would repeal the fraudulent information, I am compelled to use
every possible avenue to report and correct things. The lack of urgency
displayed by the agencies in particular does not instill any confidence.&lt;/p&gt;
&lt;p&gt;In the end, I am still left feeling quite vulnerable. My information is
definitely out there - I already knew that and wasn't happy about it. But now it
has been used, and thus confirmed, in the wild. This is information that does
not expire and is not easily changed, and I'm supposed to be content with one
year of free &amp;quot;protection&amp;quot; (which is just monitoring)?&lt;/p&gt;
&lt;p&gt;All of the protective services, credit reports and scores offered by these
agencies should be provided to the individuals they affect at no charge. If
Experian, Equifax and TransUnion are to profit from such sensitive, private
information by selling it to third parties, they must also bear the burden of
monitoring and protection.&lt;/p&gt;
&lt;/div&gt;
</content><category term="Life"></category><category term="credit"></category><category term="data breaches"></category><category term="finance"></category><category term="identity theft"></category><category term="privacy"></category><category term="usability"></category></entry><entry><title>RDAP Explorer</title><link href="https://www.chris-wells.net/articles/2017/02/06/rdap-explorer" rel="alternate"></link><published>2017-02-06T21:30:00-04:00</published><updated>2017-02-06T21:30:00-04:00</updated><author><name>Christopher Charbonneau Wells</name></author><id>tag:www.chris-wells.net,2017-02-06:/articles/2017/02/06/rdap-explorer</id><summary type="html">
&lt;p&gt;Having fallen behind a bit on &lt;a class="reference external" href="https://github.com/cdubz/takeout-inspector"&gt;Takeout Inspector&lt;/a&gt;, the &lt;a class="reference external" href="https://www.chris-wells.net/tag/12-years-of-gmail"&gt;12 Years of Gmail&lt;/a&gt;
series and some other projects, I decided to try to put something very simple
together from beginning to end and actually launch it. One of my previous posts,
&lt;a class="reference external" href="https://www.chris-wells.net/articles/2016/12/03/ddos-examination"&gt;Examining the Remnants of a Small DDoS Attack&lt;/a&gt; introduced me to the Python
package &lt;a class="reference external" href="https://pypi.python.org/pypi/ipwhois/"&gt;ipwhois&lt;/a&gt; and the alternative WHOIS system &lt;a class="reference external" href="https://www.apnic.net/about-apnic/whois_search/about/rdap/"&gt;RDAP&lt;/a&gt;. This eventually led
me to a quick and simple project called &lt;a class="reference external" href="https://rdap-explorer.com"&gt;RDAP Explorer&lt;/a&gt;...&lt;/p&gt;
</summary><content type="html">&lt;span class="target" id="top"&gt;&lt;/span&gt;
&lt;p&gt;Having fallen behind a bit on &lt;a class="reference external" href="https://github.com/cdubz/takeout-inspector"&gt;Takeout Inspector&lt;/a&gt;, the &lt;a class="reference external" href="https://www.chris-wells.net/tag/12-years-of-gmail"&gt;12 Years of Gmail&lt;/a&gt;
series and some other projects, I decided to try to put something very simple
together from beginning to end and actually launch it. One of my previous posts,
&lt;a class="reference external" href="https://www.chris-wells.net/articles/2016/12/03/ddos-examination"&gt;Examining the Remnants of a Small DDoS Attack&lt;/a&gt; introduced me to the Python
package &lt;a class="reference external" href="https://pypi.python.org/pypi/ipwhois/"&gt;ipwhois&lt;/a&gt; and the alternative WHOIS system &lt;a class="reference external" href="https://www.apnic.net/about-apnic/whois_search/about/rdap/"&gt;RDAP&lt;/a&gt;. This eventually led
me to a quick and simple project called &lt;a class="reference external" href="https://rdap-explorer.com"&gt;RDAP Explorer&lt;/a&gt;...&lt;/p&gt;

&lt;div class="section" id="what-is-rdap"&gt;
&lt;h2&gt;What is RDAP?&lt;/h2&gt;
&lt;p&gt;According to &lt;a class="reference external" href="https://www.apnic.net/about-apnic/whois_search/about/rdap/"&gt;APNIC&lt;/a&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;The Registration Data Access Protocol (RDAP)  is an alternative to WHOIS for
accessing Internet resource registration data.  RDAP is designed to address
a number of shortcomings in the existing Whois service. The most important
changes are:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;Standardization of queries and responses&lt;/li&gt;
&lt;li&gt;Internationalization considerations to cater for languages other than
English in data objects&lt;/li&gt;
&lt;li&gt;Redirection capabilities to allow seamless referrals to other registries&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;
&lt;p&gt;The most important advantage of RDAP over WHOIS is the &lt;strong&gt;Standardization of
queries and responses&lt;/strong&gt;. While reviewing a large set of IP addresses, I found it
rather difficult to deal with non-standard (and sometimes nonsensical) output of
WHOIS queries. Mostly they were easy enough to parse, but the odd balls made the
process annoying and time consuming.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="enter-ipwhois"&gt;
&lt;h2&gt;Enter ipwhois&lt;/h2&gt;
&lt;p&gt;Eventually I found my way to the wonderfully useful &lt;a class="reference external" href="https://pypi.python.org/pypi/ipwhois/"&gt;ipwhois&lt;/a&gt; package and this
made dealing with a large set of IP addresses &lt;em&gt;considerably&lt;/em&gt; easier. I dug in to
the code a little bit to see just how this was being done and eventually learned
about the aforementioned virtues of RDAP. Here is an example of the type of
output &lt;tt class="docutils literal"&gt;ipwhois&lt;/tt&gt; produces, for example, with one of Google's ever-popular
public DNS servers -&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;table class="highlighttable"&gt;&lt;tr&gt;&lt;td class="linenos"&gt;&lt;div class="linenodiv"&gt;&lt;pre&gt;&lt;span class="normal"&gt;0&lt;/span&gt;
&lt;span class="normal"&gt;1&lt;/span&gt;
&lt;span class="normal"&gt;2&lt;/span&gt;
&lt;span class="normal"&gt;3&lt;/span&gt;
&lt;span class="normal"&gt;4&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;div&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;ipwhois&lt;/span&gt;
&lt;span class="n"&gt;IP&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ipwhois&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;IPWhois&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;8.8.8.8&amp;#39;&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;IP&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;lookup_rdap&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="nb"&gt;print&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;raw&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;None&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;entities&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sa"&gt;u&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;GOGL&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;asn_registry&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;arin&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;network&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;status&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;None&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;handle&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sa"&gt;u&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;NET-8-8-8-0-1&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;name&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sa"&gt;u&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;LVLT-GOGL-8-8-8&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;links&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sa"&gt;u&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;https://rdap.arin.net/registry/ip/008.008.008.000&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sa"&gt;u&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;https://whois.arin.net/rest/net/NET-8-8-8-0-1&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sa"&gt;u&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;https://rdap.arin.net/registry/ip/008.000.000.000/8&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;raw&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;None&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;country&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;None&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;ip_version&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sa"&gt;u&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;v4&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;start_address&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;8.8.8.0&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;notices&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[{&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;description&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sa"&gt;u&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;By using the ARIN RDAP/Whois service, you are agreeing to the RDAP/Whois Terms of Use&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;links&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sa"&gt;u&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;https://www.arin.net/whois_tou.html&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;title&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sa"&gt;u&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;Terms of Service&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;}],&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;end_address&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;8.8.8.255&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;remarks&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;None&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;parent_handle&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sa"&gt;u&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;NET-8-0-0-0-1&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;cidr&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;8.8.8.0/24&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;type&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;None&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;events&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[{&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;action&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sa"&gt;u&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;last changed&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;timestamp&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sa"&gt;u&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;2014-03-14T16:52:05-04:00&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;actor&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;None&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;action&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sa"&gt;u&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;registration&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;timestamp&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sa"&gt;u&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;2014-03-14T16:52:05-04:00&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;actor&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;None&lt;/span&gt;&lt;span class="p"&gt;}]},&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;objects&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sa"&gt;u&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;GOGL&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;status&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;None&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;roles&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sa"&gt;u&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;registrant&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;handle&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sa"&gt;u&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;GOGL&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;entities&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sa"&gt;u&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;ABUSE5250-ARIN&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sa"&gt;u&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;ZG39-ARIN&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;links&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sa"&gt;u&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;https://rdap.arin.net/registry/entity/GOGL&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sa"&gt;u&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;https://whois.arin.net/rest/org/GOGL&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;raw&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;None&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;notices&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;None&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;contact&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;kind&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sa"&gt;u&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;org&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;name&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sa"&gt;u&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;Google Inc.&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;title&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;None&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;phone&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;None&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;role&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;None&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;address&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[{&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;type&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;None&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;value&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sa"&gt;u&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;1600 Amphitheatre Parkway&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s1"&gt;Mountain View&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s1"&gt;CA&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s1"&gt;94043&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s1"&gt;UNITED STATES&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;}],&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;email&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;None&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;events_actor&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;None&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;remarks&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;None&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;events&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[{&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;action&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sa"&gt;u&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;last changed&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;timestamp&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sa"&gt;u&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;2017-01-28T08:32:29-05:00&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;actor&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;None&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;action&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sa"&gt;u&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;registration&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;timestamp&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sa"&gt;u&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;2000-03-30T00:00:00-05:00&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;actor&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;None&lt;/span&gt;&lt;span class="p"&gt;}]}},&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;asn_country_code&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;US&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;asn_date&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;asn_cidr&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;8.8.8.0/24&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;nir&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;None&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;query&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;8.8.8.8&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;asn&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;15169&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/div&gt;
&lt;p&gt;With a bit of formatting, the end result looks quite nice and is easy to work
with (in code):&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;table class="highlighttable"&gt;&lt;tr&gt;&lt;td class="linenos"&gt;&lt;div class="linenodiv"&gt;&lt;pre&gt;&lt;span class="normal"&gt; 0&lt;/span&gt;
&lt;span class="normal"&gt; 1&lt;/span&gt;
&lt;span class="normal"&gt; 2&lt;/span&gt;
&lt;span class="normal"&gt; 3&lt;/span&gt;
&lt;span class="normal"&gt; 4&lt;/span&gt;
&lt;span class="normal"&gt; 5&lt;/span&gt;
&lt;span class="normal"&gt; 6&lt;/span&gt;
&lt;span class="normal"&gt; 7&lt;/span&gt;
&lt;span class="normal"&gt; 8&lt;/span&gt;
&lt;span class="normal"&gt; 9&lt;/span&gt;
&lt;span class="normal"&gt;10&lt;/span&gt;
&lt;span class="normal"&gt;11&lt;/span&gt;
&lt;span class="normal"&gt;12&lt;/span&gt;
&lt;span class="normal"&gt;13&lt;/span&gt;
&lt;span class="normal"&gt;14&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;div&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
   &lt;span class="s1"&gt;&amp;#39;raw&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="kc"&gt;None&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
   &lt;span class="s1"&gt;&amp;#39;entities&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:[&lt;/span&gt;
      &lt;span class="sa"&gt;u&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;GOGL&amp;#39;&lt;/span&gt;
   &lt;span class="p"&gt;],&lt;/span&gt;
   &lt;span class="s1"&gt;&amp;#39;asn_registry&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;arin&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
   &lt;span class="s1"&gt;&amp;#39;network&amp;#39;&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="s1"&gt;&amp;#39;objects&amp;#39;&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="s1"&gt;&amp;#39;asn_country_code&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;US&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
   &lt;span class="s1"&gt;&amp;#39;asn_date&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
   &lt;span class="s1"&gt;&amp;#39;asn_cidr&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;8.8.8.0/24&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
   &lt;span class="s1"&gt;&amp;#39;nir&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="kc"&gt;None&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
   &lt;span class="s1"&gt;&amp;#39;query&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;8.8.8.8&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
   &lt;span class="s1"&gt;&amp;#39;asn&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;15169&amp;#39;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/div&gt;
&lt;p&gt;However, outside of code this is not particularly easy to parse (well, still
better than WHOIS results), particularly when digging deeper in to the data with
some queries producing hundreds of lines of information. There are many, many
&lt;a class="reference external" href="https://duckduckgo.com/?q=whois"&gt;whois services&lt;/a&gt; available online, but I could not find a comparable service
for RDAP. So I decided to try my hand at making (and, more importantly,
launching) one...&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="rdap-explorer-2"&gt;
&lt;h2&gt;RDAP Explorer&lt;/h2&gt;
&lt;p&gt;The fruit of my (light) labor is &lt;a class="reference external" href="https://rdap-explorer.com"&gt;RDAP Explorer&lt;/a&gt;, a web interface for running
RDAP queries using the &lt;tt class="docutils literal"&gt;ipwhois&lt;/tt&gt; package. Currently, the output is not much
more than a nicely formatted expandable tree of information. This works well for
containing the vast amounts of data that RDAP queries can produce.&lt;/p&gt;
&lt;p&gt;The website is my first project built on &lt;a class="reference external" href="https://www.djangoproject.com/"&gt;Django&lt;/a&gt;, which was pretty painless to
get started with. This also enabled me to continue learning about nginx and
introduced me to &lt;a class="reference external" href="https://github.com/unbit/uwsgi"&gt;uwsgi&lt;/a&gt;. This combination proved a bit tougher to get a handle
on given my greater experience with Apache and PHP, but ultimately getting
everything running in &amp;quot;production&amp;quot; was not particularly difficult or time
consuming.&lt;/p&gt;
&lt;p&gt;For future development I intend to add clearer summary information for the
results and searching of the full result tree. For now I will just enjoy my
first launch of a Python project.&lt;/p&gt;
&lt;/div&gt;
</content><category term="Technology"></category><category term="django"></category><category term="ip"></category><category term="ipv4"></category><category term="ipv6"></category><category term="ipwhois"></category><category term="nginx"></category><category term="python"></category><category term="rdap"></category><category term="uwsgi"></category><category term="whois"></category></entry><entry><title>12 Years of Gmail, Part 5: Mail</title><link href="https://www.chris-wells.net/articles/2016/12/05/12-years-gmail-5-mail" rel="alternate"></link><published>2016-12-05T12:08:00-05:00</published><updated>2016-12-05T12:08:00-05:00</updated><author><name>Christopher Charbonneau Wells</name></author><id>tag:www.chris-wells.net,2016-12-05:/articles/2016/12/05/12-years-gmail-5-mail</id><summary type="html">
&lt;p&gt;After taking a look at the &lt;a class="reference external" href="https://www.chris-wells.net/articles/2016/11/18/12-years-gmail-4-chat"&gt;chat data&lt;/a&gt; in my export, I am finally ready to move
on to some of the actual mail! Much of what I will look at here is pretty
similar to what I was able to turn up with chat data. I tried to branch out a
bit, bringing in a new package to create word clouds, and also refactored some
of the &lt;a class="reference external" href="https://github.com/cdubz/takeout-inspector"&gt;Takeout Inspector&lt;/a&gt; code to form the beginning of a more &amp;quot;formal&amp;quot; report
generating process (instead of just spitting out a single HTML file with only a
certain subset of the data). Hopefully I can continue to improve this to a point
allowing for easier report generation for any user. Anyway, on to the mail data!&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;a class="reference external" href="https://www.chris-wells.net/articles/2016/12/05/12-years-gmail-5-mail#top10recipients"&gt;Top 10 Recipients&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="reference external" href="https://www.chris-wells.net/articles/2016/12/05/12-years-gmail-5-mail#top10senders"&gt;Top 10 Senders&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="reference external" href="https://www.chris-wells.net/articles/2016/12/05/12-years-gmail-5-mail#durations"&gt;Thread Durations&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="reference external" href="https://www.chris-wells.net/articles/2016/12/05/12-years-gmail-5-mail#sizes"&gt;Thread Sizes&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="reference external" href="https://www.chris-wells.net/articles/2016/12/05/12-years-gmail-5-mail#days"&gt;Activity by Day of the Week&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="reference external" href="https://www.chris-wells.net/articles/2016/12/05/12-years-gmail-5-mail#hours"&gt;Activity by Hour of the Day&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="reference external" href="https://www.chris-wells.net/articles/2016/12/05/12-years-gmail-5-mail#labels"&gt;Label Usage&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="reference external" href="https://www.chris-wells.net/articles/2016/12/05/12-years-gmail-5-mail#subjectwords"&gt;Subject Word Cloud&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</summary><content type="html">&lt;blockquote class="small"&gt;
    &lt;em&gt;
        This post is part of my series, &lt;a href="/tag/12-years-of-gmail"&gt;12 Years of Gmail&lt;/a&gt;,
        taking a look at the data Google has accumulated on me over the past
        12 years of using various Google services and documenting the
        learning experience developing an open source Python project
        (&lt;a href="https://github.com/cdubz/takeout-inspector"&gt;Takeout Inspector&lt;/a&gt;)
        to analyze that data.
    &lt;/em&gt;
&lt;/blockquote&gt;
&lt;p&gt;After taking a look at the &lt;a class="reference external" href="https://www.chris-wells.net/articles/2016/11/18/12-years-gmail-4-chat"&gt;chat data&lt;/a&gt; in my export, I am finally ready to move
on to some of the actual mail! Much of what I will look at here is pretty
similar to what I was able to turn up with chat data. I tried to branch out a
bit, bringing in a new package to create word clouds, and also refactored some
of the &lt;a class="reference external" href="https://github.com/cdubz/takeout-inspector"&gt;Takeout Inspector&lt;/a&gt; code to form the beginning of a more &amp;quot;formal&amp;quot; report
generating process (instead of just spitting out a single HTML file with only a
certain subset of the data). Hopefully I can continue to improve this to a point
allowing for easier report generation for any user. Anyway, on to the mail data!&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;a class="reference external" href="https://www.chris-wells.net/articles/2016/12/05/12-years-gmail-5-mail#top10recipients"&gt;Top 10 Recipients&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="reference external" href="https://www.chris-wells.net/articles/2016/12/05/12-years-gmail-5-mail#top10senders"&gt;Top 10 Senders&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="reference external" href="https://www.chris-wells.net/articles/2016/12/05/12-years-gmail-5-mail#durations"&gt;Thread Durations&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="reference external" href="https://www.chris-wells.net/articles/2016/12/05/12-years-gmail-5-mail#sizes"&gt;Thread Sizes&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="reference external" href="https://www.chris-wells.net/articles/2016/12/05/12-years-gmail-5-mail#days"&gt;Activity by Day of the Week&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="reference external" href="https://www.chris-wells.net/articles/2016/12/05/12-years-gmail-5-mail#hours"&gt;Activity by Hour of the Day&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="reference external" href="https://www.chris-wells.net/articles/2016/12/05/12-years-gmail-5-mail#labels"&gt;Label Usage&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="reference external" href="https://www.chris-wells.net/articles/2016/12/05/12-years-gmail-5-mail#subjectwords"&gt;Subject Word Cloud&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="section" id="top-10-recipients-1"&gt;
&lt;span id="top10recipients"&gt;&lt;/span&gt;&lt;h2&gt;Top 10 Recipients&lt;/h2&gt;
&lt;div class="overflow-x-container"&gt;
    &lt;div id="c8c09514-3d1b-46a4-8e47-3a39233fb59b" style="height: 100%; width: 100%;" class="plotly-graph-div"&gt;
        &lt;img src="/static/images/2016/12yog/mail-top-10-recipients.png" alt="Top 10 Recipients" class="no-js-remove" /&gt;
    &lt;/div&gt;
&lt;/div&gt;&lt;p&gt;This is one of the first graphs I produced while prototyping this idea. The top
recipient (person I have sent email to) by far, &lt;strong&gt;Crystal Meyer&lt;/strong&gt;, is my wife
and the remaining folks are a mix of friends and family so there is nothing much
to dig in to for this graph.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="top-10-senders-1"&gt;
&lt;span id="top10senders"&gt;&lt;/span&gt;&lt;h2&gt;Top 10 Senders&lt;/h2&gt;
&lt;div class="overflow-x-container"&gt;
    &lt;div id="8b8f3a55-f147-46e3-bafe-9ad121a49d9d" style="height: 100%; width: 100%;" class="plotly-graph-div"&gt;
        &lt;img src="/static/images/2016/12yog/mail-top-10-senders.png" alt="Top 10 Senders" class="no-js-remove" /&gt;
    &lt;/div&gt;
&lt;/div&gt;&lt;p&gt;This is at least slightly more interesting - while &lt;strong&gt;Crystal Meyer&lt;/strong&gt; is the top
person I send email &lt;em&gt;to&lt;/em&gt;, she is not the top person I receive email &lt;em&gt;from&lt;/em&gt;. So
who is this mystery &lt;strong&gt;Lindsey Campbell&lt;/strong&gt;? Not a person at all! This happens to
be a &amp;quot;noreply&amp;quot; style address used for automated emails from a website forum
dedicated to a &lt;a class="reference external" href="http://www.l4d.com/"&gt;Left 4 Dead&lt;/a&gt; game server that I operated for a couple of years.&lt;/p&gt;
&lt;p&gt;Number two on this list, &lt;strong&gt;Craig Wilber&lt;/strong&gt; is also not a real person. This heavy
traffic is from an automated email service from &lt;a class="reference external" href="http://www.dnsstuff.com/"&gt;DNSstuff&lt;/a&gt; that I have used in
the past to alert me of DNS issues for a website. For exactly one year from 2006
to 2007 I received a couple of emails from this service every day. Looking at
the content of some of these emails, I am not quite sure why I received so many
for such a long time. They all report odd results about changing DNS records
every couple of hours. Curious...&lt;/p&gt;
&lt;p&gt;After those two and my wife, the rest of the list rounds out with a mix of more
automated email services and other friends and family members as above.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="thread-durations-1"&gt;
&lt;span id="durations"&gt;&lt;/span&gt;&lt;h2&gt;Thread Durations&lt;/h2&gt;
&lt;div class="overflow-x-container"&gt;
    &lt;div id="f9899ff5-c6f5-4b70-ab4d-071b9de788b3" style="height: 100%; width: 100%;" class="plotly-graph-div"&gt;
        &lt;img src="/static/images/2016/12yog/mail-thread-durations.png" alt="Thread Durations" class="no-js-remove" /&gt;
    &lt;/div&gt;
&lt;/div&gt;&lt;p&gt;This graphs uses Gmail's thread IDs to determine total duration for each
mail thread (based on the &lt;tt class="docutils literal"&gt;Date&lt;/tt&gt; header of the first and last email). The
following query retrieves the basis for this information:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;table class="highlighttable"&gt;&lt;tr&gt;&lt;td class="linenos"&gt;&lt;div class="linenodiv"&gt;&lt;pre&gt;&lt;span class="normal"&gt;0&lt;/span&gt;
&lt;span class="normal"&gt;1&lt;/span&gt;
&lt;span class="normal"&gt;2&lt;/span&gt;
&lt;span class="normal"&gt;3&lt;/span&gt;
&lt;span class="normal"&gt;4&lt;/span&gt;
&lt;span class="normal"&gt;5&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;div&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;strftime&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;%s&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;MAX&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;`&lt;/span&gt;&lt;span class="nb"&gt;date&lt;/span&gt;&lt;span class="o"&gt;`&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;strftime&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;%s&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;MIN&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;`&lt;/span&gt;&lt;span class="nb"&gt;date&lt;/span&gt;&lt;span class="o"&gt;`&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;AS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;duration&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="k"&gt;COUNT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;message_key&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;AS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;message_count&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;messages&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;gmail_labels&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;NOT&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;LIKE&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;%Chat%&amp;#39;&lt;/span&gt;
&lt;span class="k"&gt;GROUP&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;BY&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;gmail_thread_id&lt;/span&gt;
&lt;span class="k"&gt;HAVING&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;message_count&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/div&gt;
&lt;p&gt;In this case I chose to only include threads of more than one email
(&lt;strong&gt;Line 5&lt;/strong&gt;) to keep out things like newsletters and other automated email
services. So this &lt;em&gt;should&lt;/em&gt; reflect mostly real conversations with actual people.&lt;/p&gt;
&lt;p&gt;No duration really dominates here, but if this were re-categorized as &amp;quot;one week
or less&amp;quot; and &amp;quot;more than one week&amp;quot;, the latter would only account for a little
more than 10% of communications.&lt;/p&gt;
&lt;p&gt;The &lt;em&gt;longest&lt;/em&gt; thread lasted &lt;strong&gt;more than seven years&lt;/strong&gt; (221,932,765 seconds) and
only had four emails. It was an alert service for &lt;a class="reference external" href="https://bugzilla.mozilla.org/show_bug.cgi?id=285774"&gt;Bug 285774&lt;/a&gt;, an issue with
the &lt;a class="reference external" href="http://caminobrowser.org/"&gt;Camino web browser&lt;/a&gt; that I was apparently interested in back in 2006. In
fact, the top five longest threads are &lt;em&gt;all&lt;/em&gt; Mozilla bug reports relating to
Camino (aka not real conversations).&lt;/p&gt;
&lt;p&gt;The first and longest &lt;em&gt;real&lt;/em&gt; thread is only three emails wherein I send my
résumé to someone and receive a response &lt;strong&gt;nine months later&lt;/strong&gt;. I guess he
wasn't impressed...&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="thread-sizes-1"&gt;
&lt;span id="sizes"&gt;&lt;/span&gt;&lt;h2&gt;Thread Sizes&lt;/h2&gt;
&lt;div class="overflow-x-container"&gt;
    &lt;div id="5e803498-ca33-44e2-807a-37f7ed92f0d9" style="height: 100%; width: 100%;" class="plotly-graph-div"&gt;
        &lt;img src="/static/images/2016/12yog/mail-thread-sizes.png" alt="Thread Sizes" class="no-js-remove" /&gt;
    &lt;/div&gt;
&lt;/div&gt;&lt;p&gt;Again using Gmail's thread ID email header, this graph compares thread
sizes by the total number of emails in the thread (at least two emails). The
curve ends up being almost exactly inverse except for small deviations and a
couple of anomalies leading up to one big 98-email thread.&lt;/p&gt;
&lt;p&gt;The first small anomaly is nine &lt;strong&gt;23-email&lt;/strong&gt; threads (zoom in to see it). Not
surprisingly, these threads have nothing to do with one another. Some are
between me and the developer of &lt;a class="reference external" href="https://web.archive.org/web/20110805131134/http://www.simonhaertel.de/quinn"&gt;Quinn&lt;/a&gt; back in the early 2000's, another is a
vacation planning thread with some friends, one relates to my wife and I closing
on our first home, and the remainder are random conversations with friends.&lt;/p&gt;
&lt;p&gt;The more noticeable anomaly is 19 &lt;strong&gt;61-email&lt;/strong&gt; threads and it turns out these
threads are in fact related somehow. As I mentioned previously in the
&lt;a class="reference external" href="https://www.chris-wells.net/articles/2016/12/05/12-years-gmail-5-mail#top10senders"&gt;Top 10 Senders&lt;/a&gt; section, I used to run a forum for a Left 4 Dead game server.
This forum was not hugely popular, but it was eventually targeted by automated
spam service that would attempt to create large swaths of accounts to spam the
forum. In order to combat this, I activated a function requiring manual review
of all newly created accounts. This is what led the &lt;tt class="docutils literal"&gt;noreply&lt;/tt&gt; address from the
forum to be far and away the top sender to my account.&lt;/p&gt;
&lt;p&gt;Why 61 emails per thread? I'm not sure. My best guess was that Gmail grouped
large threads after a set amount of time, but these threads range from about
two to seven days with no one thread having the same duration. I continued
digging around looking for similarities and, finding none, turned to the
Internet. While I could not find any definitive source or reason for this
grouping, at least a few other folks have stumbled on the oddity [&lt;a class="reference external" href="https://forums.digitalpoint.com/threads/magic-61-on-gmail.26623/"&gt;1&lt;/a&gt;, &lt;a class="reference external" href="http://blogoscoped.com/forum/165814.html"&gt;2&lt;/a&gt;]&lt;/p&gt;
&lt;p&gt;There are only two more numbers of note after 61:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;Two &lt;strong&gt;66-email&lt;/strong&gt; threads which are unrelated but both about planning and
launching a website.&lt;/li&gt;
&lt;li&gt;One &lt;strong&gt;98-email&lt;/strong&gt; thread, once again about the development process for the
&lt;a class="reference external" href="https://web.archive.org/web/20110805131134/http://www.simonhaertel.de/quinn"&gt;Quinn&lt;/a&gt; website.&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class="section" id="activity-by-day-of-the-week-1"&gt;
&lt;span id="days"&gt;&lt;/span&gt;&lt;h2&gt;Activity by Day of the Week&lt;/h2&gt;
&lt;div class="overflow-x-container"&gt;
    &lt;div id="8658a28e-e266-425d-b93e-702b15736e23" style="height: 100%; width: 100%;" class="plotly-graph-div"&gt;
        &lt;img src="/static/images/2016/12yog/mail-activity-by-dow.png" alt="Activity by Day of the Week" class="no-js-remove" /&gt;
    &lt;/div&gt;
&lt;/div&gt;&lt;p&gt;The distribution here for the days is not terribly surprising as I have never
done much emailing on the weekends. But I am surprised to see the sent and
received number almost 50/50 for every day of the week. I suspect this may be
skewed a little bit because spam is cleared out automatically every 30 days and
because I (regrettably) &amp;quot;cleaned&amp;quot; out &lt;em&gt;a lot&lt;/em&gt; of old email before doing my
Takeout export. Had I not cleaned things up, I suspect the sent/received
distribution would be closer to 40/60 than 50/50.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="activity-by-hour-of-the-day-1"&gt;
&lt;span id="hours"&gt;&lt;/span&gt;&lt;h2&gt;Activity by Hour of the Day&lt;/h2&gt;
&lt;div class="overflow-x-container"&gt;
    &lt;div id="aa666532-67e9-4061-9951-2b68b2a48097" style="height: 100%; width: 100%;" class="plotly-graph-div"&gt;
        &lt;img src="/static/images/2016/12yog/mail-activity-by-hour.png" alt="Activity by Hour of the Day" class="no-js-remove" /&gt;
    &lt;/div&gt;
&lt;/div&gt;&lt;p&gt;This activity falls pretty squarely in line with my &lt;a class="reference external" href="https://www.chris-wells.net/articles/2016/11/18/12-years-gmail-4-chat#times"&gt;chat time activity&lt;/a&gt;.
Apparently the best time to catch me for a response of any kind is on a weekday
at/around EST lunch time (11AM - 1PM EST).&lt;/p&gt;
&lt;p&gt;Interestingly, I think both the day of week and time of day activity were a
habit of Gmail in particular. Since I moved away from it, I find that I am more
prone to check and respond to emails in the evenings and on weekends. Perhaps
this is simply because of the lack of chat functionality with my new email
provider. I wish that I had had a longer history of Gmail &lt;em&gt;before&lt;/em&gt; chat was
introduced, as I wonder if that addition made some larger effect on my
overall communication habits.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="label-usage-1"&gt;
&lt;span id="labels"&gt;&lt;/span&gt;&lt;h2&gt;Label Usage&lt;/h2&gt;
&lt;div class="overflow-x-container"&gt;
    &lt;div id="41473ea5-230f-424d-a0c3-0ce518acbba6" style="height: 100%; width: 100%;" class="plotly-graph-div"&gt;
        &lt;img src="/static/images/2016/12yog/mail-label-usage.png" alt="Label Usage" class="no-js-remove" /&gt;
    &lt;/div&gt;
&lt;/div&gt;&lt;p&gt;I was never a particularly heavy user of labels so this graph is mostly
dominated by the default labels that Gmail applies automatically. Removing those
from the graph (click the labels in the legend) reveals the labels I did
actually use:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;The most used label, &lt;strong&gt;FBF&lt;/strong&gt; refers to &lt;a class="reference external" href="http://www.friendsofburkinafaso.org"&gt;Friends of Burkina Faso&lt;/a&gt;, an
organization I provide some volunteer work to on occasion.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;PC&lt;/strong&gt; labeled emails relate to my time in &lt;a class="reference external" href="https://www.peacecorps.gov/"&gt;Peace Corps&lt;/a&gt;, most of these
emails are part of the long application process.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;House&lt;/strong&gt; emails relate to the many, many back-and-forth emails and documents
involved in purchasing a home.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;RAID&lt;/strong&gt; refers to the &lt;a class="reference external" href="https://github.com/bovard/raid"&gt;Rogue AI Dungeon&lt;/a&gt; project I worked on with friends.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;CHLHS&lt;/strong&gt; is the &lt;a class="reference external" href="http://cheslights.org/"&gt;Chesapeake Chapter of the U.S. Lighthouse Society&lt;/a&gt;, another
organization I have volunteered with in the past.&lt;/li&gt;
&lt;li&gt;Last but not least, &lt;strong&gt;haxors.com&lt;/strong&gt; is a domain that I have owned for many
years and put &lt;em&gt;a lot&lt;/em&gt; of random projects on. By the time I started using
Gmail, I used this domain less and less so there are not very many emails with
this label (compared to the others).&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I never found the labels concept particularly useful - my emails tend to have
one primary subject and mixing labels would be a very rare occurrence. Prior to
using Gmail and labels, I did make heavy use of folders with my own email
services and to this day I still do with Exchange-based services. The Gmail
experience did a very effective job of pushing users to the &amp;quot;archive it all and
search as needed&amp;quot; philosophy thanks to Google's powerful search algorithms.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="subject-word-cloud-1"&gt;
&lt;span id="subjectwords"&gt;&lt;/span&gt;&lt;h2&gt;Subject Word Cloud&lt;/h2&gt;
&lt;img alt="Mail Subject Word Cloud" class="img-center" src="/static/images/2016/12yog/mail-subject-word-cloud.png" /&gt;
&lt;p&gt;Most of the words that pop out here are not at all surprising given the topics
covered in this post. The subject line of that automated forum email was always
the same, &amp;quot;Activate user account&amp;quot;, so it dominates the graphic. The rest tend to
be either common English parts of speech and things related to Peace Corps or
side projects I have worked on.&lt;/p&gt;
&lt;p&gt;This graphic is generated using the &lt;a class="reference external" href="https://pypi.python.org/pypi/wordcloud"&gt;wordcloud&lt;/a&gt; package, which has a very
simple API for Python and also includes an advanced feature for building the
word clouds using masks.&lt;/p&gt;
&lt;p&gt;As this graphic is only based on email &lt;em&gt;Subject&lt;/em&gt; headers, getting the data from
Takeout Inspector's sqlite table is very simple:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;table class="highlighttable"&gt;&lt;tr&gt;&lt;td class="linenos"&gt;&lt;div class="linenodiv"&gt;&lt;pre&gt;&lt;span class="normal"&gt;0&lt;/span&gt;
&lt;span class="normal"&gt;1&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;div&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;subject&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;messages&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;gmail_labels&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;NOT&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;LIKE&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;%Chat%&amp;#39;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;AND&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;subject&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;!=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/div&gt;
&lt;p&gt;From here Python is used to separate and clean the words and calculate
frequency:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;table class="highlighttable"&gt;&lt;tr&gt;&lt;td class="linenos"&gt;&lt;div class="linenodiv"&gt;&lt;pre&gt;&lt;span class="normal"&gt; 0&lt;/span&gt;
&lt;span class="normal"&gt; 1&lt;/span&gt;
&lt;span class="normal"&gt; 2&lt;/span&gt;
&lt;span class="normal"&gt; 3&lt;/span&gt;
&lt;span class="normal"&gt; 4&lt;/span&gt;
&lt;span class="normal"&gt; 5&lt;/span&gt;
&lt;span class="normal"&gt; 6&lt;/span&gt;
&lt;span class="normal"&gt; 7&lt;/span&gt;
&lt;span class="normal"&gt; 8&lt;/span&gt;
&lt;span class="normal"&gt; 9&lt;/span&gt;
&lt;span class="normal"&gt;10&lt;/span&gt;
&lt;span class="normal"&gt;11&lt;/span&gt;
&lt;span class="normal"&gt;12&lt;/span&gt;
&lt;span class="normal"&gt;13&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;div&gt;&lt;pre&gt;&lt;span&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="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;row&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;fetchall&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="n"&gt;subject&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;row&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="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;prefix&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;Re:&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;Fwd:&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt;
        &lt;span class="n"&gt;subject&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;subject&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;prefix&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;subject&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;re&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sub&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;[^a-zA-Z. ]&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;subject&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;strip&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;lower&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;word&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;subject&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39; &amp;#39;&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;word&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;word&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;rstrip&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;.&amp;#39;&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;word&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;words&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="n"&gt;word&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
            &lt;span class="n"&gt;words&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="o"&gt;+=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/div&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;strong&gt;Lines 3 &amp;amp; 4&lt;/strong&gt; remove common email subject prefixes.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Line 6&lt;/strong&gt; filters out non-alpha characters, leaving dots (for web addresses)
and space characters, and converts all letters to lower case.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Lines 8 - 13&lt;/strong&gt; break each subject in to a list of words, removes periods
from the end of words with &lt;tt class="docutils literal"&gt;rstrip&lt;/tt&gt; (this assumes sentence structure) and
finally ticks the frequency count for each word.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;If required, the &lt;a class="reference external" href="https://docs.python.org/2.7/library/functions.html#sorted"&gt;sorted()&lt;/a&gt; function is great for creating sorted lists from a
dictionary like &lt;tt class="docutils literal"&gt;words&lt;/tt&gt; above. However this word cloud simply filters the list
for all words with more 100 occurrences before passing the data on to
wordcloud's &lt;a class="reference external" href="https://amueller.github.io/word_cloud/generated/wordcloud.WordCloud.html?highlight=generate_from_frequencies#wordcloud.WordCloud.generate_from_frequencies"&gt;generate_from_frequencies()&lt;/a&gt; method. Wordcloud can also handle
the frequency calculate itself with the regular &lt;a class="reference external" href="https://amueller.github.io/word_cloud/generated/wordcloud.WordCloud.html#wordcloud.WordCloud.generate"&gt;generate()&lt;/a&gt; method&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="more-to-come"&gt;
&lt;h2&gt;More to Come&lt;/h2&gt;
&lt;p&gt;Much of the graphs generated for email information are very similar to what was
done for chat information. I will use a future post in the series to dig deeper
in to some of the more technical email headers, hopefully take a look at &lt;em&gt;body&lt;/em&gt;
word usage/statistics and come up with some other ways to look at and compare
more aggregate message data.&lt;/p&gt;
&lt;/div&gt;
</content><category term="Technology"></category><category term="12 years of gmail"></category><category term="email"></category><category term="graphing"></category><category term="plotly"></category><category term="python"></category><category term="takeout inspector"></category><category term="wordcloud"></category></entry><entry><title>Examining the Remnants of a Small DDoS Attack</title><link href="https://www.chris-wells.net/articles/2016/12/03/ddos-examination" rel="alternate"></link><published>2016-12-03T12:00:00-05:00</published><updated>2016-12-03T12:00:00-05:00</updated><author><name>Christopher Charbonneau Wells</name></author><id>tag:www.chris-wells.net,2016-12-03:/articles/2016/12/03/ddos-examination</id><summary type="html">
&lt;p&gt;On Sunday (27 November 2016) a small website that I advise on was the victim
of a DDoS attack that managed to knock the site offline. I received notice on
Monday that the website was not working. I was able to ssh to the web server and
quickly found that the database service was stopped. After a brief examination
of the database logs (nothing too out of the ordinary), I started the service
back up and sure enough the website came back online. As the website runs on
&lt;a class="reference external" href="https://www.drupal.org/"&gt;Drupal&lt;/a&gt;, I logged in to take a peak at the &lt;em&gt;Recent log messages&lt;/em&gt; and found
hundreds of records of log in attempts from a lot of different IP addresses.
User accounts on the website are only used by administrators to update content,
so it was clear that the site was hit by a &lt;a class="reference external" href="http://www.digitalattackmap.com/understanding-ddos/"&gt;DDoS attack&lt;/a&gt;!&lt;/p&gt;
</summary><content type="html">&lt;span class="target" id="top"&gt;&lt;/span&gt;
&lt;p&gt;On Sunday (27 November 2016) a small website that I advise on was the victim
of a DDoS attack that managed to knock the site offline. I received notice on
Monday that the website was not working. I was able to ssh to the web server and
quickly found that the database service was stopped. After a brief examination
of the database logs (nothing too out of the ordinary), I started the service
back up and sure enough the website came back online. As the website runs on
&lt;a class="reference external" href="https://www.drupal.org/"&gt;Drupal&lt;/a&gt;, I logged in to take a peak at the &lt;em&gt;Recent log messages&lt;/em&gt; and found
hundreds of records of log in attempts from a lot of different IP addresses.
User accounts on the website are only used by administrators to update content,
so it was clear that the site was hit by a &lt;a class="reference external" href="http://www.digitalattackmap.com/understanding-ddos/"&gt;DDoS attack&lt;/a&gt;!&lt;/p&gt;

&lt;p&gt;After getting things back online, I poked around the various log files to try to
get an idea of what happened. The Drupal watchdog logs seemed to indicate that
the attack started around 15:22 EST and overloaded the server's memory around
15:42 EST. The Apache server's access logs, however, revealed that the attack
started closer to 14:54 EST and lasted until 20:57 EST (about six hours). The
Drupal watchdog service relies on a database connection, so because that was
knocked offline around 15:42 EST, it was, of course, unable to continue recording
the events.&lt;/p&gt;
&lt;div class="section" id="setup"&gt;
&lt;h2&gt;Setup&lt;/h2&gt;
&lt;p&gt;I wanted to evaluate Apache's access logs a little closer, so I imported the
logs in to an sqlite table. The access logs are in the &lt;a class="reference external" href="https://httpd.apache.org/docs/2.4/logs.html#combined"&gt;Combined Log&lt;/a&gt; format:
&lt;tt class="docutils literal"&gt;%h %l %u %t &amp;quot;%r&amp;quot; %&amp;gt;s %b &lt;span class="pre"&gt;&amp;quot;%{Referer}&amp;quot;&lt;/span&gt; &lt;span class="pre"&gt;&amp;quot;%{User-agent}&amp;quot;&lt;/span&gt;&lt;/tt&gt; (host ident user time
&amp;quot;request&amp;quot; response-code response-size &amp;quot;referer&amp;quot; &amp;quot;user-agent&amp;quot;). In order to turn
this in to a tab-delimited file for import in to sqlite, I used the following
regular expression:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;table class="highlighttable"&gt;&lt;tr&gt;&lt;td class="linenos"&gt;&lt;div class="linenodiv"&gt;&lt;pre&gt;&lt;span class="normal"&gt;0&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;div&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;\&lt;span class="n"&gt;d&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;\&lt;span class="o"&gt;.&lt;/span&gt;\&lt;span class="n"&gt;d&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;\&lt;span class="o"&gt;.&lt;/span&gt;\&lt;span class="n"&gt;d&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;\&lt;span class="o"&gt;.&lt;/span&gt;\&lt;span class="n"&gt;d&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="o"&gt;.+&lt;/span&gt;&lt;span class="p"&gt;)&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="p"&gt;[(&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="s2"&gt;&amp;quot;(.+)&amp;quot;&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;\&lt;span class="n"&gt;d&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;\&lt;span class="n"&gt;d&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;(.+)&amp;quot;&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;(.+)&amp;quot;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/div&gt;
&lt;p&gt;This regular expression matches each of the relevant pieces of information in to
groups that can be used in a replacement expression using your favorite regular
expression parser. In my case, I discarded the &lt;tt class="docutils literal"&gt;ident&lt;/tt&gt; and &lt;tt class="docutils literal"&gt;user&lt;/tt&gt; fields as
they were always empty, further separated the &lt;tt class="docutils literal"&gt;request&lt;/tt&gt; fields in to three
parts (method, request and protocol) and imported all this data in to a table
with the following structure:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;table class="highlighttable"&gt;&lt;tr&gt;&lt;td class="linenos"&gt;&lt;div class="linenodiv"&gt;&lt;pre&gt;&lt;span class="normal"&gt; 0&lt;/span&gt;
&lt;span class="normal"&gt; 1&lt;/span&gt;
&lt;span class="normal"&gt; 2&lt;/span&gt;
&lt;span class="normal"&gt; 3&lt;/span&gt;
&lt;span class="normal"&gt; 4&lt;/span&gt;
&lt;span class="normal"&gt; 5&lt;/span&gt;
&lt;span class="normal"&gt; 6&lt;/span&gt;
&lt;span class="normal"&gt; 7&lt;/span&gt;
&lt;span class="normal"&gt; 8&lt;/span&gt;
&lt;span class="normal"&gt; 9&lt;/span&gt;
&lt;span class="normal"&gt;10&lt;/span&gt;
&lt;span class="normal"&gt;11&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;div&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="k"&gt;CREATE&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;TABLE&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;&amp;quot;access&amp;quot;&lt;/span&gt;
&lt;span class="p"&gt;(&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;host&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;TEXT&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;NOT&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;time&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;TEXT&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;NOT&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;request_method&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;TEXT&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;NOT&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;TEXT&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;NOT&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;request_protocol&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;TEXT&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;NOT&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;status_code&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;INTEGER&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;NOT&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;response_size&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;INTEGER&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;NOT&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;referrer&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;TEXT&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;NOT&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;user_agent&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;TEXT&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;NOT&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;NULL&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;div class="section" id="requests"&gt;
&lt;h2&gt;Requests&lt;/h2&gt;
&lt;p&gt;First, taking a look at the total effort:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;table class="highlighttable"&gt;&lt;tr&gt;&lt;td class="linenos"&gt;&lt;div class="linenodiv"&gt;&lt;pre&gt;&lt;span class="normal"&gt; 0&lt;/span&gt;
&lt;span class="normal"&gt; 1&lt;/span&gt;
&lt;span class="normal"&gt; 2&lt;/span&gt;
&lt;span class="normal"&gt; 3&lt;/span&gt;
&lt;span class="normal"&gt; 4&lt;/span&gt;
&lt;span class="normal"&gt; 5&lt;/span&gt;
&lt;span class="normal"&gt; 6&lt;/span&gt;
&lt;span class="normal"&gt; 7&lt;/span&gt;
&lt;span class="normal"&gt; 8&lt;/span&gt;
&lt;span class="normal"&gt; 9&lt;/span&gt;
&lt;span class="normal"&gt;10&lt;/span&gt;
&lt;span class="normal"&gt;11&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;div&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;request_method&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;AS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;method&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;COUNT&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;AS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;requests&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;access&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;time&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;2016-11-27 19:54:55&amp;#39;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;AND&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;time&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;lt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;2016-11-28 02:56:14&amp;#39;&lt;/span&gt;
&lt;span class="k"&gt;GROUP&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;BY&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;request_method&lt;/span&gt;

&lt;span class="k"&gt;method&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;requests&lt;/span&gt;
&lt;span class="c1"&gt;----------------&lt;/span&gt;
&lt;span class="k"&gt;GET&lt;/span&gt;&lt;span class="w"&gt;     &lt;/span&gt;&lt;span class="mi"&gt;55969&lt;/span&gt;
&lt;span class="n"&gt;POST&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="mi"&gt;9660&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/div&gt;
&lt;p&gt;The &lt;tt class="docutils literal"&gt;time&lt;/tt&gt; constraints here represent the first and last requests that were
clearly a part of the attack. There was some legitimate traffic in this period
but it is far outweighed by the attack traffic. This request reveals that there
were &lt;strong&gt;55,969&lt;/strong&gt; &lt;tt class="docutils literal"&gt;GET&lt;/tt&gt; requests and &lt;strong&gt;9,660&lt;/strong&gt; &lt;tt class="docutils literal"&gt;POST&lt;/tt&gt; requests during the 6
hours of the attack. This averages out to about &lt;strong&gt;180 requests per minute&lt;/strong&gt;, or
not very much at all.&lt;/p&gt;
&lt;p&gt;But a more fine grained query reveals the timing a little better:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;table class="highlighttable"&gt;&lt;tr&gt;&lt;td class="linenos"&gt;&lt;div class="linenodiv"&gt;&lt;pre&gt;&lt;span class="normal"&gt; 0&lt;/span&gt;
&lt;span class="normal"&gt; 1&lt;/span&gt;
&lt;span class="normal"&gt; 2&lt;/span&gt;
&lt;span class="normal"&gt; 3&lt;/span&gt;
&lt;span class="normal"&gt; 4&lt;/span&gt;
&lt;span class="normal"&gt; 5&lt;/span&gt;
&lt;span class="normal"&gt; 6&lt;/span&gt;
&lt;span class="normal"&gt; 7&lt;/span&gt;
&lt;span class="normal"&gt; 8&lt;/span&gt;
&lt;span class="normal"&gt; 9&lt;/span&gt;
&lt;span class="normal"&gt;10&lt;/span&gt;
&lt;span class="normal"&gt;11&lt;/span&gt;
&lt;span class="normal"&gt;12&lt;/span&gt;
&lt;span class="normal"&gt;13&lt;/span&gt;
&lt;span class="normal"&gt;14&lt;/span&gt;
&lt;span class="normal"&gt;15&lt;/span&gt;
&lt;span class="normal"&gt;16&lt;/span&gt;
&lt;span class="normal"&gt;17&lt;/span&gt;
&lt;span class="normal"&gt;18&lt;/span&gt;
&lt;span class="normal"&gt;19&lt;/span&gt;
&lt;span class="normal"&gt;20&lt;/span&gt;
&lt;span class="normal"&gt;21&lt;/span&gt;
&lt;span class="normal"&gt;22&lt;/span&gt;
&lt;span class="normal"&gt;23&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;div&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;strftime&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;%d&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;time&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;AS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;day&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;strftime&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;%H&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;time&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;AS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;hour&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;strftime&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;%M&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;time&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;AS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;`&lt;/span&gt;&lt;span class="k"&gt;min&lt;/span&gt;&lt;span class="o"&gt;`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;COUNT&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;AS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;requests&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;access&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;time&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;2016-11-27 19:54:55&amp;#39;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;AND&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;time&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;lt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;2016-11-28 02:56:14&amp;#39;&lt;/span&gt;
&lt;span class="k"&gt;GROUP&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;BY&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;day&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;hour&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;`&lt;/span&gt;&lt;span class="k"&gt;min&lt;/span&gt;&lt;span class="o"&gt;`&lt;/span&gt;
&lt;span class="k"&gt;ORDER&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;BY&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;requests&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;DESC&lt;/span&gt;

&lt;span class="k"&gt;day&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;hour&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;min&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;requests&lt;/span&gt;
&lt;span class="c1"&gt;------------------------&lt;/span&gt;
&lt;span class="mi"&gt;27&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="mi"&gt;41&lt;/span&gt;&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="mi"&gt;4230&lt;/span&gt;
&lt;span class="mi"&gt;27&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="mi"&gt;39&lt;/span&gt;&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="mi"&gt;2550&lt;/span&gt;
&lt;span class="mi"&gt;27&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="mi"&gt;35&lt;/span&gt;&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="mi"&gt;2448&lt;/span&gt;
&lt;span class="mi"&gt;27&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="mi"&gt;38&lt;/span&gt;&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="mi"&gt;2418&lt;/span&gt;
&lt;span class="mi"&gt;27&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="mi"&gt;40&lt;/span&gt;&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="mi"&gt;2346&lt;/span&gt;
&lt;span class="mi"&gt;27&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="mi"&gt;37&lt;/span&gt;&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="mi"&gt;2232&lt;/span&gt;
&lt;span class="mi"&gt;27&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="mi"&gt;34&lt;/span&gt;&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="mi"&gt;1980&lt;/span&gt;
&lt;span class="mi"&gt;27&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="mi"&gt;44&lt;/span&gt;&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="mi"&gt;1872&lt;/span&gt;
&lt;span class="mi"&gt;27&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="mi"&gt;45&lt;/span&gt;&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="mi"&gt;1872&lt;/span&gt;
&lt;span class="mi"&gt;27&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="mi"&gt;36&lt;/span&gt;&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="mi"&gt;1836&lt;/span&gt;
&lt;span class="p"&gt;[...]&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/div&gt;
&lt;p&gt;Between 20:41 and 20:42 (UTC) the server was actually hit with &lt;strong&gt;4,230&lt;/strong&gt;
requests (about &lt;strong&gt;70 requests per second&lt;/strong&gt;). This burst is ultimately what
knocked the database server offline by 20:42 (the 15:42 EST noted above).&lt;/p&gt;
&lt;p&gt;How much bandwidth did all of these requests chew up?&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;table class="highlighttable"&gt;&lt;tr&gt;&lt;td class="linenos"&gt;&lt;div class="linenodiv"&gt;&lt;pre&gt;&lt;span class="normal"&gt;0&lt;/span&gt;
&lt;span class="normal"&gt;1&lt;/span&gt;
&lt;span class="normal"&gt;2&lt;/span&gt;
&lt;span class="normal"&gt;3&lt;/span&gt;
&lt;span class="normal"&gt;4&lt;/span&gt;
&lt;span class="normal"&gt;5&lt;/span&gt;
&lt;span class="normal"&gt;6&lt;/span&gt;
&lt;span class="normal"&gt;7&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;div&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;SUM&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;response_size&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;as&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;bandwidth&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;access&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;time&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;2016-11-27 19:54:55&amp;#39;&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="k"&gt;AND&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;time&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;lt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;2016-11-28 02:56:14&amp;#39;&lt;/span&gt;

&lt;span class="n"&gt;bandwidth&lt;/span&gt;
&lt;span class="c1"&gt;---------&lt;/span&gt;
&lt;span class="mi"&gt;311446609&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/div&gt;
&lt;p&gt;That's only about 300MB. Consistent with the size of the attack, not very much.
It is definitely &lt;em&gt;a lot&lt;/em&gt; more than the website in question normally does, but
not enough to raise any eyebrows.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="devices"&gt;
&lt;h2&gt;Devices&lt;/h2&gt;
&lt;p&gt;How many devices were involved in this attack?&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;table class="highlighttable"&gt;&lt;tr&gt;&lt;td class="linenos"&gt;&lt;div class="linenodiv"&gt;&lt;pre&gt;&lt;span class="normal"&gt;0&lt;/span&gt;
&lt;span class="normal"&gt;1&lt;/span&gt;
&lt;span class="normal"&gt;2&lt;/span&gt;
&lt;span class="normal"&gt;3&lt;/span&gt;
&lt;span class="normal"&gt;4&lt;/span&gt;
&lt;span class="normal"&gt;5&lt;/span&gt;
&lt;span class="normal"&gt;6&lt;/span&gt;
&lt;span class="normal"&gt;7&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;div&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;COUNT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;DISTINCT&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;host&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;AS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;hosts&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;access&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;time&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;2016-11-27 19:54:55&amp;#39;&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="k"&gt;AND&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;time&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;lt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;2016-11-28 02:56:14&amp;#39;&lt;/span&gt;

&lt;span class="n"&gt;hosts&lt;/span&gt;
&lt;span class="c1"&gt;-----&lt;/span&gt;
&lt;span class="mi"&gt;311&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/div&gt;
&lt;p&gt;Apparently only about 300. This is a far cry from the tens or hundreds of
thousands of devices believed to be controlled by the news-making
&lt;a class="reference external" href="https://www.malwaretech.com/2016/10/mapping-mirai-a-botnet-case-study.html"&gt;Mirai botnet&lt;/a&gt;, but apparently more than enough to defeat a $5/mo VPS instance
with a non-optimized Drupal 8 installation.&lt;/p&gt;
&lt;p&gt;Next up, which hosts did the most leg work?&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;table class="highlighttable"&gt;&lt;tr&gt;&lt;td class="linenos"&gt;&lt;div class="linenodiv"&gt;&lt;pre&gt;&lt;span class="normal"&gt; 0&lt;/span&gt;
&lt;span class="normal"&gt; 1&lt;/span&gt;
&lt;span class="normal"&gt; 2&lt;/span&gt;
&lt;span class="normal"&gt; 3&lt;/span&gt;
&lt;span class="normal"&gt; 4&lt;/span&gt;
&lt;span class="normal"&gt; 5&lt;/span&gt;
&lt;span class="normal"&gt; 6&lt;/span&gt;
&lt;span class="normal"&gt; 7&lt;/span&gt;
&lt;span class="normal"&gt; 8&lt;/span&gt;
&lt;span class="normal"&gt; 9&lt;/span&gt;
&lt;span class="normal"&gt;10&lt;/span&gt;
&lt;span class="normal"&gt;11&lt;/span&gt;
&lt;span class="normal"&gt;12&lt;/span&gt;
&lt;span class="normal"&gt;13&lt;/span&gt;
&lt;span class="normal"&gt;14&lt;/span&gt;
&lt;span class="normal"&gt;15&lt;/span&gt;
&lt;span class="normal"&gt;16&lt;/span&gt;
&lt;span class="normal"&gt;17&lt;/span&gt;
&lt;span class="normal"&gt;18&lt;/span&gt;
&lt;span class="normal"&gt;19&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;div&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;host&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;COUNT&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;as&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;requests&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;access&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;time&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;2016-11-27 19:54:55&amp;#39;&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="k"&gt;AND&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;time&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;lt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;2016-11-28 02:56:14&amp;#39;&lt;/span&gt;
&lt;span class="k"&gt;GROUP&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;BY&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;host&lt;/span&gt;
&lt;span class="k"&gt;ORDER&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;BY&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;requests&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;DESC&lt;/span&gt;

&lt;span class="k"&gt;host&lt;/span&gt;&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="n"&gt;requests&lt;/span&gt;
&lt;span class="c1"&gt;------------------------&lt;/span&gt;
&lt;span class="mi"&gt;213&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;251&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;182&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;115&lt;/span&gt;&lt;span class="w"&gt;     &lt;/span&gt;&lt;span class="mi"&gt;3738&lt;/span&gt;
&lt;span class="mi"&gt;212&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;114&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;109&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;218&lt;/span&gt;&lt;span class="w"&gt;     &lt;/span&gt;&lt;span class="mi"&gt;2784&lt;/span&gt;
&lt;span class="mi"&gt;138&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;201&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;151&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;94&lt;/span&gt;&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="mi"&gt;2448&lt;/span&gt;
&lt;span class="mi"&gt;213&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;251&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;182&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;111&lt;/span&gt;&lt;span class="w"&gt;     &lt;/span&gt;&lt;span class="mi"&gt;2352&lt;/span&gt;
&lt;span class="mi"&gt;213&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;251&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;182&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;105&lt;/span&gt;&lt;span class="w"&gt;     &lt;/span&gt;&lt;span class="mi"&gt;1992&lt;/span&gt;
&lt;span class="mi"&gt;213&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;251&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;182&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;106&lt;/span&gt;&lt;span class="w"&gt;     &lt;/span&gt;&lt;span class="mi"&gt;1734&lt;/span&gt;
&lt;span class="mi"&gt;202&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;67&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;9&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;42&lt;/span&gt;&lt;span class="w"&gt;         &lt;/span&gt;&lt;span class="mi"&gt;1554&lt;/span&gt;
&lt;span class="mi"&gt;184&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;106&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;128&lt;/span&gt;&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="mi"&gt;1368&lt;/span&gt;
&lt;span class="mi"&gt;213&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;251&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;182&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;114&lt;/span&gt;&lt;span class="w"&gt;     &lt;/span&gt;&lt;span class="mi"&gt;1338&lt;/span&gt;
&lt;span class="mi"&gt;103&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;45&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;230&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;202&lt;/span&gt;&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="mi"&gt;1332&lt;/span&gt;
&lt;span class="p"&gt;[...]&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/div&gt;
&lt;p&gt;After the top ten, things tend to wind down rather slowly to round out the 311
hosts involved. So what can be found out about these hosts? Not a ton, really,
other than who each IP belongs to. There are lots of bulk IP whois services
available online, I choose &lt;a class="reference external" href="http://www.domainiq.com/bulk_whois_ip"&gt;a random one&lt;/a&gt; and got the following information:&lt;/p&gt;
&lt;div class="table-responsive docutils container"&gt;
&lt;table border="1" class="docutils"&gt;
&lt;colgroup&gt;
&lt;col width="33%" /&gt;
&lt;col width="12%" /&gt;
&lt;col width="23%" /&gt;
&lt;col width="23%" /&gt;
&lt;col width="9%" /&gt;
&lt;/colgroup&gt;
&lt;thead valign="bottom"&gt;
&lt;tr&gt;&lt;th class="head"&gt;Domain&lt;/th&gt;
&lt;th class="head"&gt;IP&lt;/th&gt;
&lt;th class="head"&gt;ISP&lt;/th&gt;
&lt;th class="head"&gt;Organization&lt;/th&gt;
&lt;th class="head"&gt;Country&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody valign="top"&gt;
&lt;tr&gt;&lt;td&gt;gw-cluster015.ovh.net.&lt;/td&gt;
&lt;td&gt;213.251.182.115&lt;/td&gt;
&lt;td&gt;OVH SAS&lt;/td&gt;
&lt;td&gt;OVH SAS&lt;/td&gt;
&lt;td&gt;FR&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;server2.duurzaammedia.nl.&lt;/td&gt;
&lt;td&gt;212.114.109.218&lt;/td&gt;
&lt;td&gt;Whitelabel Hosting Solutions&lt;/td&gt;
&lt;td&gt;Whitelabel Hosting Solutions&lt;/td&gt;
&lt;td&gt;NL&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;static.94.151.201.138.clients.your-server.de.&lt;/td&gt;
&lt;td&gt;138.201.151.94&lt;/td&gt;
&lt;td&gt;HOS-181062&lt;/td&gt;
&lt;td&gt;HOS-181062&lt;/td&gt;
&lt;td&gt;DE&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;gw-cluster011.ovh.net.&lt;/td&gt;
&lt;td&gt;213.251.182.111&lt;/td&gt;
&lt;td&gt;OVH SAS&lt;/td&gt;
&lt;td&gt;OVH SAS&lt;/td&gt;
&lt;td&gt;FR&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;gw-cluster005.ovh.net.&lt;/td&gt;
&lt;td&gt;213.251.182.105&lt;/td&gt;
&lt;td&gt;OVH SAS&lt;/td&gt;
&lt;td&gt;OVH SAS&lt;/td&gt;
&lt;td&gt;FR&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;gw-cluster006.ovh.net.&lt;/td&gt;
&lt;td&gt;213.251.182.106&lt;/td&gt;
&lt;td&gt;OVH SAS&lt;/td&gt;
&lt;td&gt;OVH SAS&lt;/td&gt;
&lt;td&gt;FR&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;202.67.9.42&lt;/td&gt;
&lt;td&gt;202.67.9.42&lt;/td&gt;
&lt;td&gt;Unknown&lt;/td&gt;
&lt;td&gt;Mollindo Company Jakarta&lt;/td&gt;
&lt;td&gt;ID&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;fw-snet-n01.wc1.ord1.stabletransit.com.&lt;/td&gt;
&lt;td&gt;184.106.10.128&lt;/td&gt;
&lt;td&gt;Rackspace Hosting&lt;/td&gt;
&lt;td&gt;Cloud Sites  wc1.ord1&lt;/td&gt;
&lt;td&gt;US&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;gw-cluster014.ovh.net.&lt;/td&gt;
&lt;td&gt;213.251.182.114&lt;/td&gt;
&lt;td&gt;OVH SAS&lt;/td&gt;
&lt;td&gt;OVH SAS&lt;/td&gt;
&lt;td&gt;FR&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;sd108202.server.idn.vn.&lt;/td&gt;
&lt;td&gt;103.45.230.202&lt;/td&gt;
&lt;td&gt;Online Solution Company Limited&lt;/td&gt;
&lt;td&gt;Online Solution Company Limited&lt;/td&gt;
&lt;td&gt;VN&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;/div&gt;
&lt;p&gt;Most of these devices appear to be hosted on &lt;a class="reference external" href="https://www.ovh.com"&gt;OVH&lt;/a&gt;, and &lt;a class="reference external" href="https://www.rackspace.com"&gt;Rackspace&lt;/a&gt; is
another big player represented here. This likely indicates that the devices are
either compromised websites or rented machines set up specifically for DDoS'ing.
Either way, this attack was not orchestrated from the insecure IoT devices that
many fear will continue to grow massive botnets in the coming years.&lt;/p&gt;
&lt;div class="section" id="locations"&gt;
&lt;h3&gt;Locations&lt;/h3&gt;
&lt;p&gt;There is also a fair distribution of locations for those top ten hosts - France,
Netherlands, Germany, Indonesia, the United States and Vietnam are all
represented. So where did &lt;em&gt;all&lt;/em&gt; of the devices come from?&lt;/p&gt;
&lt;div class="overflow-x-container"&gt;
    &lt;div id="1fbb1256-a930-468b-8276-58e7bcb0b347" style="height: 775px; width: 800px;" class="plotly-graph-div"&gt;
        &lt;img src="/static/images/2016/ddos_locations.png" alt="DDoS devices locations" class="no-js-remove" /&gt;
    &lt;/div&gt;
&lt;/div&gt;&lt;p class="center small"&gt;For a nicer view, &lt;a class="reference external" href="https://www.chris-wells.net/stubs/2016/ddos_locations.html"&gt;open a full screen view of the map above.&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;This fuller picture reveals that in addition to the high concentration of
requests from OVH servers in France, there were also a lot of compromised
devices in certain American cities. Who owns the networks these devices sit on?
This time around I used &lt;a class="reference external" href="http://www.infobyip.com/ipbulklookup.php"&gt;InfobyIP's Bulk Lookup Tool&lt;/a&gt;, which kindly does not
limit the number of lookups per day (or at least I didn't hit the limit):&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;In &lt;strong&gt;Scottsdale, Arizona&lt;/strong&gt;, all devices are on &lt;strong&gt;GoDaddy.com, LLC&lt;/strong&gt; (&lt;a class="reference external" href="https://www.godaddy.com"&gt;GoDaddy&lt;/a&gt;) networks.&lt;/li&gt;
&lt;li&gt;In &lt;strong&gt;Brea, California&lt;/strong&gt;, all devices are on &lt;strong&gt;New Dream Network, LLC&lt;/strong&gt; (&lt;a class="reference external" href="https://www.dreamhost.com"&gt;Dreamhost&lt;/a&gt;) networks.&lt;/li&gt;
&lt;li&gt;In &lt;strong&gt;Provo, Utah&lt;/strong&gt;, all devices are on &lt;strong&gt;Unified Layer&lt;/strong&gt; (&lt;a class="reference external" href="https://unitedlayer.com/"&gt;United Layer&lt;/a&gt;)* networks.&lt;/li&gt;
&lt;li&gt;In &lt;strong&gt;Fort Lauderdale, Florida&lt;/strong&gt;, all devices are on &lt;strong&gt;InternetNamesForBusiness.com&lt;/strong&gt; (&lt;a class="reference external" href="http://internetnamesforbusiness.com/"&gt;INFB&lt;/a&gt;) networks.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;em&gt;* The United Layer devices appear to be a small mix of regional web hosting services, with the only major outlier being one device on HostGator.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;The total requests in these cities were not nearly as high as what came from
OVH, but they helped the total number of &lt;em&gt;devices&lt;/em&gt; from the United States more
than double those from France. These network owners also further advance the
theory that the devices were compromised website. It is possible that they were
not able to fire off any many requests as OVH's servers because they are smaller
website that hit resource caps.&lt;/p&gt;
&lt;blockquote&gt;
A lot more digging could be done here to identify and report individual
compromised websites, but I suspect that would be an adventure of it's own.
And one worth taking if time ever allows...&lt;/blockquote&gt;
&lt;/div&gt;
&lt;div class="section" id="user-agents"&gt;
&lt;h3&gt;User Agents&lt;/h3&gt;
&lt;p&gt;All of the devices used in the attack had the same user agent:
&lt;tt class="docutils literal"&gt;Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 6.1; Trident/4.0)&lt;/tt&gt;. User agents
are very easy to fake, so it's difficult to read much in this. I had not seen
the &lt;tt class="docutils literal"&gt;Trident&lt;/tt&gt; token in user agent strings before and found that it apparently
has to do with Internet Explorer's &lt;a class="reference external" href="https://blogs.msdn.microsoft.com/ie/2008/08/27/introducing-compatibility-view/"&gt;compatibility mode&lt;/a&gt;. Perhaps this indicates
that the compromised machines used in the attack are servery outdated Windows
virtual machines, but there is no definitive way to make that connection.&lt;/p&gt;
&lt;p&gt;One other interesting &amp;quot;user agent&amp;quot; string in the logs is &lt;tt class="docutils literal"&gt;() { &lt;span class="pre"&gt;foo;};echo;&lt;/span&gt; /bin/bash &lt;span class="pre"&gt;-c&lt;/span&gt; \&amp;quot;expr 299663299665 / 3; echo 333:; uname &lt;span class="pre"&gt;-a;&lt;/span&gt; echo 333:; &lt;span class="pre"&gt;id;\&amp;quot;&lt;/span&gt;&lt;/tt&gt;.
This string, along with requests to some known exploitable entry points,
attempts to execute a command to reveal some potentially useful information for
compromising the server. If it had succeeded, the attacker would have received
the detailed name of the operating system (&lt;tt class="docutils literal"&gt;uname &lt;span class="pre"&gt;-a&lt;/span&gt;&lt;/tt&gt;) and user and group
information for the user running the web server (&lt;tt class="docutils literal"&gt;id&lt;/tt&gt;).&lt;/p&gt;
&lt;p&gt;The part that I was unsure about is the &lt;tt class="docutils literal"&gt;expr 299663299665&lt;/tt&gt; command, which is
apparently related to the &lt;a class="reference external" href="https://www.troyhunt.com/everything-you-need-to-know-about2/"&gt;Shellshock&lt;/a&gt; vulnerability. The command itself and
the result, &lt;tt class="docutils literal"&gt;99887766555&lt;/tt&gt;, turn up a number of results on the web but nothing
that seems to connect the dots on between the command and the vulnerability.&lt;/p&gt;
&lt;p&gt;I suspect that this is an automated worm infection attempt meant to compromise
the host being DDoS'd and increase the power of the botnet.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="section" id="oom-killer"&gt;
&lt;h2&gt;OOM Killer&lt;/h2&gt;
&lt;p&gt;Ultimately, the DDoS worked like a charm. It only took about 20 minutes to knock
out the database server, completely crippling the Drupal-based website. The
burst of 4,000+ requests in one minute caused enough hits against the database
to trigger the dreaded &lt;a class="reference external" href="https://lwn.net/Articles/317814/"&gt;oom killer&lt;/a&gt;, shutting the service down for good
(until I brought it back up manually, anyway).&lt;/p&gt;
&lt;p&gt;I have run in to oom killer in other low memory VPS situations in the past and
it certainly is not a fun issue to iron out. Luckily, this instance was entirely
related to the DDoS attack and not some deeper, more obscure memory issue. Once
the attack stopped, things were able to return to normal pretty easily.&lt;/p&gt;
&lt;p&gt;In the end, I examined some other logs and decided to restore the machine from a
previous image just in case. If the site becomes a frequent target of this sort
of attack I will evaluate mitigation options, but I suspect that this particular
attack was simply a random test of a small botnet.&lt;/p&gt;
&lt;/div&gt;
</content><category term="Technology"></category><category term="apache"></category><category term="botnets"></category><category term="ddos"></category><category term="drupal"></category><category term="ip"></category><category term="logs"></category><category term="sqlite"></category></entry><entry><title>Loading Plotly Graphs on Demand with Waypoints</title><link href="https://www.chris-wells.net/articles/2016/11/23/plotly-waypoints" rel="alternate"></link><published>2016-11-23T15:42:00-05:00</published><updated>2016-11-23T15:42:00-05:00</updated><author><name>Christopher Charbonneau Wells</name></author><id>tag:www.chris-wells.net,2016-11-23:/articles/2016/11/23/plotly-waypoints</id><summary type="html">
&lt;p&gt;In my last post, &lt;a class="reference external" href="https://www.chris-wells.net/articles/2016/11/18/12-years-gmail-4-chat"&gt;12 Years of Gmail, Part 4: Chat&lt;/a&gt;, I included eight Plotly
graphs on a single page. All the graphs worked correctly, but the page was
taking almost four seconds to render any content at all and up to 6-8 seconds to
load completely without cached elements. By contrast, the landing page of
chrxs.net takes less than a second to load with visual content rendering
almost immediately. The site is intentionally designed to be light weight and
uses very few resources on a standard load. But Plotly graphs require a big
(1MB+ uncompressed) JavaScript file in order to load with all the bells and
whistles. What can be done to improve this slow load time, particularly when
many graphs are on a single page?&lt;/p&gt;
</summary><content type="html">&lt;span class="target" id="top"&gt;&lt;/span&gt;
&lt;p&gt;In my last post, &lt;a class="reference external" href="https://www.chris-wells.net/articles/2016/11/18/12-years-gmail-4-chat"&gt;12 Years of Gmail, Part 4: Chat&lt;/a&gt;, I included eight Plotly
graphs on a single page. All the graphs worked correctly, but the page was
taking almost four seconds to render any content at all and up to 6-8 seconds to
load completely without cached elements. By contrast, the landing page of
chrxs.net takes less than a second to load with visual content rendering
almost immediately. The site is intentionally designed to be light weight and
uses very few resources on a standard load. But Plotly graphs require a big
(1MB+ uncompressed) JavaScript file in order to load with all the bells and
whistles. What can be done to improve this slow load time, particularly when
many graphs are on a single page?&lt;/p&gt;

&lt;p class="image-box center"&gt;&lt;a class="reference external image-reference" href="/static/images/2016/wbtorg_strip_before.png"&gt;&lt;img alt="film strip before" src="/static/images/2016/wbtorg_strip_before.png" /&gt;&lt;/a&gt; Film strip &lt;strong&gt;before&lt;/strong&gt; optimization (&lt;a class="reference external" href="http://www.webpagetest.org/"&gt;webpagetest.org&lt;/a&gt;)&lt;/p&gt;
&lt;p&gt;The page load film strip above shows almost three whole seconds before any
content is rendered. The obvious first step was to move the loading of Plotly's
large JavaScript file from the page &lt;tt class="docutils literal"&gt;head&lt;/tt&gt; (which loads before content is
rendered) to the end of the page &lt;tt class="docutils literal"&gt;body&lt;/tt&gt;, theoretically allowing the page's
content to be partially loaded and rendered earlier. However, doing this created
a bit of problem because the Plotly graphs were loaded in the body, like so:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;table class="highlighttable"&gt;&lt;tr&gt;&lt;td class="linenos"&gt;&lt;div class="linenodiv"&gt;&lt;pre&gt;&lt;span class="normal"&gt; 0&lt;/span&gt;
&lt;span class="normal"&gt; 1&lt;/span&gt;
&lt;span class="normal"&gt; 2&lt;/span&gt;
&lt;span class="normal"&gt; 3&lt;/span&gt;
&lt;span class="normal"&gt; 4&lt;/span&gt;
&lt;span class="normal"&gt; 5&lt;/span&gt;
&lt;span class="normal"&gt; 6&lt;/span&gt;
&lt;span class="normal"&gt; 7&lt;/span&gt;
&lt;span class="normal"&gt; 8&lt;/span&gt;
&lt;span class="normal"&gt; 9&lt;/span&gt;
&lt;span class="normal"&gt;10&lt;/span&gt;
&lt;span class="normal"&gt;11&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;div&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt; &lt;span class="na"&gt;class&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;overflow-x-container&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;62c34418-006c-4aaf-8df7-15d5ad556369&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;script&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;text/javascript&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;PLOTLYENV&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;PLOTLYENV&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;||&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{};&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;PLOTLYENV&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;BASE_URL&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;https://plot.ly&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nx"&gt;Plotly&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;newPlot&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;62c34418-006c-4aaf-8df7-15d5ad556369&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="p"&gt;[...],&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="p"&gt;{...}&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;script&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/div&gt;
&lt;p&gt;This means the &lt;tt class="docutils literal"&gt;Plotly&lt;/tt&gt; command is needed in the body, so Plotly's JavaScript
must be loaded before any of the graphs. There are a number of ways to solve
this, but the most appealing one I came across is the &lt;a class="reference external" href="http://imakewebthings.com/waypoints/"&gt;Waypoints&lt;/a&gt; library.
Basically, Waypoints provides a framework for executing actions as a user
reaches a certain point on the page. This is a common practice for website with
lots of images and/or &amp;quot;endless scrolling&amp;quot; features. To make use of it in this
situation, I simply removed the JavaScript from the code excerpt above and added
the following &lt;em&gt;below&lt;/em&gt; the Plotly JavaScript load:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;table class="highlighttable"&gt;&lt;tr&gt;&lt;td class="linenos"&gt;&lt;div class="linenodiv"&gt;&lt;pre&gt;&lt;span class="normal"&gt; 0&lt;/span&gt;
&lt;span class="normal"&gt; 1&lt;/span&gt;
&lt;span class="normal"&gt; 2&lt;/span&gt;
&lt;span class="normal"&gt; 3&lt;/span&gt;
&lt;span class="normal"&gt; 4&lt;/span&gt;
&lt;span class="normal"&gt; 5&lt;/span&gt;
&lt;span class="normal"&gt; 6&lt;/span&gt;
&lt;span class="normal"&gt; 7&lt;/span&gt;
&lt;span class="normal"&gt; 8&lt;/span&gt;
&lt;span class="normal"&gt; 9&lt;/span&gt;
&lt;span class="normal"&gt;10&lt;/span&gt;
&lt;span class="normal"&gt;11&lt;/span&gt;
&lt;span class="normal"&gt;12&lt;/span&gt;
&lt;span class="normal"&gt;13&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;div&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="ow"&gt;new&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Waypoint&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nx"&gt;element&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;getElementById&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;62c34418-006c-4aaf-8df7-15d5ad556369&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nx"&gt;handler&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;function&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;PLOTLYENV&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;PLOTLYENV&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;||&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{};&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;PLOTLYENV&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;BASE_URL&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;https://plot.ly&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;Plotly&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;newPlot&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;62c34418-006c-4aaf-8df7-15d5ad556369&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="p"&gt;[...],&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="p"&gt;{...}&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;destroy&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nx"&gt;offset&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;100%&amp;#39;&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/div&gt;
&lt;p&gt;&lt;strong&gt;Line 1&lt;/strong&gt; identifies the element ID that will trigger the &lt;tt class="docutils literal"&gt;handler&lt;/tt&gt; action in
&lt;strong&gt;Line 2&lt;/strong&gt;. This ID is the container for the graph that the handler will load.
The only addition is &lt;strong&gt;Line 10&lt;/strong&gt;, which destroys the waypoint after it runs once
in order to prevent the Plotly graph from loading itself over and over again.
This causes the graph to only load a once when the user scrolls to the point on
the page that should display the graph.&lt;/p&gt;
&lt;p&gt;After I replaced all eight graphs with Waypoints, the page load performance
improved massively:&lt;/p&gt;
&lt;p class="image-box center"&gt;&lt;a class="reference external image-reference" href="/static/images/2016/wbtorg_strip_after.png"&gt;&lt;img alt="film strip after" src="/static/images/2016/wbtorg_strip_after.png" /&gt;&lt;/a&gt; Film strip &lt;strong&gt;after&lt;/strong&gt; optimization (&lt;a class="reference external" href="http://www.webpagetest.org/"&gt;webpagetest.org&lt;/a&gt;)&lt;/p&gt;
&lt;p&gt;With these changes the page takes less than one second to render the first bit
of content - this can be applied to any number of Plotly graphs on a single page
and brings the page load time much more in line with the rest of the website
(which does not include the Plotly Javascript at all).&lt;/p&gt;
</content><category term="Technology"></category><category term="graphing"></category><category term="javascript"></category><category term="plotly"></category><category term="waypoints"></category></entry><entry><title>12 Years of Gmail, Part 4: Chat</title><link href="https://www.chris-wells.net/articles/2016/11/18/12-years-gmail-4-chat" rel="alternate"></link><published>2016-11-18T20:00:00-05:00</published><updated>2016-11-20T08:40:00-05:00</updated><author><name>Christopher Charbonneau Wells</name></author><id>tag:www.chris-wells.net,2016-11-18:/articles/2016/11/18/12-years-gmail-4-chat</id><summary type="html">
&lt;p&gt;With the &lt;a class="reference external" href="https://www.chris-wells.net/articles/2016/11/12/12-years-gmail-3-finishing-touches"&gt;Finishing Touches&lt;/a&gt; in place, it's finally time to start looking at
some of the data in my &lt;a class="reference external" href="https://www.chris-wells.net/articles/2016/10/28/12-years-gmail-1-google-takeout"&gt;Google Takeout&lt;/a&gt; Mail export file. What better to start
with than the &lt;a class="reference external" href="https://en.wikipedia.org/wiki/Google_Talk"&gt;Google &lt;em&gt;Talk&lt;/em&gt;&lt;/a&gt; (or Google &lt;strong&gt;Chat&lt;/strong&gt;, as I will refer to it) content
stored within!&lt;/p&gt;
&lt;p&gt;I am starting with Chat because I was surprised to find it all stored in the
export file. It makes sense as chat history is accessible from the &lt;strong&gt;Chats&lt;/strong&gt;
link in the old Gmail interface (I couldn't find an equivalent in Inbox). My
surprise led to curiosity and my curiosity led to obsession with trying to
figure how the Chat data is stored and what information each messages contains.
It turns out there are quite a few things that can be gleaned from these
chat messages -&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;a class="reference external" href="https://www.chris-wells.net/articles/2016/11/18/12-years-gmail-4-chat#vsemail"&gt;Chat vs. Email Usage&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="reference external" href="https://www.chris-wells.net/articles/2016/11/18/12-years-gmail-4-chat#vsemailcumu"&gt;Chat vs. Email Usage (Cumulative)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="reference external" href="https://www.chris-wells.net/articles/2016/11/18/12-years-gmail-4-chat#top10"&gt;Top 10 Chatters&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="reference external" href="https://www.chris-wells.net/articles/2016/11/18/12-years-gmail-4-chat#clients"&gt;Chat Clients&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="reference external" href="https://www.chris-wells.net/articles/2016/11/18/12-years-gmail-4-chat#times"&gt;Chat Times&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="reference external" href="https://www.chris-wells.net/articles/2016/11/18/12-years-gmail-4-chat#days"&gt;Chat (vs. Email) Days&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="reference external" href="https://www.chris-wells.net/articles/2016/11/18/12-years-gmail-4-chat#durations"&gt;Chat Durations&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="reference external" href="https://www.chris-wells.net/articles/2016/11/18/12-years-gmail-4-chat#sizes"&gt;Chat Thread Sizes&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</summary><content type="html">&lt;blockquote class="small"&gt;
    &lt;em&gt;
        This post is part of my series, &lt;a href="/tag/12-years-of-gmail"&gt;12 Years of Gmail&lt;/a&gt;,
        taking a look at the data Google has accumulated on me over the past
        12 years of using various Google services and documenting the
        learning experience developing an open source Python project
        (&lt;a href="https://github.com/cdubz/takeout-inspector"&gt;Takeout Inspector&lt;/a&gt;)
        to analyze that data.
    &lt;/em&gt;
&lt;/blockquote&gt;
&lt;p&gt;With the &lt;a class="reference external" href="https://www.chris-wells.net/articles/2016/11/12/12-years-gmail-3-finishing-touches"&gt;Finishing Touches&lt;/a&gt; in place, it's finally time to start looking at
some of the data in my &lt;a class="reference external" href="https://www.chris-wells.net/articles/2016/10/28/12-years-gmail-1-google-takeout"&gt;Google Takeout&lt;/a&gt; Mail export file. What better to start
with than the &lt;a class="reference external" href="https://en.wikipedia.org/wiki/Google_Talk"&gt;Google &lt;em&gt;Talk&lt;/em&gt;&lt;/a&gt; (or Google &lt;strong&gt;Chat&lt;/strong&gt;, as I will refer to it) content
stored within!&lt;/p&gt;
&lt;p&gt;I am starting with Chat because I was surprised to find it all stored in the
export file. It makes sense as chat history is accessible from the &lt;strong&gt;Chats&lt;/strong&gt;
link in the old Gmail interface (I couldn't find an equivalent in Inbox). My
surprise led to curiosity and my curiosity led to obsession with trying to
figure how the Chat data is stored and what information each messages contains.
It turns out there are quite a few things that can be gleaned from these
chat messages -&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;a class="reference external" href="https://www.chris-wells.net/articles/2016/11/18/12-years-gmail-4-chat#vsemail"&gt;Chat vs. Email Usage&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="reference external" href="https://www.chris-wells.net/articles/2016/11/18/12-years-gmail-4-chat#vsemailcumu"&gt;Chat vs. Email Usage (Cumulative)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="reference external" href="https://www.chris-wells.net/articles/2016/11/18/12-years-gmail-4-chat#top10"&gt;Top 10 Chatters&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="reference external" href="https://www.chris-wells.net/articles/2016/11/18/12-years-gmail-4-chat#clients"&gt;Chat Clients&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="reference external" href="https://www.chris-wells.net/articles/2016/11/18/12-years-gmail-4-chat#times"&gt;Chat Times&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="reference external" href="https://www.chris-wells.net/articles/2016/11/18/12-years-gmail-4-chat#days"&gt;Chat (vs. Email) Days&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="reference external" href="https://www.chris-wells.net/articles/2016/11/18/12-years-gmail-4-chat#durations"&gt;Chat Durations&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="reference external" href="https://www.chris-wells.net/articles/2016/11/18/12-years-gmail-4-chat#sizes"&gt;Chat Thread Sizes&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="section" id="chat-vs-email-usage-1"&gt;
&lt;span id="vsemail"&gt;&lt;/span&gt;&lt;h2&gt;Chat vs. Email Usage&lt;/h2&gt;
&lt;div class="overflow-x-container"&gt;
    &lt;div id="62c34418-006c-4aaf-8df7-15d5ad556369" style="height: 100%; width: 100%;" class="plotly-graph-div"&gt;
        &lt;img src="/static/images/2016/12yog/chat-vs-email.png" alt="Chat vs. Email Usage" class="no-js-remove" /&gt;
    &lt;/div&gt;
&lt;/div&gt;&lt;p&gt;This is the first graph I hacked away at and luckily it provides a neat little
anomaly to look at. Undoubtedly the first thing most people will notice looking
at this graph is the big spike in Chat activity that occurs in 2012. Zooming in
on this (&lt;em&gt;click and drag in the graph&lt;/em&gt;) reveals that the spike occurred in
October 2012. One other event that may be noticeable (if much less so) is the
dip in activity around May to August 2009. What is the relevance of these dates?
Two very important events in my life:&lt;/p&gt;
&lt;ol class="arabic simple"&gt;
&lt;li&gt;On &lt;strong&gt;12 June 2009&lt;/strong&gt;, my plane touched down in &lt;a class="reference external" href="https://www.cia.gov/library/publications/the-world-factbook/geos/uv.html"&gt;Burkina Faso&lt;/a&gt; and I began my
initial two years of service in &lt;a class="reference external" href="https://www.peacecorps.gov/burkina-faso/"&gt;Peace Corps Burkina Faso&lt;/a&gt;. Internet service
in Burkina was rare and unreliable. Most of the time we had to use the
&lt;a class="reference external" href="https://mail.google.com/mail/u/0/h/1pq68r75kzvdr/?v%3Dlui"&gt;Basic HTML version of Gmail&lt;/a&gt; without Chat support.&lt;ul&gt;
&lt;li&gt;The big dip in &lt;strong&gt;August 2010&lt;/strong&gt; was the result of a prolonged outage of the
Internet service in the town I lived in at the time (&lt;a class="reference external" href="http://www.tripmondo.com/burkina-faso/nord/yako/"&gt;Yako&lt;/a&gt;).&lt;/li&gt;
&lt;li&gt;The jump in activity in &lt;strong&gt;September &amp;amp; October 2011&lt;/strong&gt; represents my one
trip back home before returning to Burkina for a third year.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;On &lt;strong&gt;10 October 2012&lt;/strong&gt;, after returning from a little over three years of
service in Burkina, I moved to Washington, D.C. to start a new job. The
&lt;em&gt;huge&lt;/em&gt; jump in Chat activity is a result of the fact that I was separated
from my future wife before she was able to move to D.C. in late October.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;According to Wikipedia, &lt;a class="reference external" href="https://en.wikipedia.org/wiki/Google_Talk"&gt;Google Talk&lt;/a&gt; was first introduced in August 2005.
Apparently I did not start using it until February 2006. But it certainly took
over my inbox...&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="chat-vs-email-usage-cumulative-1"&gt;
&lt;span id="vsemailcumu"&gt;&lt;/span&gt;&lt;h2&gt;Chat vs. Email Usage (Cumulative)&lt;/h2&gt;
&lt;div class="overflow-x-container"&gt;
    &lt;div id="d133946b-5a12-4619-a973-f6f96efd974c" style="height: 100%; width: 100%;" class="plotly-graph-div"&gt;
        &lt;img src="/static/images/2016/12yog/chat-vs-email-cumu.png" alt="Chat vs. Email Usage (Cumulative)" class="no-js-remove" /&gt;
    &lt;/div&gt;
&lt;/div&gt;&lt;p&gt;In a mere three months (by May 2006), the number of Chat messages overtook more
than three years of email messages stored in my Gmail account. From there Chat
continued to grow at a much faster pace than email (outside of the relative flat
line while I was in Burkina). This is not particularly surprising given the
nature of instant messaging and email, but the volume of Chat messages over
emails is much, much higher than I thought it would be.&lt;/p&gt;
&lt;p&gt;I only have one small aside regarding the code here - I wanted to build these
graphs using a single query and sqlite made that simple with the &lt;a class="reference external" href="https://sqlite.org/lang_datefunc.html"&gt;strftime&lt;/a&gt;
function and &lt;a class="reference external" href="https://www.sqlite.org/lang_expr.html#between"&gt;CASE expression&lt;/a&gt; (which for some reason does not have an anchor
in the sqlite documentation). This query is what is used for both graphs:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;table class="highlighttable"&gt;&lt;tr&gt;&lt;td class="linenos"&gt;&lt;div class="linenodiv"&gt;&lt;pre&gt;&lt;span class="normal"&gt;0&lt;/span&gt;
&lt;span class="normal"&gt;1&lt;/span&gt;
&lt;span class="normal"&gt;2&lt;/span&gt;
&lt;span class="normal"&gt;3&lt;/span&gt;
&lt;span class="normal"&gt;4&lt;/span&gt;
&lt;span class="normal"&gt;5&lt;/span&gt;
&lt;span class="normal"&gt;6&lt;/span&gt;
&lt;span class="normal"&gt;7&lt;/span&gt;
&lt;span class="normal"&gt;8&lt;/span&gt;
&lt;span class="normal"&gt;9&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;div&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;strftime&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;%Y-%m&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;`&lt;/span&gt;&lt;span class="nb"&gt;date&lt;/span&gt;&lt;span class="o"&gt;`&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;as&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;period&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="k"&gt;COUNT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;CASE&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;WHEN&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;gmail_labels&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;LIKE&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;%Chat%&amp;#39;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;THEN&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;ELSE&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;END&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;AS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;chat_messages&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="k"&gt;COUNT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;CASE&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;WHEN&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;gmail_labels&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;NOT&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;LIKE&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;%Chat%&amp;#39;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;THEN&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;ELSE&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;END&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;AS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;email_messages&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;messages&lt;/span&gt;
&lt;span class="k"&gt;GROUP&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;BY&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;period&lt;/span&gt;
&lt;span class="k"&gt;ORDER&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;BY&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;period&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;ASC&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;div class="section" id="top-10-chatters-1"&gt;
&lt;span id="top10"&gt;&lt;/span&gt;&lt;h2&gt;Top 10 Chatters&lt;/h2&gt;
&lt;div class="overflow-x-container"&gt;
    &lt;div id="1b1f151e-35cd-4567-ba8a-60c5a7900382" style="height: 735px; width: 100%;" class="plotly-graph-div"&gt;
        &lt;img src="/static/images/2016/12yog/top-10-chatters.png" alt="Top 10 Chatters" class="no-js-remove" /&gt;
    &lt;/div&gt;
&lt;/div&gt;&lt;p&gt;This graph did not turn out particularly interesting for me given the
exponential difference between Chat and email messages. It does clearly
illustrate that I chatted with my wife (Tony Taylor) &lt;em&gt;a lot&lt;/em&gt;. The roughly 18k
Chat messages is about 20% of the total Chat messages in my mail file!&lt;/p&gt;
&lt;p&gt;Sizing was a bit of a challenge with this graph. As with my &lt;a class="reference external" href="https://www.chris-wells.net/articles/2016/11/12/12-years-gmail-3-finishing-touches#plotly"&gt;previous efforts&lt;/a&gt;,
the code determines the longest address (by character count) in the result set
and attempts to adjust the graph to compensate, like so:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;table class="highlighttable"&gt;&lt;tr&gt;&lt;td class="linenos"&gt;&lt;div class="linenodiv"&gt;&lt;pre&gt;&lt;span class="normal"&gt;0&lt;/span&gt;
&lt;span class="normal"&gt;1&lt;/span&gt;
&lt;span class="normal"&gt;2&lt;/span&gt;
&lt;span class="normal"&gt;3&lt;/span&gt;
&lt;span class="normal"&gt;4&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;div&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="n"&gt;layout&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_default_layout_options&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="n"&gt;layout&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;barmode&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;grouped&amp;#39;&lt;/span&gt;
&lt;span class="n"&gt;layout&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;height&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;longest_address&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;15&lt;/span&gt;
&lt;span class="n"&gt;layout&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;margin&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;b&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;longest_address&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;getfloat&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;font&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;size&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;
&lt;span class="n"&gt;layout&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;margin&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;go&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Margin&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;layout&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;margin&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/div&gt;
&lt;p&gt;Once again, I guess-and-checked my way to some seemingly random numbers that
appear to have the desired effect regardless of font size. &lt;strong&gt;Line 2&lt;/strong&gt; sets the
overall height for the graph and &lt;strong&gt;Line 3&lt;/strong&gt; adds a bottom margin to
(theoretically) fit all the addresses. Previously I did not change the overall
size of the graph and this can still result in squished graphs on smaller
screens.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="chat-clients-1"&gt;
&lt;span id="clients"&gt;&lt;/span&gt;&lt;h2&gt;Chat Clients&lt;/h2&gt;
&lt;div class="overflow-x-container"&gt;
    &lt;div id="6f7b45e9-ee9e-47d8-b2aa-b5575b421bbf" style="height: 100%; width: 100%;" class="plotly-graph-div"&gt;
        &lt;img src="/static/images/2016/12yog/chat-clients.png" alt="Chat Clients" class="no-js-remove" /&gt;
    &lt;/div&gt;
&lt;/div&gt;&lt;p&gt;This was a fun one to do because it is a sort of non-obvious way to look at the
metadata in each Chat message. I learned about the Resourcepart used by XMPP
while I was &lt;a class="reference external" href="https://www.chris-wells.net/articles/2016/11/08/12-years-gmail-2-bootstrapping#parsing"&gt;sorting out how to parse email addresses&lt;/a&gt;. At the time my only
goal was to get rid of it in order to clean up the email addresses. The data
that ultimately ends up in sqlite does not include the Resourcepart &lt;em&gt;except&lt;/em&gt; in
the &lt;tt class="docutils literal"&gt;headers&lt;/tt&gt; table, which holds row-by-row records of every raw header from
all messages.&lt;/p&gt;
&lt;p&gt;Because the standard is apparently not well followed by instant messaging
clients, the aggregate information here is really just a best guess. I knew of
most of the clients, but also found some others through an old blog post from
Google, &lt;a class="reference external" href="http://googletalk.blogspot.com/2006/03/third-party-client-use-of-google-talk.html"&gt;Third Party Client Use of the Google Talk Service&lt;/a&gt;, covering their
own investigation of third party clients using the (relatively newly launched)
service. I'm sure some are still missing.&lt;/p&gt;
&lt;p&gt;As for my own chart, I was a big fan of &lt;a class="reference external" href="https://adium.im/"&gt;Adium&lt;/a&gt; back when I used instant
messaging much more frequently - that's about the only revelation here.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="chat-times-1"&gt;
&lt;span id="times"&gt;&lt;/span&gt;&lt;h2&gt;Chat Times&lt;/h2&gt;
&lt;div class="overflow-x-container"&gt;
    &lt;div id="9db3cb64-e39f-49b2-97a0-7ca2a2dfc382" style="height: 100%; width: 100%;" class="plotly-graph-div"&gt;
        &lt;img src="/static/images/2016/12yog/chat-times.png" alt="Chat Times" class="no-js-remove" /&gt;
    &lt;/div&gt;
&lt;/div&gt;&lt;p&gt;This is a simple view of the hours of the day when I do the most chatting. The
graph times are UTC and for the majority of the messages I was likely in the
Central or Eastern timezone (UTC−6) or (UTC-5) so it appears that I did most of
my chatting in the morning hours.&lt;/p&gt;
&lt;p&gt;The one thing I remember about working on this graph is this long line of code
for calculating the percentages:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;table class="highlighttable"&gt;&lt;tr&gt;&lt;td class="linenos"&gt;&lt;div class="linenodiv"&gt;&lt;pre&gt;&lt;span class="normal"&gt;0&lt;/span&gt;
&lt;span class="normal"&gt;1&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;div&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;hour&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;keys&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="n"&gt;percentages&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;hour&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;round&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;float&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;hour&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nb"&gt;float&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;total_messages&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;%&amp;#39;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/div&gt;
&lt;p&gt;I suspect there is a more elegant way to arrive at the same result so I will
have to revisit that graph method in the future. Having to deal with types when
combining variables is not an issue in PHP and it is taking me a while to wrap
my brain around this in Python. I encounter lots of &lt;tt class="docutils literal"&gt;TypeError&lt;/tt&gt; exceptions!&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;table class="highlighttable"&gt;&lt;tr&gt;&lt;td class="linenos"&gt;&lt;div class="linenodiv"&gt;&lt;pre&gt;&lt;span class="normal"&gt;0&lt;/span&gt;
&lt;span class="normal"&gt;1&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;div&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="nb"&gt;print&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;%&amp;#39;&lt;/span&gt;
&lt;span class="ne"&gt;TypeError&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;unsupported&lt;/span&gt; &lt;span class="n"&gt;operand&lt;/span&gt; &lt;span class="nb"&gt;type&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;int&amp;#39;&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;str&amp;#39;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;div class="section" id="chat-days"&gt;
&lt;span id="days"&gt;&lt;/span&gt;&lt;h2&gt;Chat Days&lt;/h2&gt;
&lt;div class="overflow-x-container"&gt;
    &lt;div id="6266c51a-080c-4d46-99d2-dafc4d193b31" style="height: 100%; width: 100%;" class="plotly-graph-div"&gt;
        &lt;img src="/static/images/2016/12yog/chat-days.png" alt="Chat Days" class="no-js-remove" /&gt;
    &lt;/div&gt;
&lt;/div&gt;&lt;p&gt;This graph turned out just about as I expected it - I tended to use Chat as a
means of communication with friends while at work during the weekdays (so this
comports with the Chat Times graph above (and perhaps there is some combined
view to consider here...)). Chat conversation dominated about 80% of my
communications during the week, but dropped closer to 70% on the weekends as I
would generally have more time to compose emails. I suspect that my earlier
habits also influence this relationship - nowadays I am more inclined to spend
weekends doing other things.&lt;/p&gt;
&lt;p&gt;This is another place where I feel there could be a lot done to clean up the
code:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;table class="highlighttable"&gt;&lt;tr&gt;&lt;td class="linenos"&gt;&lt;div class="linenodiv"&gt;&lt;pre&gt;&lt;span class="normal"&gt;0&lt;/span&gt;
&lt;span class="normal"&gt;1&lt;/span&gt;
&lt;span class="normal"&gt;2&lt;/span&gt;
&lt;span class="normal"&gt;3&lt;/span&gt;
&lt;span class="normal"&gt;4&lt;/span&gt;
&lt;span class="normal"&gt;5&lt;/span&gt;
&lt;span class="normal"&gt;6&lt;/span&gt;
&lt;span class="normal"&gt;7&lt;/span&gt;
&lt;span class="normal"&gt;8&lt;/span&gt;
&lt;span class="normal"&gt;9&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;div&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="n"&gt;chat_percentages&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;OrderedDict&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="n"&gt;chat_messages&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;OrderedDict&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="n"&gt;email_percentages&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;OrderedDict&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="n"&gt;email_messages&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;OrderedDict&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;row&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;fetchall&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="n"&gt;dow&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;calendar&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;day_name&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;row&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="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;  &lt;span class="c1"&gt;# sqlite strftime() uses 0 = SUNDAY.&lt;/span&gt;
    &lt;span class="n"&gt;chat_percentages&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;dow&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;round&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;float&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;row&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="nb"&gt;sum&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="n"&gt;row&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;row&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;]])&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;%&amp;#39;&lt;/span&gt;
    &lt;span class="n"&gt;email_percentages&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;dow&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;round&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;float&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;row&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="nb"&gt;sum&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="n"&gt;row&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;row&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;]])&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;%&amp;#39;&lt;/span&gt;
    &lt;span class="n"&gt;chat_messages&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;dow&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;row&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;email_messages&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;dow&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;row&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/div&gt;
&lt;p&gt;This seems like a lot of work to make pretty simple calculations. I would also
like to cut down on the number of dictionaries, if only to make the code read a
little bit easier. Currently the up-side is that this setup makes it dead simple
to craft the graph building statements.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="chat-durations-1"&gt;
&lt;span id="durations"&gt;&lt;/span&gt;&lt;h2&gt;Chat Durations&lt;/h2&gt;
&lt;div class="overflow-x-container"&gt;
    &lt;div id="41cb4106-1c48-43ff-91b9-0001d042313f" style="height: 100%; width: 100%;" class="plotly-graph-div"&gt;
        &lt;img src="/static/images/2016/12yog/chat-durations.png" alt="Chat Durations" class="no-js-remove" /&gt;
    &lt;/div&gt;
&lt;/div&gt;&lt;p&gt;This was a particularly interesting graph to work out due to the way that Chat
messages are stored in the Mail file. An example message entry for Google Chat
content looks like this:&lt;/p&gt;
&lt;pre class="literal-block"&gt;
From 1234567890123456789&amp;#64;xxx Sun Oct 04 11:20:57 +0000 2015
X-GM-THRID: 1234567890123456789
X-Gmail-Labels: Chat
MIME-Version: 1.0
From: Bob Thompson &amp;lt;bob-thompson&amp;#64;domain2.tld&amp;gt;
Content-Type: text/html

Hello!
&lt;/pre&gt;
&lt;p&gt;Notice anything missing there? There is no &lt;strong&gt;To&lt;/strong&gt; or &lt;strong&gt;Date&lt;/strong&gt; header! Luckily,
the date is available on the &lt;tt class="docutils literal"&gt;From&lt;/tt&gt; line and its position and format is
consistent for all messages - it is always the last 30 characters in the string
so &lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;string[-30:]&lt;/span&gt;&lt;/tt&gt; captures the date and time easily. But how can this be used
to figure our chat durations? In combination with the &lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;X-GM-THRID&lt;/span&gt;&lt;/tt&gt; header!&lt;/p&gt;
&lt;p&gt;The Thread ID connects multiple messages together in to threads, which helps to
string together messages and find things like durations and participants
(remember that missing &lt;strong&gt;To&lt;/strong&gt; header?). &lt;a class="reference external" href="https://github.com/cdubz/takeout-inspector"&gt;Takeout Inspector&lt;/a&gt; handles these dates
during the import process and also brings in each message's Thread ID in the
&lt;tt class="docutils literal"&gt;messages&lt;/tt&gt; table. With these data points, the following query will produce
chat durations:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;table class="highlighttable"&gt;&lt;tr&gt;&lt;td class="linenos"&gt;&lt;div class="linenodiv"&gt;&lt;pre&gt;&lt;span class="normal"&gt;0&lt;/span&gt;
&lt;span class="normal"&gt;1&lt;/span&gt;
&lt;span class="normal"&gt;2&lt;/span&gt;
&lt;span class="normal"&gt;3&lt;/span&gt;
&lt;span class="normal"&gt;4&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;div&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="n"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;strftime&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;&lt;/span&gt;&lt;span class="si"&gt;%s&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;MAX&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="err"&gt;`&lt;/span&gt;&lt;span class="n"&gt;date&lt;/span&gt;&lt;span class="err"&gt;`&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;strftime&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;&lt;/span&gt;&lt;span class="si"&gt;%s&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;MIN&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="err"&gt;`&lt;/span&gt;&lt;span class="n"&gt;date&lt;/span&gt;&lt;span class="err"&gt;`&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="n"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;duration&lt;/span&gt;
&lt;span class="n"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;messages&lt;/span&gt;
&lt;span class="n"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;gmail_labels&lt;/span&gt; &lt;span class="n"&gt;LIKE&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;%Chat%&amp;#39;&lt;/span&gt;
&lt;span class="n"&gt;GROUP&lt;/span&gt; &lt;span class="n"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;gmail_thread_id&lt;/span&gt;
&lt;span class="n"&gt;HAVING&lt;/span&gt; &lt;span class="n"&gt;duration&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;/pre&gt;&lt;/div&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/div&gt;
&lt;p&gt;Pretty simple really. Unfortunately, some messages have the exact same time for
the entire thread so these end up with a duration of zero. I have not been able
to find any common factor that indicates why this happens. Single message
threads also come up zero and originally these results were included and
categorized as &amp;quot;Unknown&amp;quot;. In my case this accounted for almost 20% of all my
Chat messages so I elected to add &lt;strong&gt;Line 4&lt;/strong&gt; above to exclude those results from
the set (see &lt;a class="reference external" href="https://github.com/cdubz/takeout-inspector/commit/4739987fd34b3dad91ce3ab50e99f37ab64c08b0"&gt;commit #4739987&lt;/a&gt;).&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="chat-thread-sizes-1"&gt;
&lt;span id="sizes"&gt;&lt;/span&gt;&lt;h2&gt;Chat Thread Sizes&lt;/h2&gt;
&lt;div class="overflow-x-container"&gt;
    &lt;div id="b5605a94-e01f-4c98-ad79-e779b8cf7f5e" style="height: 800px; width: 100%;" class="plotly-graph-div"&gt;
        &lt;img src="/static/images/2016/12yog/chat-thread-sizes.png" alt="Chat Thread Sizes" class="no-js-remove" /&gt;
    &lt;/div&gt;
&lt;/div&gt;&lt;p&gt;Here again is the very distinct drop in activity lining up with what appears in
the &lt;a class="reference external" href="https://www.chris-wells.net/articles/2016/11/18/12-years-gmail-4-chat#vsemail"&gt;Chat vs. Email Usage&lt;/a&gt; graph above. I don't think this graph is
particularly good (for my mail file) given how clustered up everything is, but
it does do a further job of illustrating that down time and it's at least
somewhat interesting to see the &amp;quot;waterfall&amp;quot; effect from larger to smaller
threads.&lt;/p&gt;
&lt;p&gt;This graph is largely just an attempt to get away from the typical set of line,
bar, pie etc graphs. The size of each circle represents the number of messages
in the Chat thread (bigger circle == more messages). Hover over any individual
circle to also see:&lt;/p&gt;
&lt;ol class="arabic simple"&gt;
&lt;li&gt;Message count&lt;/li&gt;
&lt;li&gt;Thread date&lt;/li&gt;
&lt;li&gt;Thread participants&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;All of these data points are retrieved with the following query:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;table class="highlighttable"&gt;&lt;tr&gt;&lt;td class="linenos"&gt;&lt;div class="linenodiv"&gt;&lt;pre&gt;&lt;span class="normal"&gt;0&lt;/span&gt;
&lt;span class="normal"&gt;1&lt;/span&gt;
&lt;span class="normal"&gt;2&lt;/span&gt;
&lt;span class="normal"&gt;3&lt;/span&gt;
&lt;span class="normal"&gt;4&lt;/span&gt;
&lt;span class="normal"&gt;5&lt;/span&gt;
&lt;span class="normal"&gt;6&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;div&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;gmail_thread_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="n"&gt;strftime&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;%Y-%m-%d&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;`&lt;/span&gt;&lt;span class="nb"&gt;date&lt;/span&gt;&lt;span class="o"&gt;`&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;AS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;thread_date&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="k"&gt;COUNT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;message_key&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;as&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;thread_size&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="n"&gt;GROUP_CONCAT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;DISTINCT&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;`&lt;/span&gt;&lt;span class="k"&gt;from&lt;/span&gt;&lt;span class="o"&gt;`&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;AS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;participants&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;messages&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;gmail_labels&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;LIKE&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;%Chat%&amp;#39;&lt;/span&gt;
&lt;span class="k"&gt;GROUP&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;BY&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;gmail_thread_id&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/div&gt;
&lt;p&gt;&lt;strong&gt;Line 3&lt;/strong&gt; is the important bit - it uses the &lt;a class="reference external" href="https://sqlite.org/lang_aggfunc.html#groupconcat"&gt;GROUP_CONCAT()&lt;/a&gt; function to pull
in all the participants that it can find for each thread. Group chats
unfortunately use special addresses that identify the group as a whole. These
&lt;em&gt;is&lt;/em&gt; actually a unique identifer for each participant included in the address,
but there is no way to determine who the participant is beyond the random string
of characters assigned. Currently this convention is ignored so groups just show
up as a single participant in this graph.&lt;/p&gt;
&lt;div class="center section" id="continue-on-to-part-5-mail"&gt;
&lt;h3&gt;&lt;a class="reference external" href="https://www.chris-wells.net/articles/2016/12/05/12-years-gmail-5-mail"&gt;Continue on to Part 5: Mail...&lt;/a&gt;&lt;/h3&gt;
&lt;/div&gt;
&lt;/div&gt;
</content><category term="Technology"></category><category term="12 years of gmail"></category><category term="chat"></category><category term="graphing"></category><category term="plotly"></category><category term="python"></category><category term="takeout inspector"></category></entry><entry><title>12 Years of Gmail, Part 3: Finishing Touches</title><link href="https://www.chris-wells.net/articles/2016/11/12/12-years-gmail-3-finishing-touches" rel="alternate"></link><published>2016-11-12T20:00:00-05:00</published><updated>2016-11-19T09:30:00-05:00</updated><author><name>Christopher Charbonneau Wells</name></author><id>tag:www.chris-wells.net,2016-11-12:/articles/2016/11/12/12-years-gmail-3-finishing-touches</id><summary type="html">
&lt;p&gt;After spending last week &lt;a class="reference external" href="https://www.chris-wells.net/articles/2016/11/08/12-years-gmail-2-bootstrapping"&gt;Bootstrapping&lt;/a&gt; things and, somewhat related, working
my way around &lt;a class="reference external" href="http://blog.getpelican.com/"&gt;Pelican&lt;/a&gt;, today I have tried to tie up loose ends so I can start
spending more time thinking about what information I can get from all this data.
While the package is far from complete, these &amp;quot;finishing touches&amp;quot; ended up being
the three themes of this morning's work -&lt;/p&gt;
&lt;ol class="arabic simple"&gt;
&lt;li&gt;&lt;a class="reference external" href="https://www.chris-wells.net/articles/2016/11/12/12-years-gmail-3-finishing-touches#settings"&gt;Implementing a settings file&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="reference external" href="https://www.chris-wells.net/articles/2016/11/12/12-years-gmail-3-finishing-touches#plotly"&gt;Customising Plotly graphs&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="reference external" href="https://www.chris-wells.net/articles/2016/11/12/12-years-gmail-3-finishing-touches#random"&gt;Generating random names&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;
</summary><content type="html">&lt;blockquote class="small"&gt;
    &lt;em&gt;
        This post is part of my series, &lt;a href="/tag/12-years-of-gmail"&gt;12 Years of Gmail&lt;/a&gt;,
        taking a look at the data Google has accumulated on me over the past
        12 years of using various Google services and documenting the
        learning experience developing an open source Python project
        (&lt;a href="https://github.com/cdubz/takeout-inspector"&gt;Takeout Inspector&lt;/a&gt;)
        to analyze that data.
    &lt;/em&gt;
&lt;/blockquote&gt;
&lt;p&gt;After spending last week &lt;a class="reference external" href="https://www.chris-wells.net/articles/2016/11/08/12-years-gmail-2-bootstrapping"&gt;Bootstrapping&lt;/a&gt; things and, somewhat related, working
my way around &lt;a class="reference external" href="http://blog.getpelican.com/"&gt;Pelican&lt;/a&gt;, today I have tried to tie up loose ends so I can start
spending more time thinking about what information I can get from all this data.
While the package is far from complete, these &amp;quot;finishing touches&amp;quot; ended up being
the three themes of this morning's work -&lt;/p&gt;
&lt;ol class="arabic simple"&gt;
&lt;li&gt;&lt;a class="reference external" href="https://www.chris-wells.net/articles/2016/11/12/12-years-gmail-3-finishing-touches#settings"&gt;Implementing a settings file&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="reference external" href="https://www.chris-wells.net/articles/2016/11/12/12-years-gmail-3-finishing-touches#plotly"&gt;Customising Plotly graphs&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="reference external" href="https://www.chris-wells.net/articles/2016/11/12/12-years-gmail-3-finishing-touches#random"&gt;Generating random names&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="section" id="implementing-a-settings-file-1"&gt;
&lt;span id="settings"&gt;&lt;/span&gt;&lt;h2&gt;Implementing a Settings File&lt;/h2&gt;
&lt;p&gt;While thinking about how to customize graphs (more on that below) and allow for
changes to styles without too much effort, it struck me that there is likely
some common (&lt;a class="reference external" href="http://softwareengineering.stackexchange.com/questions/129276/give-a-more-formal-definition-for-the-term-pythonic-than-pep8"&gt;&amp;quot;Pythonic&amp;quot;&lt;/a&gt;) way to handle settings. And, of course, there is -
it's called &lt;a class="reference external" href="https://docs.python.org/2.7/library/configparser.html"&gt;ConfigParser&lt;/a&gt; and it's extremely handy.&lt;/p&gt;
&lt;p&gt;To get my feet wet, I created a &lt;tt class="docutils literal"&gt;settings.cfg&lt;/tt&gt; file with the following contents:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;table class="highlighttable"&gt;&lt;tr&gt;&lt;td class="linenos"&gt;&lt;div class="linenodiv"&gt;&lt;pre&gt;&lt;span class="normal"&gt;0&lt;/span&gt;
&lt;span class="normal"&gt;1&lt;/span&gt;
&lt;span class="normal"&gt;2&lt;/span&gt;
&lt;span class="normal"&gt;3&lt;/span&gt;
&lt;span class="normal"&gt;4&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;div&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="c1"&gt;;settings.cfg&lt;/span&gt;
&lt;span class="k"&gt;[mail]&lt;/span&gt;
&lt;span class="na"&gt;anonymize&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;False&lt;/span&gt;
&lt;span class="na"&gt;db_file&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;data/email.db&lt;/span&gt;
&lt;span class="na"&gt;mbox_file&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;data/email.mbox&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/div&gt;
&lt;!-- PELICAN_END_SUMMARY --&gt;
&lt;p&gt;It now becomes possible to do a mail import with anonymized data without having
to provide inline parameters or keyword arguments. While this is not a major
item of interest at the moment, my hope is that down the line this convention or
some future variation of it will make for a simple way for users without Python
experience to take advantage of this project.&lt;/p&gt;
&lt;p&gt;Next, a couple lines of code wherever the settings are needed will get them
loaded up for easy access:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;table class="highlighttable"&gt;&lt;tr&gt;&lt;td class="linenos"&gt;&lt;div class="linenodiv"&gt;&lt;pre&gt;&lt;span class="normal"&gt;0&lt;/span&gt;
&lt;span class="normal"&gt;1&lt;/span&gt;
&lt;span class="normal"&gt;2&lt;/span&gt;
&lt;span class="normal"&gt;3&lt;/span&gt;
&lt;span class="normal"&gt;4&lt;/span&gt;
&lt;span class="normal"&gt;5&lt;/span&gt;
&lt;span class="normal"&gt;6&lt;/span&gt;
&lt;span class="normal"&gt;7&lt;/span&gt;
&lt;span class="normal"&gt;8&lt;/span&gt;
&lt;span class="normal"&gt;9&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;div&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;ConfigParser&lt;/span&gt;
&lt;span class="n"&gt;config&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ConfigParser&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ConfigParser&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;read&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;settings.cfg&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;

&lt;span class="nb"&gt;print&lt;/span&gt; &lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;mail&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;anonymize&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="kc"&gt;False&lt;/span&gt;
&lt;span class="nb"&gt;print&lt;/span&gt; &lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;mail&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;db_file&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;email&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;db&lt;/span&gt;
&lt;span class="nb"&gt;print&lt;/span&gt; &lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;mail&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;mbox_file&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;email&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;mbox&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/div&gt;
&lt;p&gt;Here it is simply necessary to &amp;quot;read&amp;quot; the settings file in to ConfigPasrer and
then provide the &amp;quot;section&amp;quot; and setting to &lt;a class="reference external" href="https://docs.python.org/2.7/library/configparser.html#ConfigParser.ConfigParser.get"&gt;ConfigParser.get()&lt;/a&gt; in order to
retrieve the information from the file. Previously, changing the location of the
mbox file would require finding where it is set in the code and changing it.
Now, the setting can simply be changed in &lt;tt class="docutils literal"&gt;settings.cfg&lt;/tt&gt; and put to use
throughout the package.&lt;/p&gt;
&lt;p&gt;As I began to refactor the code to use ConfigParser, I hit some trouble deciding
how to provide default values if a config setting is missing. Luckily, the
Python documentation provides a very clear note about just that in the
&lt;a class="reference external" href="https://docs.python.org/2.7/library/configparser.html#ConfigParser.RawConfigParser.read"&gt;RawConfigParser.read()&lt;/a&gt; section:&lt;/p&gt;
&lt;blockquote&gt;
An application which requires initial values to be loaded from a file should
load the required file or files using readfp() before calling read() for any
optional files.&lt;/blockquote&gt;
&lt;p&gt;This can be put to use by creating another file, &lt;tt class="docutils literal"&gt;settings.default.cfg&lt;/tt&gt;, with
similar contents and adding one line to the code from above:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;table class="highlighttable"&gt;&lt;tr&gt;&lt;td class="linenos"&gt;&lt;div class="linenodiv"&gt;&lt;pre&gt;&lt;span class="normal"&gt;0&lt;/span&gt;
&lt;span class="normal"&gt;1&lt;/span&gt;
&lt;span class="normal"&gt;2&lt;/span&gt;
&lt;span class="normal"&gt;3&lt;/span&gt;
&lt;span class="normal"&gt;4&lt;/span&gt;
&lt;span class="normal"&gt;5&lt;/span&gt;
&lt;span class="normal"&gt;6&lt;/span&gt;
&lt;span class="normal"&gt;7&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;div&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="c1"&gt;; settings.default.cfg&lt;/span&gt;
&lt;span class="k"&gt;[general]&lt;/span&gt;
&lt;span class="na"&gt;color&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;blue&lt;/span&gt;

&lt;span class="k"&gt;[mail]&lt;/span&gt;
&lt;span class="na"&gt;anonymize&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;True&lt;/span&gt;
&lt;span class="na"&gt;db_file&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;path/to/database.db&lt;/span&gt;
&lt;span class="na"&gt;mbox_file&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;path/to/mail.mbox&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/div&gt;
&lt;div class="highlight"&gt;&lt;table class="highlighttable"&gt;&lt;tr&gt;&lt;td class="linenos"&gt;&lt;div class="linenodiv"&gt;&lt;pre&gt;&lt;span class="normal"&gt; 0&lt;/span&gt;
&lt;span class="normal"&gt; 1&lt;/span&gt;
&lt;span class="normal"&gt; 2&lt;/span&gt;
&lt;span class="normal"&gt; 3&lt;/span&gt;
&lt;span class="normal"&gt; 4&lt;/span&gt;
&lt;span class="normal"&gt; 5&lt;/span&gt;
&lt;span class="normal"&gt; 6&lt;/span&gt;
&lt;span class="normal"&gt; 7&lt;/span&gt;
&lt;span class="normal"&gt; 8&lt;/span&gt;
&lt;span class="normal"&gt; 9&lt;/span&gt;
&lt;span class="normal"&gt;10&lt;/span&gt;
&lt;span class="normal"&gt;11&lt;/span&gt;
&lt;span class="normal"&gt;12&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;div&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;ConfigParser&lt;/span&gt;
&lt;span class="n"&gt;config&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ConfigParser&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ConfigParser&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;readfp&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;settings.defaults.cfg&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;read&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;settings.cfg&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;

&lt;span class="nb"&gt;print&lt;/span&gt; &lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;general&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;color&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;blue&lt;/span&gt;
&lt;span class="nb"&gt;print&lt;/span&gt; &lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;mail&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;anonymize&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="kc"&gt;False&lt;/span&gt;
&lt;span class="nb"&gt;print&lt;/span&gt; &lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;mail&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;db_file&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;email&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;db&lt;/span&gt;
&lt;span class="nb"&gt;print&lt;/span&gt; &lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;mail&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;mbox_file&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;email&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;mbox&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/div&gt;
&lt;p&gt;Note the change at &lt;strong&gt;Line 2&lt;/strong&gt; above - this tells ConfigParser to read
&lt;tt class="docutils literal"&gt;settings.defaults.cfg&lt;/tt&gt; &lt;cite&gt;before&lt;/cite&gt; &lt;tt class="docutils literal"&gt;settings.cfg&lt;/tt&gt;. The output shows how the
config values end up:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;tt class="docutils literal"&gt;general.color&lt;/tt&gt; is &amp;quot;blue&amp;quot; because it is not provided at all in &lt;tt class="docutils literal"&gt;settings.cfg&lt;/tt&gt;.&lt;/li&gt;
&lt;li&gt;&lt;tt class="docutils literal"&gt;mail.anonymize&lt;/tt&gt; is &lt;tt class="docutils literal"&gt;False&lt;/tt&gt; because &lt;tt class="docutils literal"&gt;settings.cfg&lt;/tt&gt; overrides the &lt;tt class="docutils literal"&gt;True&lt;/tt&gt; setting in &lt;tt class="docutils literal"&gt;settings.defaults.cfg&lt;/tt&gt;.&lt;/li&gt;
&lt;li&gt;&lt;tt class="docutils literal"&gt;mail.db_file&lt;/tt&gt; and &lt;tt class="docutils literal"&gt;mail.mbox_file&lt;/tt&gt; are also set by &lt;tt class="docutils literal"&gt;settings.cfg&lt;/tt&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Now the code can still rely on &lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;config.get('general',&lt;/span&gt; 'color')&lt;/tt&gt; to return
&lt;cite&gt;something&lt;/cite&gt; instead of raising an error if the value is not set at all in
&lt;tt class="docutils literal"&gt;settings.cfg&lt;/tt&gt;.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="customising-plotly-graphs-1"&gt;
&lt;span id="plotly"&gt;&lt;/span&gt;&lt;h2&gt;Customising Plotly Graphs&lt;/h2&gt;
&lt;p&gt;The &lt;a class="reference external" href="https://www.chris-wells.net/articles/2016/11/08/12-years-gmail-2-bootstrapping#graphing"&gt;basic graphs&lt;/a&gt; I have produced so far were only using Plotly's default
settings. There is a &lt;strong&gt;great deal&lt;/strong&gt; of details in &lt;a class="reference external" href="https://plot.ly/python/"&gt;Plotly's Python documentation&lt;/a&gt;
for all types of graphs, settings and layouts. Because I only have two basic
bar graphs at the moment, I focused on how to make things look better without a
lot of individual effort for each graph. There are a number of issues with the
graphs from last week:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;Everything is quite squished because of the large margins.&lt;/li&gt;
&lt;li&gt;The text is much larger than it needs to be.&lt;/li&gt;
&lt;li&gt;The long email addresses do not fit in the graph's viewable area.&lt;/li&gt;
&lt;li&gt;There are no axis labels.&lt;/li&gt;
&lt;li&gt;There is no graph title.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;All of this made for some pretty poor visual representations. To remedy this, I
added some config values to my settings file and created a method to provide
some of the default layout options for a bar graph:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;table class="highlighttable"&gt;&lt;tr&gt;&lt;td class="linenos"&gt;&lt;div class="linenodiv"&gt;&lt;pre&gt;&lt;span class="normal"&gt; 0&lt;/span&gt;
&lt;span class="normal"&gt; 1&lt;/span&gt;
&lt;span class="normal"&gt; 2&lt;/span&gt;
&lt;span class="normal"&gt; 3&lt;/span&gt;
&lt;span class="normal"&gt; 4&lt;/span&gt;
&lt;span class="normal"&gt; 5&lt;/span&gt;
&lt;span class="normal"&gt; 6&lt;/span&gt;
&lt;span class="normal"&gt; 7&lt;/span&gt;
&lt;span class="normal"&gt; 8&lt;/span&gt;
&lt;span class="normal"&gt; 9&lt;/span&gt;
&lt;span class="normal"&gt;10&lt;/span&gt;
&lt;span class="normal"&gt;11&lt;/span&gt;
&lt;span class="normal"&gt;12&lt;/span&gt;
&lt;span class="normal"&gt;13&lt;/span&gt;
&lt;span class="normal"&gt;14&lt;/span&gt;
&lt;span class="normal"&gt;15&lt;/span&gt;
&lt;span class="normal"&gt;16&lt;/span&gt;
&lt;span class="normal"&gt;17&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;div&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="c1"&gt;; settings.default.cfg&lt;/span&gt;
&lt;span class="k"&gt;[font]&lt;/span&gt;
&lt;span class="na"&gt;family&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;Lucida Console, Monaco, monospace&lt;/span&gt;
&lt;span class="na"&gt;size&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;11&lt;/span&gt;

&lt;span class="k"&gt;[color]&lt;/span&gt;
&lt;span class="c1"&gt;; Primary color and shades&lt;/span&gt;
&lt;span class="na"&gt;primary&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;rgb(63, 81, 181)&lt;/span&gt;
&lt;span class="na"&gt;primary_light&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;rgb(197, 202, 233)&lt;/span&gt;
&lt;span class="na"&gt;primary_dark&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;rgb(48, 63, 159)&lt;/span&gt;
&lt;span class="c1"&gt;; Secondary color and shades&lt;/span&gt;
&lt;span class="na"&gt;secondary&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;rgb(255, 193, 7)&lt;/span&gt;
&lt;span class="na"&gt;secondary_light&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;rgb(255, 236, 179)&lt;/span&gt;
&lt;span class="na"&gt;secondary_dark&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;rgb(255, 160, 0, 1)&lt;/span&gt;
&lt;span class="c1"&gt;; Text colors&lt;/span&gt;
&lt;span class="na"&gt;text&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;rgba(0, 0, 0, 1)&lt;/span&gt;
&lt;span class="na"&gt;text_light&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;rgba(0, 0, 0, 0.54)&lt;/span&gt;
&lt;span class="na"&gt;text_lighter&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;rgba(0, 0, 0, 0.38)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/div&gt;
&lt;p&gt;The default color settings used here come from none other than Google's
&lt;a class="reference external" href="https://material.google.com/style/color.html"&gt;Material design guidelines&lt;/a&gt;. I figured this was appropriate given that this
package is working with data exported from Google.&lt;/p&gt;
&lt;p&gt;Now, the default layout options for bar graphs can take advantage of these newly
added settings:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;table class="highlighttable"&gt;&lt;tr&gt;&lt;td class="linenos"&gt;&lt;div class="linenodiv"&gt;&lt;pre&gt;&lt;span class="normal"&gt; 0&lt;/span&gt;
&lt;span class="normal"&gt; 1&lt;/span&gt;
&lt;span class="normal"&gt; 2&lt;/span&gt;
&lt;span class="normal"&gt; 3&lt;/span&gt;
&lt;span class="normal"&gt; 4&lt;/span&gt;
&lt;span class="normal"&gt; 5&lt;/span&gt;
&lt;span class="normal"&gt; 6&lt;/span&gt;
&lt;span class="normal"&gt; 7&lt;/span&gt;
&lt;span class="normal"&gt; 8&lt;/span&gt;
&lt;span class="normal"&gt; 9&lt;/span&gt;
&lt;span class="normal"&gt;10&lt;/span&gt;
&lt;span class="normal"&gt;11&lt;/span&gt;
&lt;span class="normal"&gt;12&lt;/span&gt;
&lt;span class="normal"&gt;13&lt;/span&gt;
&lt;span class="normal"&gt;14&lt;/span&gt;
&lt;span class="normal"&gt;15&lt;/span&gt;
&lt;span class="normal"&gt;16&lt;/span&gt;
&lt;span class="normal"&gt;17&lt;/span&gt;
&lt;span class="normal"&gt;18&lt;/span&gt;
&lt;span class="normal"&gt;19&lt;/span&gt;
&lt;span class="normal"&gt;20&lt;/span&gt;
&lt;span class="normal"&gt;21&lt;/span&gt;
&lt;span class="normal"&gt;22&lt;/span&gt;
&lt;span class="normal"&gt;23&lt;/span&gt;
&lt;span class="normal"&gt;24&lt;/span&gt;
&lt;span class="normal"&gt;25&lt;/span&gt;
&lt;span class="normal"&gt;26&lt;/span&gt;
&lt;span class="normal"&gt;27&lt;/span&gt;
&lt;span class="normal"&gt;28&lt;/span&gt;
&lt;span class="normal"&gt;29&lt;/span&gt;
&lt;span class="normal"&gt;30&lt;/span&gt;
&lt;span class="normal"&gt;31&lt;/span&gt;
&lt;span class="normal"&gt;32&lt;/span&gt;
&lt;span class="normal"&gt;33&lt;/span&gt;
&lt;span class="normal"&gt;34&lt;/span&gt;
&lt;span class="normal"&gt;35&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;div&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;_bar_defaults&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="sd"&gt;&amp;quot;&amp;quot;&amp;quot;Prepares default data and layout options for bar graphs.&lt;/span&gt;
&lt;span class="sd"&gt;    &amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="nb"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;marker&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                &lt;span class="n"&gt;color&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;color&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;primary_light&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
                &lt;span class="n"&gt;line&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                    &lt;span class="n"&gt;color&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;color&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;primary&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
                    &lt;span class="n"&gt;width&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;1&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;orientation&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;h&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="nb"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;font&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                &lt;span class="n"&gt;color&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;color&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;text&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
                &lt;span class="n"&gt;family&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;font&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;family&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
                &lt;span class="n"&gt;size&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;font&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;size&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
            &lt;span class="p"&gt;),&lt;/span&gt;
            &lt;span class="n"&gt;margin&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                &lt;span class="n"&gt;b&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="n"&gt;t&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="p"&gt;),&lt;/span&gt;
            &lt;span class="n"&gt;xaxis&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                &lt;span class="n"&gt;titlefont&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                    &lt;span class="n"&gt;color&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;color&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;text_lighter&amp;#39;&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;yaxis&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                &lt;span class="n"&gt;titlefont&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                    &lt;span class="n"&gt;color&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;color&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;text_lighter&amp;#39;&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="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/div&gt;
&lt;p&gt;This method simply returns two dicts, one for each parameter (&lt;tt class="docutils literal"&gt;data&lt;/tt&gt; and
&lt;tt class="docutils literal"&gt;layout&lt;/tt&gt;) of Plotly's &lt;tt class="docutils literal"&gt;plot()&lt;/tt&gt; method. These dicts provide the following:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;strong&gt;Line 6&lt;/strong&gt;: The color of the bars.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Lines 7-10&lt;/strong&gt;: The color and width of the bar outlines.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Line 12&lt;/strong&gt;: The graph's orientation (I changed this to horizontal to make room for longer email addresses).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Lines 15-19&lt;/strong&gt;: Default font settings for the entire graph.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Lines 20-23&lt;/strong&gt;: Margins for the graph container.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Lines 24-33&lt;/strong&gt;: The font color of the x- and y-axis titles.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;For each individual graph it is necessary to provide some additional
configuration. Here is a modified exert from the &lt;strong&gt;Top Senders&lt;/strong&gt; graph:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;table class="highlighttable"&gt;&lt;tr&gt;&lt;td class="linenos"&gt;&lt;div class="linenodiv"&gt;&lt;pre&gt;&lt;span class="normal"&gt; 0&lt;/span&gt;
&lt;span class="normal"&gt; 1&lt;/span&gt;
&lt;span class="normal"&gt; 2&lt;/span&gt;
&lt;span class="normal"&gt; 3&lt;/span&gt;
&lt;span class="normal"&gt; 4&lt;/span&gt;
&lt;span class="normal"&gt; 5&lt;/span&gt;
&lt;span class="normal"&gt; 6&lt;/span&gt;
&lt;span class="normal"&gt; 7&lt;/span&gt;
&lt;span class="normal"&gt; 8&lt;/span&gt;
&lt;span class="normal"&gt; 9&lt;/span&gt;
&lt;span class="normal"&gt;10&lt;/span&gt;
&lt;span class="normal"&gt;11&lt;/span&gt;
&lt;span class="normal"&gt;12&lt;/span&gt;
&lt;span class="normal"&gt;13&lt;/span&gt;
&lt;span class="normal"&gt;14&lt;/span&gt;
&lt;span class="normal"&gt;15&lt;/span&gt;
&lt;span class="normal"&gt;16&lt;/span&gt;
&lt;span class="normal"&gt;17&lt;/span&gt;
&lt;span class="normal"&gt;18&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;div&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="n"&gt;addresses&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;OrderedDict&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="n"&gt;longest_address&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;row&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;fetchall&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="n"&gt;addresses&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;row&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="n"&gt;row&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;longest_address&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;max&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;longest_address&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;row&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]))&lt;/span&gt;

&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;layout&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_bar_defaults&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;x&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;addresses&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;values&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;y&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;addresses&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;keys&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="n"&gt;layout&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;margin&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;l&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;longest_address&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;getfloat&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;font&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;size&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mf"&gt;1.55&lt;/span&gt;
&lt;span class="n"&gt;layout&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;margin&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;go&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Margin&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;layout&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;margin&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;span class="n"&gt;layout&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;title&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;Top Senders&amp;#39;&lt;/span&gt;
&lt;span class="n"&gt;layout&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;xaxis&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;title&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;Emails received from&amp;#39;&lt;/span&gt;
&lt;span class="n"&gt;layout&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;yaxis&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;title&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;Sender address&amp;#39;&lt;/span&gt;

&lt;span class="n"&gt;py&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;plot&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;go&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&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="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Bar&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;)],&lt;/span&gt;
    &lt;span class="n"&gt;layout&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;go&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Layout&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;layout&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/div&gt;
&lt;p&gt;&lt;strong&gt;Line 6&lt;/strong&gt; makes use of &lt;tt class="docutils literal"&gt;_bar_defaults()&lt;/tt&gt; to establish the &lt;tt class="docutils literal"&gt;data&lt;/tt&gt; and
&lt;tt class="docutils literal"&gt;layout&lt;/tt&gt; template dictionaries. From there a few more details are added and
then the information is passed to &lt;tt class="docutils literal"&gt;plot()&lt;/tt&gt; as keyword arguments.&lt;/p&gt;
&lt;p&gt;One of the big issues with the initial graphs is that only a portion of each
email address can be seen. To remedy this, &lt;strong&gt;Line 9&lt;/strong&gt; makes a small calculation
based on the length of the longest email address (determined while looping
through the query result set) and font size setting. Dividing by &lt;tt class="docutils literal"&gt;1.55&lt;/tt&gt; was
merely a guess-and-check process for various font sizes that seemed to
produce the best results. I suspect this will not always work well.&lt;/p&gt;
&lt;p&gt;&lt;a class="reference external" href="https://docs.python.org/2.7/tutorial/controlflow.html#unpacking-argument-lists"&gt;Unpacking argument lists&lt;/a&gt; was the most important lesson I (re)learned while
working my way through this. This happens in &lt;strong&gt;Lines 10, 16 &amp;amp; 17&lt;/strong&gt;. By placing
two asterisks (&lt;tt class="docutils literal"&gt;**&lt;/tt&gt;) before each dictionary object as it is passed to the
method, the dictionaries are &amp;quot;unpacked&amp;quot; to a group of keyword arguments for the
method using the key:value pairs of the dictionary. Without this, it would have
been much more difficult to figure out how to prepare these default graph
settings.&lt;/p&gt;
&lt;div class="section" id="now-to-compare-the-results"&gt;
&lt;h3&gt;Now, to compare the results!&lt;/h3&gt;
&lt;p&gt;The old &lt;strong&gt;Top Recipients&lt;/strong&gt; graph:&lt;/p&gt;
&lt;div class="overflow-x-container"&gt;
    &lt;div id="210e8275-0ea9-4ea3-80a4-9f3313d0f633" style="height: 100%; width: 100%;" class="plotly-graph-div"&gt;
        &lt;img src="/static/images/2016/12yog/top-recipients.png" alt="Top Recipients (old)" class="no-js-remove" /&gt;
    &lt;/div&gt;
&lt;/div&gt;&lt;p&gt;The &lt;em&gt;new&lt;/em&gt; &lt;strong&gt;Top Recipients&lt;/strong&gt; graph:&lt;/p&gt;
&lt;div class="overflow-x-container"&gt;
    &lt;div id="5cb5c4f9-d2e2-4e53-b3f0-5ccf041894ed" style="height: 100%; width: 100%;" class="plotly-graph-div js-plotly-plot"&gt;
        &lt;img src="/static/images/2016/12yog/top-recipients-new.png" alt="Top Recipients (new)" class="no-js-remove" /&gt;
    &lt;/div&gt;
&lt;/div&gt;&lt;p&gt;The old &lt;strong&gt;Top Senders&lt;/strong&gt; graph:&lt;/p&gt;
&lt;div class="overflow-x-container"&gt;
    &lt;div id="8b1867b3-4885-4081-b111-75e981abed80" style="height: 100%; width: 100%;" class="plotly-graph-div"&gt;
        &lt;img src="/static/images/2016/12yog/top-senders.png" alt="Top Senders (old)" class="no-js-remove" /&gt;
    &lt;/div&gt;
&lt;/div&gt;&lt;p&gt;The &lt;em&gt;new&lt;/em&gt; &lt;strong&gt;Top Senders&lt;/strong&gt; graph:&lt;/p&gt;
&lt;div class="overflow-x-container"&gt;
    &lt;div id="81a6b549-6caf-464f-ae52-171cf2583f46" style="height: 100%; width: 100%;" class="plotly-graph-div js-plotly-plot"&gt;
        &lt;img src="/static/images/2016/12yog/top-senders-new.png" alt="Top Senders (new)" class="no-js-remove" /&gt;
    &lt;/div&gt;
&lt;/div&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;div class="section" id="generating-random-names-1"&gt;
&lt;span id="random"&gt;&lt;/span&gt;&lt;h2&gt;Generating Random Names&lt;/h2&gt;
&lt;p&gt;&lt;a class="reference external" href="https://github.com/cdubz/takeout-inspector/blob/c9e63d49ed0fa67dc0bf1945facef5a831c160a4/takeout_inspector/mail.py#L179"&gt;Previously&lt;/a&gt;, a simple &lt;tt class="docutils literal"&gt;uuid.uuid4()&lt;/tt&gt; was used to come up with &amp;quot;anonymized&amp;quot;
names and addresses for every email address encountered. While effective enough,
this created very long (36 characters) strings that made even longer complete
email addresses. Take this address for example:&lt;/p&gt;
&lt;pre class="literal-block"&gt;
&amp;quot;f47ac10b-58cc-4372-a567-0e02b2c3d479&amp;quot; &amp;lt;f47ac10b-58cc-4372-a567-0e02b2c3d479&amp;#64;domain.tld&amp;gt;
&lt;/pre&gt;
&lt;p&gt;That is &lt;strong&gt;87&lt;/strong&gt; total characters and, while the uniformity is nice, looking at
hundreds of those one after another became very daunting. Plus, these long
addresses were using up precious space on graphs! With little effort I managed
to find the very simple &lt;a class="reference external" href="https://pypi.python.org/pypi/names"&gt;names&lt;/a&gt; package. What does it do? It generates random
names. That's it.&lt;/p&gt;
&lt;p&gt;So I replaced the old &lt;tt class="docutils literal"&gt;_anonymize_address()&lt;/tt&gt; method with one similar to the
following:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;table class="highlighttable"&gt;&lt;tr&gt;&lt;td class="linenos"&gt;&lt;div class="linenodiv"&gt;&lt;pre&gt;&lt;span class="normal"&gt;0&lt;/span&gt;
&lt;span class="normal"&gt;1&lt;/span&gt;
&lt;span class="normal"&gt;2&lt;/span&gt;
&lt;span class="normal"&gt;3&lt;/span&gt;
&lt;span class="normal"&gt;4&lt;/span&gt;
&lt;span class="normal"&gt;5&lt;/span&gt;
&lt;span class="normal"&gt;6&lt;/span&gt;
&lt;span class="normal"&gt;7&lt;/span&gt;
&lt;span class="normal"&gt;8&lt;/span&gt;
&lt;span class="normal"&gt;9&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;div&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;names&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;anonymize_address&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;address&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;domain&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;domain1.tld&amp;#39;&lt;/span&gt;
    &lt;span class="n"&gt;anon_name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;names&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get_full_name&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="s1"&gt;&amp;#39;address&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;address&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="s1"&gt;&amp;#39;anon_address&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;anon_name&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39; &amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;-&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;lower&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;@&amp;#39;&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;domain&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="s1"&gt;&amp;#39;name&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="s1"&gt;&amp;#39;anon_name&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;anon_name&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/div&gt;
&lt;p&gt;Very simply, this function makes up a fake domain, gets a random full name from
&lt;tt class="docutils literal"&gt;names&lt;/tt&gt; and then creates an email address and a name to associate with the
provided &lt;tt class="docutils literal"&gt;address&lt;/tt&gt; and &lt;tt class="docutils literal"&gt;name&lt;/tt&gt; parameters. The &lt;a class="reference external" href="https://github.com/cdubz/takeout-inspector/blob/11526a103d5b8c9b382504b6fc3622a3a7448e56/takeout_inspector/mail.py#L174"&gt;actual method&lt;/a&gt; also keeps
track of these domains and addresses while parsing the data.&lt;/p&gt;
&lt;p&gt;What does all this mean? Instead of getting 87 character random addresses like
these:&lt;/p&gt;
&lt;pre class="literal-block"&gt;
&amp;quot;f47ac10b-58cc-4372-a567-0e02b2c3d479&amp;quot; &amp;lt;f47ac10b-58cc-4372-a567-0e02b2c3d479&amp;#64;domain.tld&amp;gt;
&amp;quot;b2d351b3-4607-48e4-9e17-e8d7266438af&amp;quot; &amp;lt;b2d351b3-4607-48e4-9e17-e8d7266438af&amp;#64;domain.tld&amp;gt;
&amp;quot;8e35be14-f719-416b-bc16-a31e260f4385&amp;quot; &amp;lt;8e35be14-f719-416b-bc16-a31e260f4385&amp;#64;domain.tld&amp;gt;
&amp;quot;79417a64-dd09-400c-93ec-58cbef0d1e01&amp;quot; &amp;lt;79417a64-dd09-400c-93ec-58cbef0d1e01&amp;#64;domain.tld&amp;gt;
&amp;quot;f38e5aac-3211-45b3-bc24-80b3754ae32c&amp;quot; &amp;lt;f38e5aac-3211-45b3-bc24-80b3754ae32c&amp;#64;domain.tld&amp;gt;
&lt;/pre&gt;
&lt;p&gt;We get addresses like these:&lt;/p&gt;
&lt;pre class="literal-block"&gt;
&amp;quot;Stevie Fulton&amp;quot; &amp;lt;stevie-fulton&amp;#64;domain1.tld&amp;gt;
&amp;quot;Anthony Scott&amp;quot; &amp;lt;anthony-scott&amp;#64;domain1.tld&amp;gt;
&amp;quot;Tony Pack&amp;quot; &amp;lt;tony-pack&amp;#64;domain1.tld&amp;gt;
&amp;quot;Charlotte Wilson&amp;quot; &amp;lt;charlotte-wilson&amp;#64;domain1.tld&amp;gt;
&amp;quot;Hugh Jackson&amp;quot; &amp;lt;hugh-jackson&amp;#64;domain1.tld&amp;gt;
&lt;/pre&gt;
&lt;p&gt;The longest address in that group is 49 characters, almost half the uuid4-based
addresses. Granted, this is random so we may get something 87 characters or even
longer, but the non-uniformity has one other big advantage: referencing. If I
want to produce and talk about some graph based on email addresses, it will be
much easier refer to a name like &amp;quot;Stevie Fulton&amp;quot; than
&amp;quot;f47ac10b-58cc-4372-a567-0e02b2c3d479&amp;quot;.&lt;/p&gt;
&lt;div class="center section" id="continue-on-to-part-4-chat"&gt;
&lt;h3&gt;&lt;a class="reference external" href="https://www.chris-wells.net/articles/2016/11/18/12-years-gmail-4-chat"&gt;Continue on to Part 4: Chat...&lt;/a&gt;&lt;/h3&gt;
&lt;/div&gt;
&lt;/div&gt;
</content><category term="Technology"></category><category term="12 years of gmail"></category><category term="configparser"></category><category term="names"></category><category term="graphing"></category><category term="plotly"></category><category term="python"></category><category term="takeout inspector"></category></entry><entry><title>12 Years of Gmail, Part 2: Bootstrapping</title><link href="https://www.chris-wells.net/articles/2016/11/08/12-years-gmail-2-bootstrapping" rel="alternate"></link><published>2016-11-08T16:45:00-04:00</published><updated>2016-11-19T09:30:00-05:00</updated><author><name>Christopher Charbonneau Wells</name></author><id>tag:www.chris-wells.net,2016-11-08:/articles/2016/11/08/12-years-gmail-2-bootstrapping</id><summary type="html">
&lt;p&gt;Jumping back in to Python has been just as fun as my first experiences
with it. After brushing off some of the dust, I have managed to put
together a (very) small package that does a couple of basic things with
a Google Takeout Mail (mbox) file:&lt;/p&gt;
&lt;ol class="arabic simple"&gt;
&lt;li&gt;&lt;a class="reference external" href="https://www.chris-wells.net/articles/2016/11/08/12-years-gmail-2-bootstrapping#parsing"&gt;Parses and standardizes the format of email addresses&lt;/a&gt;;&lt;/li&gt;
&lt;li&gt;&lt;a class="reference external" href="https://www.chris-wells.net/articles/2016/11/08/12-years-gmail-2-bootstrapping#importing"&gt;Imports key messages data in to an sqlite database&lt;/a&gt;;&lt;/li&gt;
&lt;li&gt;&lt;a class="reference external" href="https://www.chris-wells.net/articles/2016/11/08/12-years-gmail-2-bootstrapping#graphing"&gt;Produces simple graphs of top recipients and senders&lt;/a&gt;.&lt;/li&gt;
&lt;/ol&gt;
</summary><content type="html">&lt;blockquote class="small"&gt;
    &lt;em&gt;
        This post is part of my series, &lt;a href="/tag/12-years-of-gmail"&gt;12 Years of Gmail&lt;/a&gt;,
        taking a look at the data Google has accumulated on me over the past
        12 years of using various Google services and documenting the
        learning experience developing an open source Python project
        (&lt;a href="https://github.com/cdubz/takeout-inspector"&gt;Takeout Inspector&lt;/a&gt;)
        to analyze that data.
    &lt;/em&gt;
&lt;/blockquote&gt;
&lt;p&gt;Jumping back in to Python has been just as fun as my first experiences
with it. After brushing off some of the dust, I have managed to put
together a (very) small package that does a couple of basic things with
a Google Takeout Mail (mbox) file:&lt;/p&gt;
&lt;ol class="arabic simple"&gt;
&lt;li&gt;&lt;a class="reference external" href="https://www.chris-wells.net/articles/2016/11/08/12-years-gmail-2-bootstrapping#parsing"&gt;Parses and standardizes the format of email addresses&lt;/a&gt;;&lt;/li&gt;
&lt;li&gt;&lt;a class="reference external" href="https://www.chris-wells.net/articles/2016/11/08/12-years-gmail-2-bootstrapping#importing"&gt;Imports key messages data in to an sqlite database&lt;/a&gt;;&lt;/li&gt;
&lt;li&gt;&lt;a class="reference external" href="https://www.chris-wells.net/articles/2016/11/08/12-years-gmail-2-bootstrapping#graphing"&gt;Produces simple graphs of top recipients and senders&lt;/a&gt;.&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="section" id="parsing-email-addresses"&gt;
&lt;span id="parsing"&gt;&lt;/span&gt;&lt;h2&gt;Parsing Email Addresses&lt;/h2&gt;
&lt;p&gt;The &lt;a class="reference external" href="https://docs.python.org/2/library/mailbox.html"&gt;mailbox&lt;/a&gt; Python module makes it very simple to get an
mbox file in to Python and play around using the &lt;a class="reference external" href="https://docs.python.org/2/library/mailbox.html"&gt;mailbox.Mailbox&lt;/a&gt;
and &lt;a class="reference external" href="https://docs.python.org/2/library/email.message.html"&gt;email.Message&lt;/a&gt; classes. Here is an example using my mbox file:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;table class="highlighttable"&gt;&lt;tr&gt;&lt;td class="linenos"&gt;&lt;div class="linenodiv"&gt;&lt;pre&gt;&lt;span class="normal"&gt;0&lt;/span&gt;
&lt;span class="normal"&gt;1&lt;/span&gt;
&lt;span class="normal"&gt;2&lt;/span&gt;
&lt;span class="normal"&gt;3&lt;/span&gt;
&lt;span class="normal"&gt;4&lt;/span&gt;
&lt;span class="normal"&gt;5&lt;/span&gt;
&lt;span class="normal"&gt;6&lt;/span&gt;
&lt;span class="normal"&gt;7&lt;/span&gt;
&lt;span class="normal"&gt;8&lt;/span&gt;
&lt;span class="normal"&gt;9&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;div&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;mailbox&lt;/span&gt;
&lt;span class="n"&gt;email&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;mailbox&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;mbox&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;/path/to/email.mbox&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# The number of emails in the mbox file.&lt;/span&gt;
&lt;span class="nb"&gt;print&lt;/span&gt; &lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;email&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;keys&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
&lt;span class="mi"&gt;114407&lt;/span&gt;

&lt;span class="c1"&gt;# The &amp;quot;Delivered-To&amp;quot; header of the first email.&lt;/span&gt;
&lt;span class="nb"&gt;print&lt;/span&gt; &lt;span class="n"&gt;email&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="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;Delivered-To&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;my&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;address&lt;/span&gt;&lt;span class="nd"&gt;@hmail&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;com&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/div&gt;
&lt;p&gt;After using &lt;tt class="docutils literal"&gt;mailbox&lt;/tt&gt; to establish a database and doing an initial
import, I quickly noticed that many email addresses were either oddly
formatted or differently formatted versions of the same address. Also,
header fields with lists of address were not all the same format and
often repeated addresses or had other issues. These inconsistencies made
it difficult to aggregate information for specific addresses or
otherwise organize SQL queries around them.&lt;/p&gt;
&lt;p&gt;Here is an example header from the very first email in my mbox file (all
of the actual email addresses are replaced with fake ones):&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;table class="highlighttable"&gt;&lt;tr&gt;&lt;td class="linenos"&gt;&lt;div class="linenodiv"&gt;&lt;pre&gt;&lt;span class="normal"&gt;0&lt;/span&gt;
&lt;span class="normal"&gt;1&lt;/span&gt;
&lt;span class="normal"&gt;2&lt;/span&gt;
&lt;span class="normal"&gt;3&lt;/span&gt;
&lt;span class="normal"&gt;4&lt;/span&gt;
&lt;span class="normal"&gt;5&lt;/span&gt;
&lt;span class="normal"&gt;6&lt;/span&gt;
&lt;span class="normal"&gt;7&lt;/span&gt;
&lt;span class="normal"&gt;8&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;div&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="n"&gt;John&lt;/span&gt; &lt;span class="n"&gt;Smith&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;john&lt;/span&gt;&lt;span class="nd"&gt;@smith&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;com&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;Joe&lt;/span&gt; &lt;span class="n"&gt;Wilson&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Joe&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;wilson&lt;/span&gt;&lt;span class="nd"&gt;@hmail&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;com&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;bobl1943&lt;/span&gt;&lt;span class="nd"&gt;@wahoo&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;com&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="s2"&gt;&amp;quot;Bill White&amp;quot;&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;bwhite&lt;/span&gt;&lt;span class="nd"&gt;@frontier&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;com&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;Joe&lt;/span&gt; &lt;span class="n"&gt;Johnson&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;jj&lt;/span&gt;&lt;span class="nd"&gt;@jandj&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;com&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;Mike&lt;/span&gt; &lt;span class="n"&gt;Proud&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;myfirstemail&lt;/span&gt;&lt;span class="nd"&gt;@wahoo&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;com&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;Mom&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Jill&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;Johnson&lt;/span&gt;&lt;span class="nd"&gt;@hmail&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;com&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Dad&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;bob&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;johnson&lt;/span&gt;&lt;span class="nd"&gt;@hmail&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;com&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="s2"&gt;&amp;quot;John Irving&amp;quot;&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;JOHN&lt;/span&gt;&lt;span class="nd"&gt;@johnirving&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;com&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Big&lt;/span&gt; &lt;span class="n"&gt;bro&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;rob&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;m&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;johnson&lt;/span&gt;&lt;span class="nd"&gt;@hmail&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;com&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;Jane&lt;/span&gt; &lt;span class="n"&gt;Stevens&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;jane&lt;/span&gt;&lt;span class="nd"&gt;@stevens&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;inc&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;net&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Jane&lt;/span&gt; &lt;span class="n"&gt;Stevens&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;jane&lt;/span&gt;&lt;span class="nd"&gt;@stevens&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;inc&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;net&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;Michael&lt;/span&gt; &lt;span class="n"&gt;Whitehead&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;mwh823&lt;/span&gt;&lt;span class="nd"&gt;@purdue&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;edu&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/div&gt;
&lt;p&gt;There are a lot of problems to note here:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;Addresses are inconsistently separated by carriage returns.&lt;/li&gt;
&lt;li&gt;Most addresses are prefixed by a tab character.&lt;/li&gt;
&lt;li&gt;Some lines have one address, others have more than one.&lt;/li&gt;
&lt;li&gt;One email address does not have an associated name.&lt;/li&gt;
&lt;li&gt;Some names are surrounded in double quotes.&lt;/li&gt;
&lt;li&gt;One email address is repeated.&lt;/li&gt;
&lt;li&gt;Letter case is inconsistent.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;None of this is particularly difficult to deal with in Python. Each
address _is_ separated by a comma, for example, so creating a list from
this header would be simple enough (but the comma is not always
guaranteed!). I worked on parsing these fields and addresses manually a
bit before finding the wonderful &lt;a class="reference external" href="https://docs.python.org/2/library/email.util.html"&gt;email.utils&lt;/a&gt; module.&lt;/p&gt;
&lt;p&gt;The method &lt;a class="reference external" href="https://docs.python.org/2/library/email.util.html#email.utils.getaddresses"&gt;email.utils.getaddresses&lt;/a&gt; can convert the above header to a
list of name-email tuples without having to worry about the extraneous
carriage returns, spaces or other formatting differences in the header:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;table class="highlighttable"&gt;&lt;tr&gt;&lt;td class="linenos"&gt;&lt;div class="linenodiv"&gt;&lt;pre&gt;&lt;span class="normal"&gt; 0&lt;/span&gt;
&lt;span class="normal"&gt; 1&lt;/span&gt;
&lt;span class="normal"&gt; 2&lt;/span&gt;
&lt;span class="normal"&gt; 3&lt;/span&gt;
&lt;span class="normal"&gt; 4&lt;/span&gt;
&lt;span class="normal"&gt; 5&lt;/span&gt;
&lt;span class="normal"&gt; 6&lt;/span&gt;
&lt;span class="normal"&gt; 7&lt;/span&gt;
&lt;span class="normal"&gt; 8&lt;/span&gt;
&lt;span class="normal"&gt; 9&lt;/span&gt;
&lt;span class="normal"&gt;10&lt;/span&gt;
&lt;span class="normal"&gt;11&lt;/span&gt;
&lt;span class="normal"&gt;12&lt;/span&gt;
&lt;span class="normal"&gt;13&lt;/span&gt;
&lt;span class="normal"&gt;14&lt;/span&gt;
&lt;span class="normal"&gt;15&lt;/span&gt;
&lt;span class="normal"&gt;16&lt;/span&gt;
&lt;span class="normal"&gt;17&lt;/span&gt;
&lt;span class="normal"&gt;18&lt;/span&gt;
&lt;span class="normal"&gt;19&lt;/span&gt;
&lt;span class="normal"&gt;20&lt;/span&gt;
&lt;span class="normal"&gt;21&lt;/span&gt;
&lt;span class="normal"&gt;22&lt;/span&gt;
&lt;span class="normal"&gt;23&lt;/span&gt;
&lt;span class="normal"&gt;24&lt;/span&gt;
&lt;span class="normal"&gt;25&lt;/span&gt;
&lt;span class="normal"&gt;26&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;div&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;email&lt;/span&gt;
&lt;span class="n"&gt;header&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;&amp;#39;&amp;#39;John Smith &amp;lt;john@smith.com&amp;gt;,&lt;/span&gt;
&lt;span class="s1"&gt;        Joe Wilson &amp;lt;Joe.wilson@hmail.com&amp;gt;, &amp;lt;bobl1943@wahoo.com&amp;gt;,&lt;/span&gt;
&lt;span class="s1"&gt;        &amp;quot;Bill White&amp;quot; &amp;lt;bwhite@frontier.com&amp;gt;,&lt;/span&gt;
&lt;span class="s1"&gt;        Joe Johnson &amp;lt;jj@jandj.com&amp;gt;,&lt;/span&gt;
&lt;span class="s1"&gt;        Mike Proud &amp;lt;myfirstemail@wahoo.com&amp;gt;,&lt;/span&gt;
&lt;span class="s1"&gt;        Mom &amp;lt;Jill-Johnson@hmail.com&amp;gt;, Dad &amp;lt;bob-johnson@hmail.com&amp;gt;,&lt;/span&gt;
&lt;span class="s1"&gt;        &amp;quot;John Irving&amp;quot; &amp;lt;JOHN@johnirving.com&amp;gt;, Big bro &amp;lt;rob.m.johnson@hmail.com&amp;gt;,&lt;/span&gt;
&lt;span class="s1"&gt;        Jane Stevens &amp;lt;jane@stevens-inc.net&amp;gt;, Jane Stevens &amp;lt;jane@stevens-inc.net&amp;gt;,&lt;/span&gt;
&lt;span class="s1"&gt;        Michael Whitehead &amp;lt;mwh823@purdue.edu&amp;gt;&amp;#39;&amp;#39;&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="n"&gt;addresses&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;email&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;utils&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;getaddresses&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;header&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nb"&gt;print&lt;/span&gt; &lt;span class="n"&gt;addresses&lt;/span&gt;
&lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;John Smith&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;john@smith.com&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;Joe Wilson&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;Joe.wilson@hmail.com&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;bobl1943@wahoo.com&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;Bill White&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;bwhite@frontier.com&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;Joe Johnson&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;jj@jandj.com&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;Mike Proud&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;myfirstemail@wahoo.com&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;Mom&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;Jill-Johnson@hmail.com&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;Dad&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;bob-johnson@hmail.com&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;John Irving&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;JOHN@johnirving.com&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;Big bro&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;rob.m.johnson@hmail.com&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;Jane Stevens&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;jane@stevens-inc.net&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;Jane Stevens&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;jane@stevens-inc.net&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;Michael Whitehead&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;mwh823@purdue.edu&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/div&gt;
&lt;p&gt;Now some basic Python can clean things up a bit (using the &lt;tt class="docutils literal"&gt;addresses&lt;/tt&gt;
list created above):&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;table class="highlighttable"&gt;&lt;tr&gt;&lt;td class="linenos"&gt;&lt;div class="linenodiv"&gt;&lt;pre&gt;&lt;span class="normal"&gt;0&lt;/span&gt;
&lt;span class="normal"&gt;1&lt;/span&gt;
&lt;span class="normal"&gt;2&lt;/span&gt;
&lt;span class="normal"&gt;3&lt;/span&gt;
&lt;span class="normal"&gt;4&lt;/span&gt;
&lt;span class="normal"&gt;5&lt;/span&gt;
&lt;span class="normal"&gt;6&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;div&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="n"&gt;addresses&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;list&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;addresses&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;idx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;address&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nb"&gt;enumerate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;addresses&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;local_part&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;domain&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;address&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="n"&gt;split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;@&amp;#39;&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;domain&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;domain&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;/&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;lower&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;local_part&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;local_part&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;.&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;lower&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;address_formatted&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;local_part&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;@&amp;#39;&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;domain&lt;/span&gt;
    &lt;span class="n"&gt;addresses&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;idx&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;address&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;address_formatted&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/div&gt;
&lt;p&gt;The above code block removes duplicates (&lt;strong&gt;Line 0&lt;/strong&gt;), breaks
each address in to its &lt;a class="reference external" href="https://xmpp.org/rfcs/rfc6122.html#addressing-localpart"&gt;local&lt;/a&gt; and &lt;a class="reference external" href="https://xmpp.org/rfcs/rfc6122.html#addressing-domain"&gt;domain&lt;/a&gt; parts (&lt;strong&gt;Line 2&lt;/strong&gt;),
removes any &lt;a class="reference external" href="https://xmpp.org/rfcs/rfc6122.html#addressing-resource"&gt;resourcepart&lt;/a&gt; (Gmail stores chat data in the mbox file)
(&lt;strong&gt;Line 3&lt;/strong&gt;), converts all strings to lower case (&lt;strong&gt;Lines 3-4&lt;/strong&gt;) and
recombines the parts to update the entry in &lt;tt class="docutils literal"&gt;addresses&lt;/tt&gt; (&lt;strong&gt;Lines 5-6&lt;/strong&gt;).&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Line 4&lt;/strong&gt; also removes any periods from the localpart. I
initially did this because Gmail addresses ignore periods, however it
now strikes me that this is likely not universally true! I will have to
fix this in a forthcoming push of &lt;a class="reference external" href="https://github.com/cdubz/takeout-inspector"&gt;takeout inspector&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;All together these operations create a list of unique tuples with
standard relatively email address formatting:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;table class="highlighttable"&gt;&lt;tr&gt;&lt;td class="linenos"&gt;&lt;div class="linenodiv"&gt;&lt;pre&gt;&lt;span class="normal"&gt; 0&lt;/span&gt;
&lt;span class="normal"&gt; 1&lt;/span&gt;
&lt;span class="normal"&gt; 2&lt;/span&gt;
&lt;span class="normal"&gt; 3&lt;/span&gt;
&lt;span class="normal"&gt; 4&lt;/span&gt;
&lt;span class="normal"&gt; 5&lt;/span&gt;
&lt;span class="normal"&gt; 6&lt;/span&gt;
&lt;span class="normal"&gt; 7&lt;/span&gt;
&lt;span class="normal"&gt; 8&lt;/span&gt;
&lt;span class="normal"&gt; 9&lt;/span&gt;
&lt;span class="normal"&gt;10&lt;/span&gt;
&lt;span class="normal"&gt;11&lt;/span&gt;
&lt;span class="normal"&gt;12&lt;/span&gt;
&lt;span class="normal"&gt;13&lt;/span&gt;
&lt;span class="normal"&gt;14&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;div&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="nb"&gt;print&lt;/span&gt; &lt;span class="n"&gt;addresses&lt;/span&gt;
&lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;Dad&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;bob-johnson@hmail.com&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;Mike Proud&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;myfirstemail@wahoo.com&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;Joe Johnson&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;jj@jandj.com&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;Jane Stevens&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;jane@stevens-inc.net&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;bobl1943@wahoo.com&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;Joe Wilson&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;joewilson@hmail.com&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;Mom&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;jill-johnson@hmail.com&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;Bill White&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;bwhite@frontier.com&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;Big bro&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;robmjohnson@hmail.com&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;John Irving&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;john@johnirving.com&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;John Smith&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;john@smith.com&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;Michael Whitehead&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;mwh823@purdue.edu&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;div class="section" id="importing-in-to-sqlite"&gt;
&lt;span id="importing"&gt;&lt;/span&gt;&lt;h2&gt;Importing in to SQLite&lt;/h2&gt;
&lt;p&gt;Now that the To and CC headers can be parsed in to simple lists, the key
data needs to be stored in a way that is easier (and quicker) to work
with than &lt;em&gt;Mailbox&lt;/em&gt; - enter &lt;a class="reference external" href="https://sqlite.org/"&gt;SQLite&lt;/a&gt;. My experience is largely with
MySQL so there was a bit of fumbling around getting this set up with
Python's &lt;a class="reference external" href="https://docs.python.org/2/library/sqlite3.html"&gt;sqlite3&lt;/a&gt; module. After I got the hang of it, I started poking
through my entire mbox file (3.5GB) to figure out what information to
import (for now).&lt;/p&gt;
&lt;p&gt;I decided to start with three tables -&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;tt class="docutils literal"&gt;messages&lt;/tt&gt; to store some key information about all emails;&lt;/li&gt;
&lt;li&gt;&lt;tt class="docutils literal"&gt;headers&lt;/tt&gt; to store raw headers for all emails;&lt;/li&gt;
&lt;li&gt;&lt;tt class="docutils literal"&gt;recipients&lt;/tt&gt; to store one-row-per-address entries for any email address in the &amp;quot;To&amp;quot; or &amp;quot;CC&amp;quot; header.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The &lt;tt class="docutils literal"&gt;messages&lt;/tt&gt; table includes the headers: &lt;tt class="docutils literal"&gt;From&lt;/tt&gt;, &lt;tt class="docutils literal"&gt;To&lt;/tt&gt;,
&lt;tt class="docutils literal"&gt;Subject&lt;/tt&gt;, &lt;tt class="docutils literal"&gt;Date&lt;/tt&gt;, &lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;X-GM-THRID&lt;/span&gt;&lt;/tt&gt; and &lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;X-Gmail-Labels&lt;/span&gt;&lt;/tt&gt;.
&lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;X-GM-THRID&lt;/span&gt;&lt;/tt&gt; is a Gmail-specific numeric ID that relates messages in
threads and &lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;X-Gmail-Labels&lt;/span&gt;&lt;/tt&gt; stores a comma-separated list of labels
for each email. Common labels include &amp;quot;Chat&amp;quot; for chat messages, &amp;quot;Sent&amp;quot;
for sent emails, Important, Spam, etc.&lt;/p&gt;
&lt;p&gt;The &lt;tt class="docutils literal"&gt;Date&lt;/tt&gt; field is an interesting one to deal with because it needs
to converted to a format that SQLite can use efficiently. SQLite relies
on &lt;a class="reference external" href="https://en.wikipedia.org/wiki/ISO_8601"&gt;ISO 8601&lt;/a&gt; while email &lt;tt class="docutils literal"&gt;Date&lt;/tt&gt; headers use a format outlined in
&lt;a class="reference external" href="https://tools.ietf.org/html/rfc2822#section-3.3"&gt;RFC 2822&lt;/a&gt;. Also, Gmail puts date information for its chat messages
in a different header that is accessible using
&lt;a class="reference external" href="https://docs.python.org/2/library/mailbox.html?#mailbox.mboxMessage.get_from"&gt;mailbox.mboxMessage.getfrom()&lt;/a&gt;. Once again using my mbox file as an
example:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;table class="highlighttable"&gt;&lt;tr&gt;&lt;td class="linenos"&gt;&lt;div class="linenodiv"&gt;&lt;pre&gt;&lt;span class="normal"&gt; 0&lt;/span&gt;
&lt;span class="normal"&gt; 1&lt;/span&gt;
&lt;span class="normal"&gt; 2&lt;/span&gt;
&lt;span class="normal"&gt; 3&lt;/span&gt;
&lt;span class="normal"&gt; 4&lt;/span&gt;
&lt;span class="normal"&gt; 5&lt;/span&gt;
&lt;span class="normal"&gt; 6&lt;/span&gt;
&lt;span class="normal"&gt; 7&lt;/span&gt;
&lt;span class="normal"&gt; 8&lt;/span&gt;
&lt;span class="normal"&gt; 9&lt;/span&gt;
&lt;span class="normal"&gt;10&lt;/span&gt;
&lt;span class="normal"&gt;11&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;div&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;mailbox&lt;/span&gt;
&lt;span class="n"&gt;email&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;mailbox&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;mbox&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;/path/to/email.mbox&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# A typical Date header.&lt;/span&gt;
&lt;span class="nb"&gt;print&lt;/span&gt; &lt;span class="n"&gt;email&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="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;Date&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;Mon&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt; &lt;span class="n"&gt;Oct&lt;/span&gt; &lt;span class="mi"&gt;2015&lt;/span&gt; &lt;span class="mi"&gt;13&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;18&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;0300&lt;/span&gt;

&lt;span class="c1"&gt;# A missing Date header for Gmail chats.&lt;/span&gt;
&lt;span class="nb"&gt;print&lt;/span&gt; &lt;span class="n"&gt;email&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;Date&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="kc"&gt;None&lt;/span&gt;
&lt;span class="nb"&gt;print&lt;/span&gt; &lt;span class="n"&gt;email&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get_from&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="n"&gt;xxx&lt;/span&gt;&lt;span class="nd"&gt;@xxx&lt;/span&gt; &lt;span class="n"&gt;Mon&lt;/span&gt; &lt;span class="n"&gt;Oct&lt;/span&gt; &lt;span class="mi"&gt;05&lt;/span&gt; &lt;span class="mi"&gt;00&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;37&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;04&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="mi"&gt;0000&lt;/span&gt; &lt;span class="mi"&gt;2015&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/div&gt;
&lt;p&gt;Both of these possibilies can be handled and converted to ISO 8601
format UTC dates using the &lt;tt class="docutils literal"&gt;email.utils&lt;/tt&gt; and &lt;a class="reference external" href="https://docs.python.org/2.7/library/datetime.html"&gt;datetime&lt;/a&gt; Python
modules. The following function assumes two parameters, the result of
&lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;get('Date')&lt;/span&gt;&lt;/tt&gt; and the result of &lt;tt class="docutils literal"&gt;get_from()&lt;/tt&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;table class="highlighttable"&gt;&lt;tr&gt;&lt;td class="linenos"&gt;&lt;div class="linenodiv"&gt;&lt;pre&gt;&lt;span class="normal"&gt; 0&lt;/span&gt;
&lt;span class="normal"&gt; 1&lt;/span&gt;
&lt;span class="normal"&gt; 2&lt;/span&gt;
&lt;span class="normal"&gt; 3&lt;/span&gt;
&lt;span class="normal"&gt; 4&lt;/span&gt;
&lt;span class="normal"&gt; 5&lt;/span&gt;
&lt;span class="normal"&gt; 6&lt;/span&gt;
&lt;span class="normal"&gt; 7&lt;/span&gt;
&lt;span class="normal"&gt; 8&lt;/span&gt;
&lt;span class="normal"&gt; 9&lt;/span&gt;
&lt;span class="normal"&gt;10&lt;/span&gt;
&lt;span class="normal"&gt;11&lt;/span&gt;
&lt;span class="normal"&gt;12&lt;/span&gt;
&lt;span class="normal"&gt;13&lt;/span&gt;
&lt;span class="normal"&gt;14&lt;/span&gt;
&lt;span class="normal"&gt;15&lt;/span&gt;
&lt;span class="normal"&gt;16&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;div&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;email&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;datetime&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;datetime&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;get_message_date&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;get_date&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;get_from&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;get_date&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;mail_date&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;get_date&lt;/span&gt;
    &lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;mail_date&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;get_from&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;strip&lt;/span&gt;&lt;span class="p"&gt;()[&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="p"&gt;:]&lt;/span&gt;

    &lt;span class="n"&gt;datetime_tuple&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;email&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;utils&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;parsedate_tz&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;mail_date&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;datetime_tuple&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;unix_time&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;email&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;utils&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;mktime_tz&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;datetime_tuple&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;mail_date_iso8601&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;datetime&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;utcfromtimestamp&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;unix_time&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;isoformat&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39; &amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;mail_date_iso8601&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;&amp;#39;&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;mail_date_iso8601&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/div&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;strong&gt;Lines 4-7&lt;/strong&gt; determine which of the two headers to use.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Line 9&lt;/strong&gt; creates a tuple of the date including timezone information.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Lines 10-14&lt;/strong&gt; attempt to convert the date tuples to ISO 8601 format.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Using the sane two example emails from my mbox file, the function above
produces the following outputs:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;table class="highlighttable"&gt;&lt;tr&gt;&lt;td class="linenos"&gt;&lt;div class="linenodiv"&gt;&lt;pre&gt;&lt;span class="normal"&gt;0&lt;/span&gt;
&lt;span class="normal"&gt;1&lt;/span&gt;
&lt;span class="normal"&gt;2&lt;/span&gt;
&lt;span class="normal"&gt;3&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;div&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="nb"&gt;print&lt;/span&gt; &lt;span class="n"&gt;get_message_date&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;Mon, 5 Oct 2015 13:18:10 -0300&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;None&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="mi"&gt;2015&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;05&lt;/span&gt; &lt;span class="mi"&gt;16&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;18&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;
&lt;span class="nb"&gt;print&lt;/span&gt; &lt;span class="n"&gt;get_message_date&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;None&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;xxx@xxx Mon Oct 05 00:37:04 +0000 2015&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="mi"&gt;2015&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;05&lt;/span&gt; &lt;span class="mi"&gt;00&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;37&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;04&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/div&gt;
&lt;p&gt;Associating these formatted dates with the rows in &lt;tt class="docutils literal"&gt;messages&lt;/tt&gt; allows
for queries to be run with date/time parameters. For example, to see how
many emails from my mbox were handled in December 2014, I can run:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;table class="highlighttable"&gt;&lt;tr&gt;&lt;td class="linenos"&gt;&lt;div class="linenodiv"&gt;&lt;pre&gt;&lt;span class="normal"&gt;0&lt;/span&gt;
&lt;span class="normal"&gt;1&lt;/span&gt;
&lt;span class="normal"&gt;2&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;div&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;COUNT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;`&lt;/span&gt;&lt;span class="nb"&gt;date&lt;/span&gt;&lt;span class="o"&gt;`&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;`&lt;/span&gt;&lt;span class="n"&gt;messages&lt;/span&gt;&lt;span class="o"&gt;`&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;`&lt;/span&gt;&lt;span class="nb"&gt;date&lt;/span&gt;&lt;span class="o"&gt;`&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;2014-12-01&amp;#39;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;AND&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;`&lt;/span&gt;&lt;span class="nb"&gt;date&lt;/span&gt;&lt;span class="o"&gt;`&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;lt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;2014-12-31&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="mi"&gt;1086&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/div&gt;
&lt;p&gt;If this data were not in a database, it would be necessary to loop
through all the messages in my mbox to figure this out. Instead, this
SQL query takes a measly 31ms.&lt;/p&gt;
&lt;p&gt;The remaining tables (&lt;tt class="docutils literal"&gt;recipients&lt;/tt&gt; and &lt;tt class="docutils literal"&gt;headers&lt;/tt&gt;) don't deal with
anything as &amp;quot;complex&amp;quot; (by comparision) as the &lt;tt class="docutils literal"&gt;messages.date&lt;/tt&gt; column.
Check out &lt;a class="reference external" href="https://github.com/cdubz/takeout-inspector/blob/b6f5de98d383593bfe940c428edf687dc633aebb/takeout_inspector/mail.py"&gt;commit b6f5de9&lt;/a&gt; on GitHub to see how these tables are
populated as of this writing.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="very-basic-graphing-using-plotly"&gt;
&lt;span id="graphing"&gt;&lt;/span&gt;&lt;h2&gt;(Very) Basic Graphing using Plotly&lt;/h2&gt;
&lt;p&gt;I have not yet spent a lot of time considering the many Python graphing
packages that are available. &lt;a class="reference external" href="http://pbpython.com/visualization-tools-1.html"&gt;Overview of Python Visualization Tools&lt;/a&gt;
is a quick review of some of the more popular packages that exist. After
reading that article, I elected to give &lt;a class="reference external" href="https://plot.ly/"&gt;Plotly&lt;/a&gt; a try as it appeared
to have the quickest path to interactive graphs (and it did prove to be
quite fast). There really isn't much happening on this front yet. I
only pushed out this bit of code to get a feel for pulling data from
SQLite in to Plotly.&lt;/p&gt;
&lt;div class="section" id="top-20-recipients"&gt;
&lt;h3&gt;Top 20 Recipients&lt;/h3&gt;
&lt;p&gt;To produce a &lt;strong&gt;Top 20 Recipients&lt;/strong&gt; bar graph with the default Plotly
formatting, I can execute the following SQL from Python and massage the
results a bit to create a graph in just a few lines of code:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;table class="highlighttable"&gt;&lt;tr&gt;&lt;td class="linenos"&gt;&lt;div class="linenodiv"&gt;&lt;pre&gt;&lt;span class="normal"&gt; 0&lt;/span&gt;
&lt;span class="normal"&gt; 1&lt;/span&gt;
&lt;span class="normal"&gt; 2&lt;/span&gt;
&lt;span class="normal"&gt; 3&lt;/span&gt;
&lt;span class="normal"&gt; 4&lt;/span&gt;
&lt;span class="normal"&gt; 5&lt;/span&gt;
&lt;span class="normal"&gt; 6&lt;/span&gt;
&lt;span class="normal"&gt; 7&lt;/span&gt;
&lt;span class="normal"&gt; 8&lt;/span&gt;
&lt;span class="normal"&gt; 9&lt;/span&gt;
&lt;span class="normal"&gt;10&lt;/span&gt;
&lt;span class="normal"&gt;11&lt;/span&gt;
&lt;span class="normal"&gt;12&lt;/span&gt;
&lt;span class="normal"&gt;13&lt;/span&gt;
&lt;span class="normal"&gt;14&lt;/span&gt;
&lt;span class="normal"&gt;15&lt;/span&gt;
&lt;span class="normal"&gt;16&lt;/span&gt;
&lt;span class="normal"&gt;17&lt;/span&gt;
&lt;span class="normal"&gt;18&lt;/span&gt;
&lt;span class="normal"&gt;19&lt;/span&gt;
&lt;span class="normal"&gt;20&lt;/span&gt;
&lt;span class="normal"&gt;21&lt;/span&gt;
&lt;span class="normal"&gt;22&lt;/span&gt;
&lt;span class="normal"&gt;23&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;div&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;plotly.offline&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nn"&gt;py&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;plotly.graph_objs&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nn"&gt;go&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;sqlite3&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;collections&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;OrderedDict&lt;/span&gt;

&lt;span class="n"&gt;conn&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;sqlite3&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;connect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;/path/to/email.db&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;c&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;conn&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;cursor&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;execute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;&amp;#39;&amp;#39;SELECT address, COUNT(r.message_key) AS message_count&lt;/span&gt;
&lt;span class="s1"&gt;    FROM recipients AS r&lt;/span&gt;
&lt;span class="s1"&gt;    LEFT JOIN messages AS m ON(m.message_key = r.message_key)&lt;/span&gt;
&lt;span class="s1"&gt;    WHERE m.gmail_labels LIKE &amp;#39;%Sent%&amp;#39;&lt;/span&gt;
&lt;span class="s1"&gt;    GROUP BY address&lt;/span&gt;
&lt;span class="s1"&gt;    ORDER BY message_count DESC&lt;/span&gt;
&lt;span class="s1"&gt;    LIMIT ?&amp;#39;&amp;#39;&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;,))&lt;/span&gt;

&lt;span class="n"&gt;addresses&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;OrderedDict&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;row&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;fetchall&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="n"&gt;addresses&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;row&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="n"&gt;row&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;py&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;plot&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="n"&gt;go&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Bar&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;addresses&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;keys&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;addresses&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;values&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
&lt;span class="p"&gt;])&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/div&gt;
&lt;p&gt;One thing that I discovered while working on this code: a typical Python
&lt;tt class="docutils literal"&gt;dict&lt;/tt&gt; is unordered. Luckily, there is a &lt;a class="reference external" href="https://docs.python.org/2/library/collections.html#collections.OrderedDict"&gt;collections.OrderedDict&lt;/a&gt;
object that respects the order of the SQL query results (&lt;strong&gt;Lines 16-18&lt;/strong&gt;).
With that data in a dict, all Plotly needs is the the X and Y lists
(&lt;strong&gt;Lines 20-23&lt;/strong&gt;) to produce a simple, &lt;em&gt;interactive&lt;/em&gt; graph! And a good
deal of Javascript, of course...&lt;/p&gt;
&lt;div class="overflow-x-container"&gt;
    &lt;div id="210e8275-0ea9-4ea3-80a4-9f3313d0f633" style="height: 100%; width: 100%;" class="plotly-graph-div"&gt;
        &lt;img src="/static/images/2016/12yog/top-recipients.png" alt="Top Recipients" class="no-js-remove" /&gt;
    &lt;/div&gt;
&lt;/div&gt;&lt;p&gt;Note that the resulting graph uses anonymized email addresses. This is
handled by the code, but not something I will be discussing quite yet as
I want to look in to some other ways to do this. The method used for
these graphs is available to view in &lt;a class="reference external" href="https://github.com/cdubz/takeout-inspector/blob/b6f5de98d383593bfe940c428edf687dc633aebb/takeout_inspector/mail.py"&gt;commit b6f5de9&lt;/a&gt;.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="top-20-senders"&gt;
&lt;h3&gt;Top 20 Senders&lt;/h3&gt;
&lt;p&gt;Similarly, a &lt;strong&gt;Top 20 Senders&lt;/strong&gt; bar graph is simple to create from the
&lt;tt class="docutils literal"&gt;messages&lt;/tt&gt; table using the same code as above and this SQL query:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;table class="highlighttable"&gt;&lt;tr&gt;&lt;td class="linenos"&gt;&lt;div class="linenodiv"&gt;&lt;pre&gt;&lt;span class="normal"&gt;0&lt;/span&gt;
&lt;span class="normal"&gt;1&lt;/span&gt;
&lt;span class="normal"&gt;2&lt;/span&gt;
&lt;span class="normal"&gt;3&lt;/span&gt;
&lt;span class="normal"&gt;4&lt;/span&gt;
&lt;span class="normal"&gt;5&lt;/span&gt;
&lt;span class="normal"&gt;6&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;div&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;`&lt;/span&gt;&lt;span class="k"&gt;from&lt;/span&gt;&lt;span class="o"&gt;`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;COUNT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;message_key&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;AS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;message_count&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;messages&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;gmail_labels&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;NOT&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;LIKE&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;%Sent%&amp;#39;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;AND&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;gmail_labels&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;NOT&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;LIKE&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;%Chat%&amp;#39;&lt;/span&gt;
&lt;span class="k"&gt;GROUP&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;BY&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;`&lt;/span&gt;&lt;span class="k"&gt;from&lt;/span&gt;&lt;span class="o"&gt;`&lt;/span&gt;
&lt;span class="k"&gt;ORDER&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;BY&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;message_count&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;DESC&lt;/span&gt;
&lt;span class="k"&gt;LIMIT&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;20&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/div&gt;
&lt;div class="overflow-x-container"&gt;
    &lt;div id="8b1867b3-4885-4081-b111-75e981abed80" style="height: 100%; width: 100%;" class="plotly-graph-div"&gt;
        &lt;img src="/static/images/2016/12yog/top-senders.png" alt="Top Senders" class="no-js-remove" /&gt;
    &lt;/div&gt;
&lt;/div&gt;&lt;div class="center section" id="continue-on-to-part-3-finishing-touches"&gt;
&lt;h4&gt;&lt;a class="reference external" href="https://www.chris-wells.net/articles/2016/11/12/12-years-gmail-3-finishing-touches"&gt;Continue on to Part 3: Finishing Touches...&lt;/a&gt;&lt;/h4&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
</content><category term="Technology"></category><category term="12 years of gmail"></category><category term="mailbox"></category><category term="graphing"></category><category term="plotly"></category><category term="python"></category><category term="sqlite"></category><category term="takeout inspector"></category></entry><entry><title>12 Years of Gmail, Part 1: Google Takeout</title><link href="https://www.chris-wells.net/articles/2016/10/28/12-years-gmail-1-google-takeout" rel="alternate"></link><published>2016-10-28T20:00:00-04:00</published><updated>2016-11-20T10:15:00-05:00</updated><author><name>Christopher Charbonneau Wells</name></author><id>tag:www.chris-wells.net,2016-10-28:/articles/2016/10/28/12-years-gmail-1-google-takeout</id><summary type="html">
&lt;p&gt;I have been slowly migrating off of a Gmail email address for a couple
of months now - I established this domain, selected an email provider,
set up SPF, DMARC, etc. and finally created myself a new email address.
I updated the address in all of the obvious places, but still found
myself using Gmail frequently to keep up. At some point I realized that
the only way to finish the migration would be to do something with all
the email I had hoarded away in Gmail.&lt;/p&gt;
</summary><content type="html">&lt;blockquote class="small"&gt;
    &lt;em&gt;
        This post is part of my series, &lt;a href="/tag/12-years-of-gmail"&gt;12 Years of Gmail&lt;/a&gt;,
        taking a look at the data Google has accumulated on me over the past
        12 years of using various Google services and documenting the
        learning experience developing an open source Python project
        (&lt;a href="https://github.com/cdubz/takeout-inspector"&gt;Takeout Inspector&lt;/a&gt;)
        to analyze that data.
    &lt;/em&gt;
&lt;/blockquote&gt;
&lt;p&gt;I have been slowly migrating off of a Gmail email address for a couple
of months now - I established this domain, selected an email provider,
set up SPF, DMARC, etc. and finally created myself a new email address.
I updated the address in all of the obvious places, but still found
myself using Gmail frequently to keep up. At some point I realized that
the only way to finish the migration would be to do something with all
the email I had hoarded away in Gmail.&lt;/p&gt;

&lt;p&gt;When I made the transition &lt;em&gt;to&lt;/em&gt; Gmail (from a mail server in my
basement) back in 2004, I found some tool that pulled all my existing
email in to Gmail using POP. So, I thought to myself in 2016, I'll just
do that again! I fired up Thunderbird, set up Gmail POP access and
started downloading. At some point, thousands of emails in, I decided to
check just how many emails I had in Gmail - a little over 30,000.
Instead of downloading &lt;em&gt;all&lt;/em&gt; of that, I went hunting through the emails
and deleted stuff I really didn't need (newsletters, test emails, spam,
etc.). It was a nice little trip down memory lane! I managed to cut the
total in half, but still wanted a better way to get it all downloaded.&lt;/p&gt;
&lt;p&gt;Enter &lt;a class="reference external" href="https://takeout.google.com/settings/takeout"&gt;Google Takeout&lt;/a&gt;. When signed in to Google, this service allows a
plethora of data to be exported from an account - mail, contacts, Chrome
settings, Hangouts chat history, location history, etc. I ticked the
&lt;strong&gt;Mail&lt;/strong&gt; option and was told I would receive an email when my archive
was ready for download.&lt;/p&gt;
&lt;p class="image-box center"&gt;&lt;img alt="Google Takeout" src="/static/images/2016/12yog/google-takeout-mail.png" /&gt; &lt;em&gt;Google Takeout data selection screen.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;A few hours later I received the email alert, downloaded the zipped file
and uncompressed it to find a 3.5GB &lt;a class="reference external" href="https://en.wikipedia.org/wiki/Mbox"&gt;mbox mail file&lt;/a&gt;. I could import
this in to something like Thunderbird (or any other mail client) and
have it to refer to, or find some way to import to my new email
provider.&lt;/p&gt;
&lt;blockquote&gt;
However, what ultimately interested me is just how much data is in
this little file - 12 years of my communications on the Internet.&lt;/blockquote&gt;
&lt;p&gt;I started the account back when I was 20 years old and imported email
going back even further in to my teens!&lt;/p&gt;
&lt;p&gt;There could be some interesting aggregate information in all this email,
such as which emails and domains I communicate with most frequently,
when I send and receive emails, what are the most common words I use in
communication, etc. etc. I spent a bit of time poking around GitHub
looking for projects that may already do this sort of thing, but didn't
find anything of particular interest. So I decided this would be a great
excuse to dip my toes back in to Python!&lt;/p&gt;
&lt;p&gt;I worked through &lt;a class="reference external" href="https://learnpythonthehardway.org/book/"&gt;Learn Python the Hard Way&lt;/a&gt; a year or so ago and found
that I really enjoyed the language. The code structure and syntax is
much less cluttered than PHP (where most of my experience is), there is
great documentation, lots of easy-to-use packages and it just seemed
more fun to write. This experience felt much more like my earlier years
learning about HTML and PHP development on &lt;a class="reference external" href="https://web.archive.org/web/20020720172634/http://devshed.com/"&gt;DevShed&lt;/a&gt;. Ultimately, I
didn't have a good project or time to devote to keeping it up so most of
that knowledge has slipped away.&lt;/p&gt;
&lt;p&gt;As I began working my way around the Mail export, I realized that other
elements of Google Takeout - Chrome data, Hangouts and Location History,
for example - may also be very interesting to poke around in (and I also
greatly regretted my decision to delete half of my email &lt;em&gt;before&lt;/em&gt; doing
the Mail export!).&lt;/p&gt;
&lt;p&gt;&lt;a class="reference external" href="https://github.com/cdubz/takeout-inspector"&gt;&lt;strong&gt;Takeout Inspector&lt;/strong&gt;&lt;/a&gt; will be where I capture the code I work on to
this end and hopefully I can come up with some fairly interesting
information and insights along the way to capture in this series.&lt;/p&gt;
&lt;div class="center section" id="continue-on-to-part-2-bootstrapping"&gt;
&lt;h2&gt;&lt;a class="reference external" href="https://www.chris-wells.net/articles/2016/11/08/12-years-gmail-2-bootstrapping"&gt;Continue on to Part 2: Bootstrapping...&lt;/a&gt;&lt;/h2&gt;
&lt;/div&gt;
</content><category term="Technology"></category><category term="12 years of gmail"></category><category term="email"></category><category term="google takeout"></category><category term="python"></category><category term="takeout inspector"></category></entry></feed>