<?xml version="1.0" encoding="utf-8"?>
<?xml-stylesheet type="text/xsl" href="../assets/xml/rss.xsl" media="all"?><rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>chris' random ramblings (Posts about python)</title><link>https://atlee.ca/</link><description></description><atom:link href="https://atlee.ca/categories/python.xml" rel="self" type="application/rss+xml"></atom:link><language>en</language><lastBuildDate>Sat, 22 Feb 2025 20:04:31 GMT</lastBuildDate><generator>Nikola (getnikola.com)</generator><docs>http://blogs.law.harvard.edu/tech/rss</docs><item><title>Learning Ruby as an experienced Python developer</title><link>https://atlee.ca/posts/learning-ruby-as-an-experienced-python-programmer/</link><dc:creator>chris</dc:creator><description>&lt;p&gt;As I mentioned in my &lt;a href="https://atlee.ca/posts/hello-world-2022/"&gt;previous post&lt;/a&gt;, in my new role at
Shopify, I've been doing backend development in Ruby. Previously I had been
working nearly exclusively with Python for over ten years, so I was a bit
nervous about the move.&lt;/p&gt;
&lt;p&gt;In addition to my regular work, I also tried to solve other types of
problems using Ruby. &lt;a href="https://adventofcode.com/"&gt;Advent of code&lt;/a&gt; was a
really great way to learn a new language.&lt;/p&gt;
&lt;p&gt;After nearly two years in the new role, I'd like to share some of my
experiences, hopefully as a way to help and encourage others who would
like to learn a new language, or are worried about moving into a new role,
but don't know some specific technology.&lt;/p&gt;
&lt;h2 id="early-pains"&gt;Early pains&lt;/h2&gt;
&lt;p&gt;The first few weeks with Ruby were pretty tough. Luckily, Ruby shares some
similarities with Python that make it a bit more approachable at first
glance:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Dynamically typed, interpreted language&lt;/li&gt;
&lt;li&gt;Class / method syntax is similar&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="method-calls-dont-need"&gt;Method calls don't need ()&lt;/h3&gt;
&lt;p&gt;One of the first things that tripped me up was not understanding that using
() to call a method is not required in Ruby.&lt;/p&gt;
&lt;div class="code"&gt;&lt;pre class="code literal-block"&gt;&lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;greet&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nb"&gt;puts&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Hello, world!"&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="n"&gt;greet&lt;/span&gt;
&lt;span class="c1"&gt;# =&amp;gt; "Hello, world!"&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;In fact, () are optional when passing arguments as well!&lt;/p&gt;
&lt;div class="code"&gt;&lt;pre class="code literal-block"&gt;&lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;greet&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nb"&gt;puts&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Hello, &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="nb"&gt;name&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="n"&gt;greet&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Chris"&lt;/span&gt;
&lt;span class="c1"&gt;# =&amp;gt; "Hello, Chris"&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;This can be used to build some very nice DSL features, resulting in code
that is much more readable. e.g. Rail's
&lt;a href="https://devdocs.io/rails~7.0/module#method-i-delegate"&gt;delegate&lt;/a&gt; method&lt;/p&gt;
&lt;div class="code"&gt;&lt;pre class="code literal-block"&gt;&lt;span class="nb"&gt;require&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"rails"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;# to get delegate&lt;/span&gt;
&lt;span class="k"&gt;class&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nc"&gt;Message&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;greet&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nb"&gt;puts&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Hello!"&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nc"&gt;Greeter&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;delegate&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;:greet&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;to&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;:message&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="kp"&gt;attr_accessor&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;:message&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;initialize&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="vi"&gt;@message&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="no"&gt;Message&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;new&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="no"&gt;Greeter&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;new&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;greet&lt;/span&gt;
&lt;span class="c1"&gt;# =&amp;gt; "Hello!"&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;

&lt;h3 id="implicit-return"&gt;Implicit return&lt;/h3&gt;
&lt;p&gt;Like Rust, the result of the last expression in Ruby is used as the return
value of a function.&lt;/p&gt;
&lt;div class="code"&gt;&lt;pre class="code literal-block"&gt;&lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;add_one&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="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;x&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="mi"&gt;1&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="n"&gt;add_one&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="c1"&gt;# =&amp;gt; 3&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;

&lt;h3 id="strings-symbols"&gt;Strings &amp;amp; Symbols&lt;/h3&gt;
&lt;p&gt;Ruby has a concept called &lt;a href="https://devdocs.io/ruby~3.1/symbol"&gt;symbols&lt;/a&gt;,
which are a kind of identifier using the &lt;code&gt;:&lt;/code&gt; prefix. They're often used to
reference method names, since you can't get a reference to a method just by
accessing it by name like in Python. E.g. &lt;code&gt;obj.foo&lt;/code&gt; will &lt;em&gt;call&lt;/em&gt; the &lt;code&gt;foo&lt;/code&gt;
method on &lt;code&gt;obj&lt;/code&gt; in Ruby, whereas it will give you a reference to the &lt;code&gt;foo&lt;/code&gt;
method in Python. The equivalent in Ruby would be &lt;code&gt;obj.method(:foo)&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;Symbols are also used for named parameters in method calls. E.g.&lt;/p&gt;
&lt;div class="code"&gt;&lt;pre class="code literal-block"&gt;&lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;greet&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;message&lt;/span&gt;&lt;span class="p"&gt;:)&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nb"&gt;puts&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Hello &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="n"&gt;greet&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;message&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"world!"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="c1"&gt;# =&amp;gt; "Hello world!"&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;However, where symbols presented me with the steepest learning curve is how
they're handled in Hash (i.e. dict) literals.&lt;/p&gt;
&lt;div class="code"&gt;&lt;pre class="code literal-block"&gt;&lt;span class="n"&gt;a&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="s2"&gt;"key"&lt;/span&gt;
&lt;span class="n"&gt;h&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="ss"&gt;a&lt;/span&gt;&lt;span class="p"&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;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;a&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;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="c1"&gt;# =&amp;gt; { :a=&amp;gt;1, "key"=&amp;gt;2 }&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;It's extremely easy to get the two ways of defining a value mixed up. I've
wasted an embarrassing number of hours on bugs caused by mixing up strings
and symbols as the keys in hashes.&lt;/p&gt;
&lt;h3 id="range-expressions"&gt;Range expressions&lt;/h3&gt;
&lt;p&gt;Ruby has nice built-in syntax for &lt;a href="https://devdocs.io/ruby~3.1/range"&gt;ranges&lt;/a&gt;:&lt;/p&gt;
&lt;div class="code"&gt;&lt;pre class="code literal-block"&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="o"&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;to_a&lt;/span&gt;
&lt;span class="c1"&gt;# =&amp;gt; [0, 1, 2, 3, 4, 5]&lt;/span&gt;
&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="o"&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;to_a&lt;/span&gt;
&lt;span class="c1"&gt;# =&amp;gt; [0, 1, 2, 3, 4]&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;These are super convenient, but I almost always forget which form is
inclusive and which is exclusive.&lt;/p&gt;
&lt;h2 id="a-ha-moments"&gt;A-ha! moments&lt;/h2&gt;
&lt;h3 id="blocks"&gt;Blocks&lt;/h3&gt;
&lt;p&gt;Before really learning Ruby, I remember trying to read up on what blocks
were...and not really getting it.&lt;/p&gt;
&lt;p&gt;The best way I can come up with to explain them now is that they're a kind
of anonymous function / closure.&lt;/p&gt;
&lt;p&gt;Part of my confusion was not understanding that there are a few ways of
defining and calling blocks.&lt;/p&gt;
&lt;p&gt;These are equivalent:&lt;/p&gt;
&lt;div class="code"&gt;&lt;pre class="code literal-block"&gt;&lt;span class="n"&gt;mymap&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="o"&gt;...&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;do&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;x&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="mi"&gt;2&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="c1"&gt;# is the same as&lt;/span&gt;

&lt;span class="n"&gt;mymap&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="o"&gt;...&lt;/span&gt;&lt;span class="mi"&gt;5&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="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;x&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="mi"&gt;2&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;The block's arguments are passed via the identifiers between the pipe (&lt;code&gt;|&lt;/code&gt;)
symbols.&lt;/p&gt;
&lt;p&gt;In both cases, the &lt;code&gt;mymap&lt;/code&gt; method is being passed a block, which in our
case gets executed once per element (but that's completely up to the
implementation of &lt;code&gt;mymap&lt;/code&gt;). The block can be named as an explicit
function parameter, and this could be written as:&lt;/p&gt;
&lt;div class="code"&gt;&lt;pre class="code literal-block"&gt;&lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;mymap&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;obj&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;block&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;result&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="o"&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;&lt;span class="n"&gt;i&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="n"&gt;obj&lt;/span&gt;
&lt;span class="w"&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;push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;block&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;call&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;The block can also be passed implicitly (check using the
&lt;a href="https://devdocs.io/ruby~3.1/kernel#method-i-block_given-3F"&gt;&lt;code&gt;block_given?&lt;/code&gt;&lt;/a&gt;
method) and called via &lt;code&gt;yield&lt;/code&gt;:&lt;/p&gt;
&lt;div class="code"&gt;&lt;pre class="code literal-block"&gt;&lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;mymap&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;obj&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;result&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="o"&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;&lt;span class="n"&gt;i&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="n"&gt;obj&lt;/span&gt;
&lt;span class="w"&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;push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;yield&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Once I wrapped my head around blocks, I found them to be very useful!&lt;/p&gt;
&lt;h3 id="idiom"&gt;"&amp;amp;:" idiom&lt;/h3&gt;
&lt;p&gt;Ruby has this really neat shorthand for creating a block that calls a
method on an object: "&lt;code&gt;&amp;amp;:&lt;/code&gt;". It's used like this:&lt;/p&gt;
&lt;div class="code"&gt;&lt;pre class="code literal-block"&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"hello"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"world"&lt;/span&gt;&lt;span class="o"&gt;].&lt;/span&gt;&lt;span class="n"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="ss"&gt;:upcase&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="c1"&gt;# =&amp;gt; ["HELLO", "WORLD"]&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;There are a few things going on here:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;:upcase&lt;/code&gt; is a Symbol referring to the &lt;code&gt;upcase&lt;/code&gt; method on the object&lt;/li&gt;
&lt;li&gt;&lt;code&gt;&amp;amp;&lt;/code&gt; tries to convert its argument to a kind of closure using the
  argument's &lt;code&gt;.to_proc&lt;/code&gt; method.
  &lt;a href="https://devdocs.io/ruby~3.1/symbol#method-i-to_proc"&gt;&lt;code&gt;Symbol#to_proc&lt;/code&gt;&lt;/a&gt; returns a closure that
  calls the given method on the passed in object.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The net result is something equivalent to&lt;/p&gt;
&lt;div class="code"&gt;&lt;pre class="code literal-block"&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"hello"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"world"&lt;/span&gt;&lt;span class="o"&gt;].&lt;/span&gt;&lt;span class="n"&gt;map&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="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:upcase&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="c1"&gt;# OR&lt;/span&gt;
&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"hello"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"world"&lt;/span&gt;&lt;span class="o"&gt;].&lt;/span&gt;&lt;span class="n"&gt;map&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="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;upcase&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Brian Storti explains this in much more detail in &lt;a href="https://www.brianstorti.com/understanding-ruby-idiom-map-with-symbol/"&gt;his blog post&lt;/a&gt;.&lt;/p&gt;
&lt;h3 id="enumeration"&gt;Enumeration&lt;/h3&gt;
&lt;p&gt;Ruby has fantastic enumeration primitives built in, just checkout the
&lt;a href="https://devdocs.io/ruby~3.1/enumerable"&gt;Enumerable module&lt;/a&gt;. Most of the
basic container types in Ruby support &lt;code&gt;Enumerable&lt;/code&gt;; when combined with blocks,
this makes filtering and transforming data in Ruby a joy.&lt;/p&gt;
&lt;p&gt;It also fits my brain better than Python's generator / comprehension
expressions. When writing Python code to transform data, I often found that
I was writing some code, then backing the cursor up to the beginning of the
line.&lt;/p&gt;
&lt;p&gt;In Ruby the data and logic flow from left to right, which makes it easy to
chain thing together.&lt;/p&gt;
&lt;div class="code"&gt;&lt;pre class="code literal-block"&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="o"&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;map&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="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;x&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="mi"&gt;2&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;In Python the logic is on left but the data is on the right.&lt;/p&gt;
&lt;div class="code"&gt;&lt;pre class="code literal-block"&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;2&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nb"&gt;range&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;p&gt;If I want to get only the square numbers that are even, I would simply
add this in Ruby:&lt;/p&gt;
&lt;div class="code"&gt;&lt;pre class="code literal-block"&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="o"&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;map&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="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;x&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="mi"&gt;2&lt;/span&gt;&lt;span class="w"&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;filter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="ss"&gt;:even?&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Whereas in Python I would need to introduce more code before/after the
expression I already have to achieve the same result:&lt;/p&gt;
&lt;div class="code"&gt;&lt;pre class="code literal-block"&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;y&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt; &lt;span class="ow"&gt;in&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;2&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nb"&gt;range&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="k"&gt;if&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;2&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;/pre&gt;&lt;/div&gt;

&lt;h2 id="tldr"&gt;tl;dr&lt;/h2&gt;
&lt;p&gt;Ruby is a really nice language. I'm glad I've had the opportunity to learn
it!&lt;/p&gt;
&lt;p&gt;Python has a philosophy of &lt;a href="https://peps.python.org/pep-0020/"&gt;"There should be one-- and preferably only one
--obvious way to do it."&lt;/a&gt;. I think this
helps with Python's readability at the cost of expressibility, elegance,
and conciseness in some cases.&lt;/p&gt;
&lt;p&gt;In Ruby there are often multiple ways of achieving the same thing. It has a
richer vocabulary for expressing ideas in code. This richness allows for
more elegant code in many cases, although this perhaps requires more effort
on the part of the reader to understand the code.&lt;/p&gt;</description><category>python</category><category>ruby</category><category>shopify</category><guid>https://atlee.ca/posts/learning-ruby-as-an-experienced-python-programmer/</guid><pubDate>Tue, 13 Sep 2022 09:20:38 GMT</pubDate></item><item><title>PyCon Canada 2018</title><link>https://atlee.ca/posts/pycon-canada-2018/</link><dc:creator>chris</dc:creator><description>&lt;p&gt;I've very happy to have had the opportunity to attend and speak at &lt;a href="https://2018.pycon.ca/"&gt;PyCon
Canada&lt;/a&gt; here in Toronto last week.&lt;/p&gt;
&lt;p&gt;PyCon has always been a very well organized conference. There are a wide
range of talks available, even on topics not directly related to Python.
I've attended previous PyCon events in the past, but never the Canadian
one!&lt;/p&gt;
&lt;p&gt;My talk was titled &lt;a href="https://2018.pycon.ca/talks/talk-PC-55508/"&gt;How Mozilla uses Python to Build and Ship
Firefox.&lt;/a&gt; The
slides are available &lt;a href="https://mzl.la/2yUhiqH"&gt;here&lt;/a&gt; if you're interested. I
believe the sessions were recorded, but they're not yet available online. I
was happy with the attendance at the session, and the questions during and
after the talk.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://twitter.com/alexsnurnikov/status/1061302031246376961"&gt;&lt;img src="https://atlee.ca/posts/biggraph.png"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;As part of the talk, I mentioned how Release Engineering is
a very distributed team. Afterwards, many people had followup questions
about how to work effectively with remote teams, which gave me a great
opportunity to recommend John O'Duinn's new book, &lt;a href="https://www.amazon.com/Distributed-Teams-Practice-Together-Physically/dp/1732254907/"&gt;Distributed
Teams&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Some other highlights from the conference:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="https://2018.pycon.ca/talks/talk-PC-55249/"&gt;CircuitPython: Python on
  hardware&lt;/a&gt;
  I really enjoyed learning about
  &lt;a href="https://www.adafruit.com/circuitpython"&gt;CircuitPython&lt;/a&gt;, and the work
  that &lt;a href="https://www.adafruit.com/"&gt;Adafruit&lt;/a&gt; is doing to make programming
  and electronics more accessible.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="https://2018.pycon.ca/talks/talk-PC-53179/"&gt;Using Python to find Russian Twitter troll tweets aimed at
  Canada&lt;/a&gt;
  A really interesting dive into 3 million tweets that
  &lt;a href="https://fivethirtyeight.com/features/why-were-sharing-3-million-russian-troll-tweets/"&gt;FiveThirtyEight&lt;/a&gt;
  made available for analysis.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="https://2018.pycon.ca/talks/talk-PC-55520/"&gt;PEP 572: The Walrus
  Operator&lt;/a&gt;
  My favourite quote from the talk: "Dictators are people too!"
  If you haven't followed Python governance, Guido stepped down as BDFL
  (Benevolent Dictator for Life) after the PEP was resolved. Dustin focused
  much of his talk about how we in the Python community, and more generally
  in tech, need to treat each other better.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="https://2018.pycon.ca/talks/talk-PC-55520/"&gt;Who's There? Building a home security system with Pi &amp;amp;
  Slack&lt;/a&gt;
  A great example of how you can get started hacking on home automation
  with really simple tools.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Froilán Irzarry's Keynote talk on the second day was really impressive.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="https://2018.pycon.ca/talks/talk-PC-52228/"&gt;You Don't Need That!&lt;/a&gt;
  Design patterns in Python
  My main takeaway from this was that you shouldn't try and write Python
  code as if it were Java or C++ :) Python has plenty of language features
  built-in that make many classic design patterns unnecessary or trivial to
  implement.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="https://2018.pycon.ca/talks/talk-PC-53293/"&gt;Numpy to PyTorch&lt;/a&gt;
  Really neat to learn about PyTorch, and leveraging the GPU to accelerate
  computation.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="https://2018.pycon.ca/talks/talk-PC-53944/"&gt;Flying Python - A reverse engineering dive into Python
  performance&lt;/a&gt;
  Made me want to investigate &lt;a href="https://wiki.mozilla.org/Balrog"&gt;Balrog&lt;/a&gt;
  performance, and also look at ways we can improve Python startup time.
  Some neat tips about examining disassembled Python bytecode.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="https://2018.pycon.ca/talks/talk-PC-53735/"&gt;Working with Useless
  Machines&lt;/a&gt;
  Hilarious talk about (ab)using IoT devices.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="https://2018.pycon.ca/talks/talk-PC-52797/"&gt;Gathering Related Functionality: Patterns for Clean API
  Design&lt;/a&gt;
  I really liked his approach for creating clean APIs for things like class
  constructors. He introduced a module called
  &lt;a href="https://variants.readthedocs.io/en/latest/"&gt;variants&lt;/a&gt; which lets you
  write variants of a function / class initializer to support varying types
  of parameters. For example, a common pattern is to have a function that
  takes either a string path to a file, or a file object. Instead of having
  one function that supports both types of arguments, variants allows you
  to make distinct functions for each type, but in a way that makes it
  easy to share underlying functionality and also not clutter your
  namespace.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;</description><category>mozilla</category><category>pycon</category><category>python</category><category>releng</category><guid>https://atlee.ca/posts/pycon-canada-2018/</guid><pubDate>Tue, 20 Nov 2018 19:20:02 GMT</pubDate></item><item><title>PyCon 2016 report</title><link>https://atlee.ca/posts/pycon-2016-report/</link><dc:creator>chris</dc:creator><description>&lt;p&gt;I had the opportunity to spend last week in Portland for &lt;a class="reference external" href="https://us.pycon.org/2016/"&gt;PyCon 2016&lt;/a&gt;. I'd like to share some of my thoughts and
some pointers to good talks I was able to attend. The full schedule can be
found &lt;a class="reference external" href="https://us.pycon.org/2016/schedule/talks/"&gt;here&lt;/a&gt; and all the
videos are &lt;a class="reference external" href="https://www.youtube.com/channel/UCwTD5zJbsQGJN75MwbykYNw/videos"&gt;here&lt;/a&gt;.&lt;/p&gt;
&lt;section id="monday"&gt;
&lt;h2&gt;Monday&lt;/h2&gt;
&lt;p&gt;Brandon Rhodes' &lt;cite&gt;Welcome to PyCon&lt;/cite&gt; was one of the best introductions to a
conference I've ever seen. Unfortunately I can't find a link to a
recording...
What I liked about it was that he made
everyone feel very welcome to PyCon and to Portland. He explained some of
the simple (but important!) practical details like where to find the
conference rooms, how to take transit, etc. He noted that for the first
time, they have live transcriptions of the talks being done and put up on
screens beside the speaker slides for the hearing impaired.&lt;/p&gt;
&lt;p&gt;He also emphasized the importance of keeping questions &lt;em&gt;short&lt;/em&gt; during Q&amp;amp;A
after the regular sessions. "Please form your question in the form of a
question." I've been to way too many Q&amp;amp;A sessions where the person asking
the question took the opportunity to go off on a long, unrelated tangent. For
the most part, this advice was followed at PyCon: I didn't see very many
long winded questions or statements during Q&amp;amp;A sessions.&lt;/p&gt;
&lt;section id="machete-mode-debugging"&gt;
&lt;h3&gt;Machete-mode Debugging&lt;/h3&gt;
&lt;p&gt;(&lt;a class="reference external" href="https://us.pycon.org/2016/schedule/presentation/1658/"&gt;abstract&lt;/a&gt;;
&lt;a class="reference external" href="https://www.youtube.com/watch?v=5XvAVgcbmdY"&gt;video&lt;/a&gt;)&lt;/p&gt;
&lt;p&gt;Ned Batchelder gave this great talk about using python's language features
to debug problematic code. He ran through several examples of tricky
problems that could come up, and how to use things like monkey patching
and the debug trace hook to find out where the problem is. One piece of
advice I liked was when he said that it doesn't matter how ugly the code
is, since it's only going to last 10 minutes. The point is the get the
information you need out of the system the easiest way possible, and then
you can undo your changes.&lt;/p&gt;
&lt;/section&gt;
&lt;section id="refactoring-python"&gt;
&lt;h3&gt;Refactoring Python&lt;/h3&gt;
&lt;p&gt;(&lt;a class="reference external" href="https://us.pycon.org/2016/schedule/presentation/2073/"&gt;abstract&lt;/a&gt;;
&lt;a class="reference external" href="https://www.youtube.com/watch?v=d46PjvFki38"&gt;video&lt;/a&gt;)&lt;/p&gt;
&lt;p&gt;I found this session pretty interesting. We certainly have lots of code
that needs refactoring!&lt;/p&gt;
&lt;/section&gt;
&lt;section id="security-with-object-capabilities"&gt;
&lt;h3&gt;Security with object-capabilities&lt;/h3&gt;
&lt;p&gt;(&lt;a class="reference external" href="https://us.pycon.org/2016/schedule/presentation/2248/"&gt;abstract&lt;/a&gt;;
&lt;a class="reference external" href="https://www.youtube.com/watch?v=OS94twkD74s"&gt;video&lt;/a&gt;;
&lt;a class="reference external" href="https://smpfle21zb7r5nnat5uq.oasis.sandstorm.io/index.html#/"&gt;slides&lt;/a&gt;)&lt;/p&gt;
&lt;p&gt;I found this interesting, but a little too theoretical. Object
capabilities are a completely orthogonal way to access control lists as a
way model security and permissions. It was hard for me to see how we could
apply this to the systems we're building.&lt;/p&gt;
&lt;/section&gt;
&lt;section id="awaken-your-home"&gt;
&lt;h3&gt;Awaken your home&lt;/h3&gt;
&lt;p&gt;(&lt;a class="reference external" href="https://us.pycon.org/2016/schedule/presentation/1752/"&gt;abstract&lt;/a&gt;;
&lt;a class="reference external" href="https://www.youtube.com/watch?v=DJpNmDK_g6Y"&gt;video&lt;/a&gt;)&lt;/p&gt;
&lt;p&gt;A really cool intro to the &lt;a class="reference external" href="https://home-assistant.io/"&gt;Home Assistant&lt;/a&gt;
project, which integrates all kinds of IoT type things in your home. E.g.
Nest, Sonos, IFTTT, OpenWrt, light bulbs, switches, automatic sprinkler
systems. I'm definitely going to give this a try once I free up my
&lt;a class="reference external" href="http://raspberrypi.org/"&gt;raspberry pi&lt;/a&gt;.&lt;/p&gt;
&lt;/section&gt;
&lt;section id="finding-closure-with-closures"&gt;
&lt;h3&gt;Finding closure with closures&lt;/h3&gt;
&lt;p&gt;(&lt;a class="reference external" href="https://us.pycon.org/2016/schedule/presentation/2231/"&gt;abstract&lt;/a&gt;;
&lt;a class="reference external" href="https://www.youtube.com/watch?v=E9wS6LdXM8Y"&gt;video&lt;/a&gt;)&lt;/p&gt;
&lt;p&gt;A very entertaining session about closures in Python. Does Python even
have closures? (yes!)&lt;/p&gt;
&lt;/section&gt;
&lt;section id="life-cycle-of-a-python-class"&gt;
&lt;h3&gt;Life cycle of a Python class&lt;/h3&gt;
&lt;p&gt;(&lt;a class="reference external" href="https://us.pycon.org/2016/schedule/presentation/2074/"&gt;abstract&lt;/a&gt;;
&lt;a class="reference external" href="https://www.youtube.com/watch?v=kZtC_4Ecq1Y"&gt;video&lt;/a&gt;)&lt;/p&gt;
&lt;p&gt;Lots of good information about how classes work in Python, including some
details about meta-classes. I think I understand meta-classes better after
having attended this session. I still don't get descriptors though!&lt;/p&gt;
&lt;p&gt;(I hope Mike learns soon that &lt;code class="docutils literal"&gt;__new__&lt;/code&gt; is pronounced "dunder new" and not
"under under new"!)&lt;/p&gt;
&lt;/section&gt;
&lt;section id="deep-learning"&gt;
&lt;h3&gt;Deep learning&lt;/h3&gt;
&lt;p&gt;(&lt;a class="reference external" href="https://us.pycon.org/2016/schedule/presentation/2112/"&gt;abstract&lt;/a&gt;;
&lt;a class="reference external" href="https://www.youtube.com/watch?v=kVud83kqv30"&gt;video&lt;/a&gt;)&lt;/p&gt;
&lt;p&gt;Very good presentation about getting started with deep learning. There are
lots of great libraries and pre-trained neural networks out there to get
started with!&lt;/p&gt;
&lt;/section&gt;
&lt;section id="building-protocol-libraries-the-right-way"&gt;
&lt;h3&gt;Building protocol libraries the right way&lt;/h3&gt;
&lt;p&gt;(&lt;a class="reference external" href="https://us.pycon.org/2016/schedule/presentation/1743/"&gt;abstract&lt;/a&gt;;
&lt;a class="reference external" href="https://www.youtube.com/watch?v=7cC3_jGwl_U"&gt;video&lt;/a&gt;)&lt;/p&gt;
&lt;p&gt;I really enjoyed this talk. Cory Benfield describes the importance of
keeping a clean separation between your protocol parsing code, and your IO.
It not only makes things more testable, but makes code more reusable.
Nearly every HTTP library in the Python ecosystem needs to re-implement its
own HTTP parsing code, since all the existing code is tightly coupled to
the network IO calls.&lt;/p&gt;
&lt;/section&gt;
&lt;/section&gt;
&lt;section id="tuesday"&gt;
&lt;h2&gt;Tuesday&lt;/h2&gt;
&lt;section id="guido-s-keynote"&gt;
&lt;h3&gt;Guido's Keynote&lt;/h3&gt;
&lt;p&gt;(&lt;a class="reference external" href="https://www.youtube.com/watch?v=YgtL4S7Hrwo"&gt;video&lt;/a&gt;)&lt;/p&gt;
&lt;p&gt;Some interesting notes in here about the history of Python, and a look at
what's coming in 3.6.&lt;/p&gt;
&lt;/section&gt;
&lt;section id="click"&gt;
&lt;h3&gt;Click&lt;/h3&gt;
&lt;p&gt;(&lt;a class="reference external" href="https://us.pycon.org/2016/schedule/presentation/2223/"&gt;abstract&lt;/a&gt;;
&lt;a class="reference external" href="https://www.youtube.com/watch?v=SDyHLG2ltSY"&gt;video&lt;/a&gt;)&lt;/p&gt;
&lt;p&gt;An intro to the &lt;a class="reference external" href="http://click.pocoo.org/5/"&gt;click&lt;/a&gt; module for creating
beautiful command line interfaces.&lt;/p&gt;
&lt;p&gt;I like that click helps you to build &lt;em&gt;testable&lt;/em&gt; CLIs.&lt;/p&gt;
&lt;/section&gt;
&lt;section id="http-2-and-asynchronous-apis"&gt;
&lt;h3&gt;HTTP/2 and asynchronous APIs&lt;/h3&gt;
&lt;p&gt;(&lt;a class="reference external" href="https://us.pycon.org/2016/schedule/presentation/1737/"&gt;abstract&lt;/a&gt;;
&lt;a class="reference external" href="https://www.youtube.com/watch?v=Mou17XxYRZk"&gt;video&lt;/a&gt;)&lt;/p&gt;
&lt;p&gt;A good introduction to what HTTP/2 can do, and why it's such an
improvement over HTTP/1.x.&lt;/p&gt;
&lt;/section&gt;
&lt;section id="remote-calls-local-calls"&gt;
&lt;h3&gt;Remote calls != local calls&lt;/h3&gt;
&lt;p&gt;(&lt;a class="reference external" href="https://us.pycon.org/2016/schedule/presentation/2027/"&gt;abstract&lt;/a&gt;;
&lt;a class="reference external" href="https://www.youtube.com/watch?v=dY-SkuENZP8"&gt;video&lt;/a&gt;)&lt;/p&gt;
&lt;p&gt;Really good talk about failing gracefully. He covered some familiar topics
like adding timeouts and retries to things that can fail, but also
introduced to me the concept of &lt;a class="reference external" href="http://martinfowler.com/bliki/CircuitBreaker.html"&gt;circuit breakers&lt;/a&gt;. The idea with a circuit
breaker is to prevent talking to services you know are down. For example,
if you have failed to get a response from service X the past 5 times due
to timeouts or errors, then open the circuit breaker for a set amount of
time. Future calls to service X from your application will be intercepted,
and will fail early. This can avoid hammering a service while it's in an
error state, and works well in combination with timeouts and retries of course.&lt;/p&gt;
&lt;p&gt;I was thinking quite a bit about &lt;a class="reference external" href="http://hearsum.ca/blog/index.html"&gt;Ben's&lt;/a&gt; &lt;a class="reference external" href="https://pypi.python.org/pypi/redo"&gt;redo&lt;/a&gt; module during this talk. It's a
great module for handling retries!&lt;/p&gt;
&lt;/section&gt;
&lt;section id="diving-into-the-wreck"&gt;
&lt;h3&gt;Diving into the wreck&lt;/h3&gt;
&lt;p&gt;(&lt;a class="reference external" href="https://us.pycon.org/2016/schedule/presentation/2032/"&gt;abstract&lt;/a&gt;;
&lt;a class="reference external" href="https://www.youtube.com/watch?v=yHpy4Khx0Ds"&gt;video&lt;/a&gt;)&lt;/p&gt;
&lt;p&gt;A look into diagnosing performance problems in applications. Some neat
tools and techniques introduced here, but I felt he blamed the DB a little
too much :)&lt;/p&gt;
&lt;/section&gt;
&lt;/section&gt;
&lt;section id="wednesday"&gt;
&lt;h2&gt;Wednesday&lt;/h2&gt;
&lt;section id="magic-wormhole"&gt;
&lt;h3&gt;Magic Wormhole&lt;/h3&gt;
&lt;p&gt;(&lt;a class="reference external" href="https://us.pycon.org/2016/schedule/presentation/1838/"&gt;abstract&lt;/a&gt;;
&lt;a class="reference external" href="https://www.youtube.com/watch?v=dgnikoiau68"&gt;video&lt;/a&gt;;
&lt;a class="reference external" href="http://www.lothar.com/%7Ewarner/MagicWormhole-PyCon2016.pdf"&gt;slides&lt;/a&gt;)&lt;/p&gt;
&lt;p&gt;I didn't end up going to this talk, but I did have a chance to chat with
Brian before. &lt;a class="reference external" href="https://github.com/warner/magic-wormhole"&gt;magic-wormhole&lt;/a&gt; is a tool to safely transfer
files from one computer to another. Think scp, but without needing ssh
keys set up already, or direct network flows. Very neat tool!&lt;/p&gt;
&lt;/section&gt;
&lt;section id="computational-physics"&gt;
&lt;h3&gt;Computational Physics&lt;/h3&gt;
&lt;p&gt;(&lt;a class="reference external" href="https://us.pycon.org/2016/schedule/presentation/1711/"&gt;abstract&lt;/a&gt;;
&lt;a class="reference external" href="https://www.youtube.com/watch?v=NGVBo6JJa6M"&gt;video&lt;/a&gt;)&lt;/p&gt;
&lt;p&gt;How to do planetary orbit simulations in Python. Pretty interesting talk,
he introduced me to Feynman, and some of the important characteristics of
the simulation methods introduced.&lt;/p&gt;
&lt;/section&gt;
&lt;section id="small-batch-artisinal-bots"&gt;
&lt;h3&gt;Small batch artisinal bots&lt;/h3&gt;
&lt;p&gt;(&lt;a class="reference external" href="https://us.pycon.org/2016/schedule/presentation/2175/"&gt;abstract&lt;/a&gt;;
&lt;a class="reference external" href="https://www.youtube.com/watch?v=mizX7n2tx8k"&gt;video&lt;/a&gt;)&lt;/p&gt;
&lt;p&gt;Hilarious talk about building bots with Python. Definitely worth watching,
although unfortunately it's only a partial recording.&lt;/p&gt;
&lt;/section&gt;
&lt;section id="gilectomy"&gt;
&lt;h3&gt;Gilectomy&lt;/h3&gt;
&lt;p&gt;(&lt;a class="reference external" href="https://us.pycon.org/2016/schedule/presentation/2101/"&gt;abstract&lt;/a&gt;;
&lt;a class="reference external" href="https://www.youtube.com/watch?v=P3AyI_u66Bw"&gt;video&lt;/a&gt;)&lt;/p&gt;
&lt;p&gt;The infamous GIL is gone! And your Python programs only run 25x slower!&lt;/p&gt;
&lt;p&gt;Larry describes why the GIL was introduced, what it does, and what's
involved with removing it. He's actually got a fork of Python with the GIL
removed, but performance suffers quite a bit when run without the GIL.&lt;/p&gt;
&lt;/section&gt;
&lt;section id="lars-keynote"&gt;
&lt;h3&gt;Lars' Keynote&lt;/h3&gt;
&lt;p&gt;(&lt;a class="reference external" href="https://www.youtube.com/watch?v=bSfe5M_zG2s"&gt;video&lt;/a&gt;)&lt;/p&gt;
&lt;p&gt;If you watch only one video from PyCon, watch this. It's just incredible.&lt;/p&gt;
&lt;/section&gt;
&lt;/section&gt;</description><category>mozilla</category><category>pycon</category><category>python</category><category>releng</category><guid>https://atlee.ca/posts/pycon-2016-report/</guid><pubDate>Thu, 09 Jun 2016 19:39:40 GMT</pubDate></item><item><title>Diving into python logging</title><link>https://atlee.ca/posts/diving-into-python-logging/</link><dc:creator>chris</dc:creator><description>&lt;p&gt;Python has a very rich
&lt;a href="https://docs.python.org/3/library/logging.html"&gt;logging&lt;/a&gt; system. It's very
easy to add structured or unstructured log output to your python code, and
have it written to a file, or output to the console, or sent to syslog, or
to customize the output format.&lt;/p&gt;
&lt;p&gt;We're in the middle of re-examining how logging works in
&lt;a href="https://wiki.mozilla.org/ReleaseEngineering/Mozharness"&gt;mozharness&lt;/a&gt; to
make it easier to factor-out code and have fewer
&lt;a href="https://mgerva.wordpress.com/2015/02/25/on-mixins/"&gt;mixins&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Here are a few tips and tricks that have really helped me with python
logging:&lt;/p&gt;
&lt;h2 id="there-can-be-only-more-than-one"&gt;There can be &lt;s&gt;only&lt;/s&gt; more than one&lt;/h2&gt;
&lt;p&gt;Well, there can be only one logger with a given name. There is a special
"root" logger with no name. Multiple
&lt;code&gt;getLogger(name)&lt;/code&gt; calls with the same name will return the same logger
object.  This is an important property because it means you don't need to
explicitly pass logger objects around in your code. You can retrieve them
by name if you wish. The logging module is maintaining a global registry of
logging objects.&lt;/p&gt;
&lt;p&gt;You can have multiple loggers active, each specific to its own module or
even class or instance.&lt;/p&gt;
&lt;p&gt;Each logger has a name, typically the name of the module it's being used
from. A common pattern you see in python modules is this:&lt;/p&gt;
&lt;div class="code"&gt;&lt;pre class="code literal-block"&gt;&lt;span class="c1"&gt;# in module foo.py&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;logging&lt;/span&gt;
&lt;span class="n"&gt;log&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;logging&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;getLogger&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="vm"&gt;__name__&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;This works because inside &lt;code&gt;foo.py&lt;/code&gt;, &lt;code&gt;__name__&lt;/code&gt; is equal to "foo". So inside
this module the &lt;code&gt;log&lt;/code&gt; object is specific to this module.&lt;/p&gt;
&lt;h2 id="loggers-are-hierarchical"&gt;Loggers are hierarchical&lt;/h2&gt;
&lt;p&gt;The names of the loggers form their own namespace, with "." separating
levels. This means that if you have have loggers called &lt;code&gt;foo.bar&lt;/code&gt;, and &lt;code&gt;foo.baz&lt;/code&gt;,
you can do things on logger &lt;code&gt;foo&lt;/code&gt; that will impact both of the children. In
particular, you can set the logging level of &lt;code&gt;foo&lt;/code&gt; to show or ignore debug
messages for both submodules.&lt;/p&gt;
&lt;div class="code"&gt;&lt;pre class="code literal-block"&gt;&lt;span class="c1"&gt;# Let's enable all the debug logging for all the foo modules&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;logging&lt;/span&gt;
&lt;span class="n"&gt;logging&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;getLogger&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'foo'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;setLevel&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;logging&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;DEBUG&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;

&lt;h2 id="log-messages-are-like-events-that-flow-up-through-the-hierarchy"&gt;Log messages are like events that flow up through the hierarchy&lt;/h2&gt;
&lt;p&gt;Let's say we have a module foo.bar:&lt;/p&gt;
&lt;div class="code"&gt;&lt;pre class="code literal-block"&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;logging&lt;/span&gt;
&lt;span class="n"&gt;log&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;logging&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;getLogger&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="vm"&gt;__name__&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# __name__ is "foo.bar" here&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;make_widget&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;debug&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"made a widget!"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;When we call &lt;code&gt;make_widget()&lt;/code&gt;, the code generates a debug log message. Each
logger in the hierarchy has a chance to output something for the message,
ignore it, or pass the message along to its parent.&lt;/p&gt;
&lt;p&gt;The default
configuration for loggers is to have their levels unset (or set to &lt;a href="https://docs.python.org/3/library/logging.html#logging-levels"&gt;&lt;code&gt;NOTSET&lt;/code&gt;&lt;/a&gt;). This means the logger will just pass the message on up to its parent. Rinse &amp;amp; repeat until you get up to the root logger.&lt;/p&gt;
&lt;p&gt;So if the &lt;code&gt;foo.bar&lt;/code&gt; logger hasn't specified a level, the message will
continue up to the &lt;code&gt;foo&lt;/code&gt; logger. If the &lt;code&gt;foo&lt;/code&gt; logger hasn't specified a
level, the message will continue up to the root logger.&lt;/p&gt;
&lt;p&gt;This is why you typically configure the logging output on the root logger;
it typically gets ALL THE MESSAGES!!! Because this is so common, there's a
dedicated method for configuring the root logger:
&lt;a href="https://docs.python.org/3/library/logging.html#logging.basicConfig"&gt;&lt;code&gt;logging.basicConfig()&lt;/code&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;This also allows us to use mixed levels of log output depending on where
the message are coming from:&lt;/p&gt;
&lt;div class="code"&gt;&lt;pre class="code literal-block"&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;logging&lt;/span&gt;

&lt;span class="c1"&gt;# Enable debug logging for all the foo modules&lt;/span&gt;
&lt;span class="n"&gt;logging&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;getLogger&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"foo"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;setLevel&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;logging&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;DEBUG&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# Configure the root logger to log only INFO calls, and output to the console&lt;/span&gt;
&lt;span class="c1"&gt;# (the default)&lt;/span&gt;
&lt;span class="n"&gt;logging&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;basicConfig&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;level&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;logging&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;INFO&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# This will output the debug message&lt;/span&gt;
&lt;span class="n"&gt;logging&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;getLogger&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"foo.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;debug&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"ohai!"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;If you comment out the &lt;code&gt;setLevel(logging.DEBUG)&lt;/code&gt; call, you won't see the
message at all.&lt;/p&gt;
&lt;h2 id="exc_info-is-teh-awesome"&gt;exc_info is teh awesome&lt;/h2&gt;
&lt;p&gt;All the built-in logging calls support a keyword called &lt;code&gt;exc_info&lt;/code&gt;, which
if isn't false, causes the current exception information to be logged in
addition to the log message.
e.g.:&lt;/p&gt;
&lt;div class="code"&gt;&lt;pre class="code literal-block"&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;logging&lt;/span&gt;
&lt;span class="n"&gt;logging&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;basicConfig&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;level&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;logging&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;INFO&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;log&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;logging&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;getLogger&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="vm"&gt;__name__&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="k"&gt;assert&lt;/span&gt; &lt;span class="kc"&gt;False&lt;/span&gt;
&lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="ne"&gt;AssertionError&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;info&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"surprise! got an exception!"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;exc_info&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;/pre&gt;&lt;/div&gt;

&lt;p&gt;There's a special case for this, &lt;code&gt;log.exception()&lt;/code&gt;, which is equivalent to
&lt;code&gt;log.error(..., exc_info=True)&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;Python 3.2 introduced a new keyword, &lt;code&gt;stack_info&lt;/code&gt;, which will output the
current stack to the current code. Very handy to figure out &lt;em&gt;how&lt;/em&gt; you got
to a certain point in the code, even if no exceptions have occurred!&lt;/p&gt;
&lt;h2 id="no-handlers-found"&gt;"No handlers found..."&lt;/h2&gt;
&lt;p&gt;You've probably come across this message, especially when working with 3rd
party modules. What this means is that you don't have any logging handlers
configured, and something is trying to log a message. The message has gone
all the way up the logging hierarchy and fallen off the...top of the chain
(maybe I need a better metaphor).&lt;/p&gt;
&lt;div class="code"&gt;&lt;pre class="code literal-block"&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;logging&lt;/span&gt;
&lt;span class="n"&gt;log&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;logging&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;getLogger&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"no log for you!"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;outputs:&lt;/p&gt;
&lt;div class="code"&gt;&lt;pre class="code literal-block"&gt;&lt;span class="nv"&gt;No&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;handlers&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;could&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;be&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;found&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;&lt;span class="nv"&gt;logger&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"root"&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;There are two things that can be done here:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Configure logging in your module with &lt;code&gt;basicConfig()&lt;/code&gt; or similar&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Library authors should add a
   &lt;a href="https://docs.python.org/3/library/logging.handlers.html#logging.NullHandler"&gt;NullHandler&lt;/a&gt; at the root of their module to
   prevent this. See the
   &lt;a href="https://docs.python.org/3/howto/logging.html#library-config"&gt;cookbook&lt;/a&gt;
   and &lt;a href="http://pythonsweetness.tumblr.com/post/67394619015/use-of-logging-package-from-within-a-library"&gt;this
   blog&lt;/a&gt;
   for more details here.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id="want-more"&gt;Want more?&lt;/h2&gt;
&lt;p&gt;I really recommend that you read the &lt;a href="https://docs.python.org/3/library/logging.html"&gt;logging
documentation&lt;/a&gt; and
&lt;a href="https://docs.python.org/3/howto/logging-cookbook.html"&gt;cookbook&lt;/a&gt; which
have a lot more great information (and are also very well written!) There's
a lot more you can do, with custom log handlers, different output formats,
outputting to many locations at once, etc. Have fun!&lt;/p&gt;</description><category>mozharness</category><category>mozilla</category><category>python</category><guid>https://atlee.ca/posts/diving-into-python-logging/</guid><pubDate>Fri, 27 Feb 2015 21:09:33 GMT</pubDate></item><item><title>Stuff I learned this weekend - vim, python and more!</title><link>https://atlee.ca/posts/blog20120429stuff-i-learned-this-weekend/</link><dc:creator>chris</dc:creator><description>&lt;p&gt;Call me strange, but I actually enjoy spending time reading up on programming tools that I use regularly. I think of programming tools as tools in same way that a hammer or a saw is a tool. They both help you to get a job done. You need to learn how to use them properly. You need to keep tools well maintained. Sometimes you need to throw a tool away and get a new one.


For my professional and personal programming I spend 99% of my time writing &lt;a href="http://python.org"&gt;python&lt;/a&gt; with &lt;a href="http://vim.org"&gt;vim&lt;/a&gt;, and so I really enjoy learning more about them.



Stuff I learned about vim:



&lt;a href="http://nvie.com/posts/how-i-boosted-my-vim/"&gt;How I boosted my vim&lt;/a&gt; - lots of great vim tips (how did I not know about :set visualbell until now???) and plugins, which introduced me to...



&lt;a href="https://github.com/scrooloose/nerdtree"&gt;nerdtree&lt;/a&gt; - for file browsing in vim. It also reminded me to make use of the &lt;a href="https://wincent.com/products/command-t"&gt;command-t plugin&lt;/a&gt; I had installed a while back.



&lt;a href="https://github.com/tpope/vim-surround"&gt;surround&lt;/a&gt; - for giving you the ability to work with the surroundings for text objects. Ever wanted to easily add quotes to a word, or change double quotes surrounding a string to single quotes? I know you have - so go install this plugin now!



&lt;a href="http://www.vim.org/scripts/script.php?script_id=2540"&gt;snipmate&lt;/a&gt; - lets you define lots of predefined snippets for various languages. Now in python I can type "def&amp;lt;tab&amp;gt;" and bam! I get a basic function definition.



I wasn't able to get to PyCon US 2012 this year, so I'm very happy that the sessions were &lt;a href="http://pyvideo.org/category/17/pycon-us-2012"&gt;all recorderd.&lt;/a&gt;



&lt;a href="http://pyvideo.org/video/879/the-art-of-subclassing"&gt;The art of subclassing&lt;/a&gt; - great tips on how to do subclassing well in python.



&lt;a href="http://pyvideo.org/video/880/stop-writing-classes"&gt;why classes aren't always what you want&lt;/a&gt; - I liked how he emphasized that you should be always be open to refactoring your code. Usually making your own exception classes is a bad idea...however one great nugget buried in there was if you can't decide if you should raise a KeyError, AttributeError or TypeError (for example), make a class that inherits from all 3 and raise that. Then consumers can catch what makes sense to them instead of guessing.



&lt;a href="http://pyvideo.org/video/877/introduction-to-metaclasses"&gt;introduction to metaclasses&lt;/a&gt; - metaclasses aren't so scary after all!



&lt;a href="http://pyvideo.org/video/642/throwing-together-distributed-services-with-geven"&gt;nice framework for building gevent services&lt;/a&gt; I liked the simple examples here. It introduces the &lt;a href="https://github.com/progrium/ginkgo"&gt;ginkgo&lt;/a&gt; framework, which I'm hoping to have some time to play with soon.&lt;/p&gt;</description><category>mozilla</category><category>python</category><category>tips</category><guid>https://atlee.ca/posts/blog20120429stuff-i-learned-this-weekend/</guid><pubDate>Sun, 29 Apr 2012 21:06:21 GMT</pubDate></item><item><title>How RelEng uses mercurial quickly and safely</title><link>https://atlee.ca/posts/blog20120127hg-quick-and-safe/</link><dc:creator>chris</dc:creator><description>&lt;p&gt;Release Engineering uses hg &lt;strong&gt;a lot&lt;/strong&gt;. Every build or test involves code from at least one hg repository.


&lt;a href="https://atlee.ca/blog/2011/11/22/a-small-battle-won-in-the-war-on-build-times/"&gt;Last year&lt;/a&gt; we started using some internal mirrors at the same time as making use of the hg share extension across the board, both of these had a big impact on the load on hg and time to clone/update local working copies.



I think what we've done is pretty useful and resilient to various types of failure, so I hope this blog post is helpful for others trying to automate processes involving hg!



The primary tool we're using for hg operations is called &lt;a href="http://hg.mozilla.org/build/tools/file/437cce872cb7/buildfarm/utils/hgtool.py"&gt;hgtool&lt;/a&gt; (available from our &lt;a href="http://hg.mozilla.org/build/tools"&gt;tools repo&lt;/a&gt;). Yes, we're very inventive at naming things.



hgtool's basic usage is to be given the location of a remote repository, a local directory, and usually a revision. Its job is to make sure that the local directory contains a clean working copy of the repository at the specified revision.



First of all, you don't need to worry about doing an 'hg clone' if the directory doesn't exist, or 'hg pull' if it does exist. This simplifies a lot of build logic!



Next, we've build support for mirrors into hgtool. You can pass one or more mirror repositories to the tool with '--mirror', and it will attempt to pull/clone from the mirrors before trying to pull/clone from the primary repository. At Mozilla we have several internal hg mirrors that we use to reduce load on the primary public-facing hg servers.



To improve the case when you need to do a full clone, we've added support for importing an hg bundle to initialize the local repository rather than doing a full clone from the mirror or master repositories. You can pass one or more bundle urls with '--bundle'. hgtool will download and import the bundle, and then pull in new changesets from the mirrors and master repositories.



Finally, hgtool supports the 'hg share' extension. If you specify a base directory for shared repositories, all of the above operations will be run on a locally shared repository first, and then the working copy will be created with 'hg share', and updated to the correct revision.



There are all kinds of fallback behaviours specified, like if you fail to import a bundle, try to clone from a mirror; then if you fail to clone from a mirror, try to clone from the master. These fallbacks have resulted in a far more resilient build process.&lt;/p&gt;</description><category>mozilla</category><category>python</category><guid>https://atlee.ca/posts/blog20120127hg-quick-and-safe/</guid><pubDate>Fri, 27 Jan 2012 19:08:20 GMT</pubDate></item><item><title>Investigating hg performance</title><link>https://atlee.ca/posts/blog20111209hg-performance/</link><dc:creator>chris</dc:creator><description>&lt;em&gt;(caveat lector: this is a long post with lots of shell snippets and output; it's mostly a brain dump of what I did to investigate performance issues on hg.mozilla.org. I hope you find it useful. Scroll to the bottom for the summary.)&lt;/em&gt;


&lt;a href="https://bugzilla.mozilla.org/show_bug.cgi?id=656757"&gt;Everybody&lt;/a&gt; &lt;a href="https://bugzilla.mozilla.org/show_bug.cgi?id=676420"&gt;knows&lt;/a&gt; &lt;a href="https://bugzilla.mozilla.org/show_bug.cgi?id=554656"&gt;that&lt;/a&gt; &lt;a href="https://bugzilla.mozilla.org/show_bug.cgi?id=708632"&gt;pushing&lt;/a&gt; &lt;a href="https://bugzilla.mozilla.org/show_bug.cgi?id=688240"&gt;to&lt;/a&gt; &lt;a href="https://bugzilla.mozilla.org/show_bug.cgi?id=695454"&gt;try&lt;/a&gt; &lt;a href="https://bugzilla.mozilla.org/show_bug.cgi?id=696682"&gt;can&lt;/a&gt; &lt;a href="https://bugzilla.mozilla.org/show_bug.cgi?id=672231"&gt;be&lt;/a&gt; &lt;a href="https://bugzilla.mozilla.org/show_bug.cgi?id=633161"&gt;slow&lt;/a&gt;. but why?



while waiting for my push to try to complete, I wondered what exactly was slow.



I started by cloning my own version of try:

&lt;pre&gt;

$ hg clone http://hg.mozilla.org try

destination directory: try

requesting all changes

adding changesets

adding manifests

adding file changes

added 95917 changesets with 447521 changes to 89564 files (+2446 heads)

updating to branch default

53650 files updated, 0 files merged, 0 files removed, 0 files unresolved

&lt;/pre&gt;



Next I instrumented hg so I could get some profile information:

&lt;pre&gt;

$ sudo vi /usr/local/bin/hg

python -m cProfile -o /tmp/hg.profile /usr/bin/hg $*

&lt;/pre&gt;



Then I timed out long it took me to check what would be pushed:

&lt;pre&gt;

$ time hg out ssh://localhost//home/catlee/mozilla/try

hg out ssh://localhost//home/catlee/mozilla/try  0.57s user 0.04s system 54% cpu 1.114 total

&lt;/pre&gt;



That's not too bad. Let's check our profile:

&lt;pre lang="python"&gt;

import pstats

pstats.Stats("/tmp/hg.profile").strip_dirs().sort_stats('time').print_stats(10)

Fri Dec  9 00:25:02 2011    /tmp/hg.profile


         38744 function calls (37761 primitive calls) in 0.593 seconds

   Ordered by: internal time
   List reduced from 476 to 10 due to restriction 

   ncalls  tottime  percall  cumtime  percall filename:lineno(function)
       13    0.462    0.036    0.462    0.036 {method 'readline' of 'file' objects}
        1    0.039    0.039    0.039    0.039 {mercurial.parsers.parse_index2}
       40    0.031    0.001    0.031    0.001 revlog.py:291(rev)
        1    0.019    0.019    0.019    0.019 revlog.py:622(headrevs)
   177/70    0.009    0.000    0.019    0.000 {__import__}
     6326    0.004    0.000    0.006    0.000 cmdutil.py:15(parsealiases)
       13    0.003    0.000    0.003    0.000 {method 'read' of 'file' objects}
       93    0.002    0.000    0.008    0.000 cmdutil.py:18(findpossible)
     7212    0.001    0.000    0.001    0.000 {method 'split' of 'str' objects}
  392/313    0.001    0.000    0.007    0.000 demandimport.py:92(_demandimport)
&lt;/pre&gt;



The top item is readline() on file objects? I wonder if that's socket operations. I'm ssh'ing to localhost, so it's really fast. Let's add 100ms latency:



&lt;pre&gt;

$ sudo tc qdisc add dev lo root handle 1:0 netem delay 100ms

$ time hg out ssh://localhost//home/catlee/mozilla/try

hg out ssh://localhost//home/catlee/mozilla/try  0.58s user 0.05s system 14% cpu 4.339 total

&lt;/pre&gt;



&lt;pre lang="python"&gt;

import pstats

pstats.Stats("/tmp/hg.profile").strip_dirs().sort_stats('time').print_stats(10)

Fri Dec  9 00:42:09 2011    /tmp/hg.profile


         38744 function calls (37761 primitive calls) in 2.728 seconds

   Ordered by: internal time
   List reduced from 476 to 10 due to restriction 

   ncalls  tottime  percall  cumtime  percall filename:lineno(function)
       13    2.583    0.199    2.583    0.199 {method 'readline' of 'file' objects}
        1    0.054    0.054    0.054    0.054 {mercurial.parsers.parse_index2}
       40    0.028    0.001    0.028    0.001 revlog.py:291(rev)
        1    0.019    0.019    0.019    0.019 revlog.py:622(headrevs)
   177/70    0.010    0.000    0.019    0.000 {__import__}
       13    0.006    0.000    0.006    0.000 {method 'read' of 'file' objects}
     6326    0.002    0.000    0.004    0.000 cmdutil.py:15(parsealiases)
       93    0.002    0.000    0.006    0.000 cmdutil.py:18(findpossible)
  392/313    0.002    0.000    0.008    0.000 demandimport.py:92(_demandimport)
     7212    0.001    0.000    0.001    0.000 {method 'split' of 'str' objects}
&lt;/pre&gt;



Yep, definitely getting worse with more latency on the network connection.



Oh, and I'm using a recent version of hg:

&lt;pre&gt;

$ hg --version

Mercurial Distributed SCM (version 2.0)



$ echo hello | ssh localhost hg -R /home/catlee/mozilla/try serve --stdio

145

capabilities: lookup changegroupsubset branchmap pushkey known getbundle unbundlehash batch stream unbundle=HG10GZ,HG10BZ,HG10UN httpheader=1024

&lt;/pre&gt;



This doesn't match what hg.mozilla.org is running:

&lt;pre&gt;

$ echo hello | ssh hg.mozilla.org hg -R /mozilla-central serve --stdio  

67

capabilities: unbundle lookup changegroupsubset branchmap stream=1

&lt;/pre&gt;



So it must be using an older version. Let's see what mercurial 1.6 does:

&lt;pre&gt;

$ mkvirtualenv hg16

New python executable in hg16/bin/python

Installing setuptools...



(hg16)$ pip install mercurial==1.6

Downloading/unpacking mercurial==1.6
  Downloading mercurial-1.6.tar.gz (2.2Mb): 2.2Mb downloaded
...



(hg16)$ hg --version

Mercurial Distributed SCM (version 1.6)



(hg16)$ echo hello | ssh localhost /home/catlee/.virtualenvs/hg16/bin/hg -R /home/catlee/mozilla/mozilla-central serve --stdio

75

capabilities: unbundle lookup changegroupsubset branchmap pushkey stream=1

&lt;/pre&gt;



That looks pretty close to what hg.mozilla.org claims it supports, so let's time 'hg out' again:



&lt;pre&gt;

(hg16)$ time hg out ssh://localhost//home/catlee/mozilla/try

hg out ssh://localhost//home/catlee/mozilla/try  0.73s user 0.04s system 3% cpu 24.278 total

&lt;/pre&gt;



&lt;h2&gt;tl;dr&lt;/h2&gt;

Finding missing changesets between two local repositories is &lt;strong&gt;6x slower with hg 1.6&lt;/strong&gt; (4 seconds with hg 2.0 to 24 seconds hg 1.6). Add a few hundred people and machines hitting the same repository at the same time, and I imagine things can get bad pretty quickly.



Some further searching reveals that mercurial does support a &lt;a href="http://mercurial.selenic.com/wiki/WireProtocol"&gt;faster&lt;/a&gt; method of finding missing changesets in "newer" versions, although I can't figure out exactly when this change was introduced.  There's already a &lt;a href="https://bugzilla.mozilla.org/show_bug.cgi?id=623505"&gt;bug on file&lt;/a&gt; for upgrading mercurial on hg.mozilla.org, so hopefully that improves the situation for pushes to try.



The tools we use everyday aren't magical; they're subject to normal debugging and profiling techniques. If a tool you're using is holding you back, find out why!</description><category>linux</category><category>mozilla</category><category>performance</category><category>python</category><category>technology</category><guid>https://atlee.ca/posts/blog20111209hg-performance/</guid><pubDate>Fri, 09 Dec 2011 07:02:28 GMT</pubDate></item><item><title>cURL and paste</title><link>https://atlee.ca/posts/blog20110816curl-and-paste/</link><dc:creator>chris</dc:creator><description>&lt;a href="http://curl.haxx.se/" target="_blank"&gt;cURL&lt;/a&gt; and &lt;a href="http://pythonpaste.org/" target="_blank"&gt;paste&lt;/a&gt;...two great tastes that apparently don't go well at all together!


I've been writing a bunch of simple &lt;a href="http://wsgi.org/wsgi/" target="_blank"&gt;wsgi&lt;/a&gt; apps lately, some of which handle file uploads.



Take this tiny application:

&lt;pre lang="python"&gt;

import webob



def app(environ, start_response):
    req = webob.Request(environ)
    req.body_file.read()
    return webob.Response("OK!")(environ, start_response)


import paste.httpserver

paste.httpserver.serve(app, port=8090)

&lt;/pre&gt;



Then throw some files at it with cURL:

&lt;pre lang="bash"&gt;

[catlee] % for f in $(find -type f); do time curl -s -o /dev/null --data-binary @$f http://localhost:8090; done

curl -s -o /dev/null --data-binary @$f http://localhost:8090  0.00s user 0.00s system 0% cpu 1.013 total

curl -s -o /dev/null --data-binary @$f http://localhost:8090  0.01s user 0.00s system 63% cpu 0.013 total

curl -s -o /dev/null --data-binary @$f http://localhost:8090  0.01s user 0.00s system 64% cpu 0.012 total

curl -s -o /dev/null --data-binary @$f http://localhost:8090  0.01s user 0.00s system 81% cpu 0.015 total

curl -s -o /dev/null --data-binary @$f http://localhost:8090  0.01s user 0.00s system 0% cpu 1.014 total

curl -s -o /dev/null --data-binary @$f http://localhost:8090  0.00s user 0.00s system 0% cpu 1.009 total

&lt;/pre&gt;



Huh? Some files take a second to upload?



I discovered after much digging, and rewriting my (more complicated) app several times, that the problem is that cURL sends an extra "Expect: 100-continue" header. This is supposed to let a web server respond with "100 Continue" immediately or reject an upload based on the request headers.



The problem is that paste's httpserver doesn't send this by default, and so cURL will wait for a second before giving up and sending the rest of the request.



The magic to turn this off is the '-0' to cURL, which forces HTTP/1.0 mode:

&lt;pre lang="bash"&gt;

[catlee] % for f in $(find -type f); do time curl -0 -s -o /dev/null --data-binary @$f http://localhost:8090; done

curl -0 -s -o /dev/null --data-binary @$f http://localhost:8090  0.00s user 0.00s system 66% cpu 0.012 total

curl -0 -s -o /dev/null --data-binary @$f http://localhost:8090  0.01s user 0.00s system 64% cpu 0.012 total

curl -0 -s -o /dev/null --data-binary @$f http://localhost:8090  0.00s user 0.01s system 58% cpu 0.014 total

curl -0 -s -o /dev/null --data-binary @$f http://localhost:8090  0.01s user 0.00s system 66% cpu 0.012 total

curl -0 -s -o /dev/null --data-binary @$f http://localhost:8090  0.00s user 0.00s system 59% cpu 0.013 total

curl -0 -s -o /dev/null --data-binary @$f http://localhost:8090  0.01s user 0.00s system 65% cpu 0.012 total

&lt;/pre&gt;</description><category>performance</category><category>python</category><category>tips</category><guid>https://atlee.ca/posts/blog20110816curl-and-paste/</guid><pubDate>Wed, 17 Aug 2011 03:17:31 GMT</pubDate></item><item><title>self-serve builds!</title><link>https://atlee.ca/posts/blog20110217self-serve-builds/</link><dc:creator>chris</dc:creator><description>&lt;p&gt;Do you want to be able to cancel your own try server builds?


Do you want to be able to re-trigger a failed nightly build before the RelEng sheriff wakes up?



Do you want to be able to get additional test runs on your build?



If you answered an enthusiastic &lt;strong&gt;YES&lt;/strong&gt; to any or all of these questions, then &lt;a href="https://build.mozilla.org/buildapi/self-serve"&gt;self-serve&lt;/a&gt; is for you.



self-serve was created to provide an API to allow developers to interact with our build infrastructure, with the goal being that others would then create tools against it. It's still early days for this self-serve API, so just a few caveats:



&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;strong&gt;This is very much pre-alpha&lt;/strong&gt; and may cause your computer to explode, your keg to run dry, or may simply hang.&lt;/li&gt;



&lt;li&gt;It's slower than I want. I've spent a bit of time optimizing and caching, but I think it can be much better. Just look at shaver's &lt;a href="http://shaver.off.net/diary/2011/01/22/i-made-a-thing/"&gt;bugzilla search&lt;/a&gt; to see what's possible for speed. Part of the problem here is that it's currently running on a VM that's doing a few dozen other things. We're working on getting faster hardware, but didn't want to block this pre-alpha-rollout on that.&lt;/li&gt;



&lt;li&gt;You need to log in with your LDAP credentials to work with it.&lt;/li&gt;



&lt;li&gt;The HTML interface is teh suck. Good thing I'm not paid to be a front-end webdev! Really, the goal here wasn't to create a fully functional web interface, but rather to provide a functional &lt;em&gt;programmatic&lt;/em&gt; interface.&lt;/li&gt;



&lt;li&gt;Changing build priorities may run afoul of &lt;a href="https://bugzilla.mozilla.org/show_bug.cgi?id=555664"&gt;bug 555664&lt;/a&gt;...haven't had a chance to test out exactly what happens right now if a high priority job gets merged with a lower priority one.&lt;/li&gt;



&lt;/ul&gt;



That being said, I'm proud to be able to finally make this public. Documentation for the REST API is available as part of the web interface itself, and the code is available as part of the &lt;a href="http://hg.mozilla.org/build/buildapi"&gt;buildapi&lt;/a&gt; repository on hg.mozilla.org



&lt;a href="https://build.mozilla.org/buildapi/self-serve"&gt;https://build.mozilla.org/buildapi/self-serve&lt;/a&gt;



Please be gentle!



Any questions, problems or feedback can be left here, or filed in &lt;a href="https://bugzilla.mozilla.org/enter_bug.cgi?product=mozilla.org&amp;amp;component=Release%20Engineering"&gt;bugzilla.&lt;/a&gt;</description><category>buildbot</category><category>firefox</category><category>mozilla</category><category>python</category><category>utilities</category><category>work</category><guid>https://atlee.ca/posts/blog20110217self-serve-builds/</guid><pubDate>Thu, 17 Feb 2011 23:14:26 GMT</pubDate></item><item><title>Just who am I talking to? (verifying https connections with python)</title><link>https://atlee.ca/posts/blog20110210verifying-https-python/</link><dc:creator>chris</dc:creator><description>&lt;p&gt;Did you know that python's urllib module supports connecting to web servers over HTTPS?  It's easy!


&lt;/p&gt;&lt;pre lang="python"&gt;

import urllib

data = urllib.urlopen("https://www.google.com").read()

print data

&lt;/pre&gt;



Did you also know that it provides absolutely zero guarantees that your "secure" data isn't being observed by a man-in-the-middle?



Run this:

&lt;pre lang="python"&gt;

from paste import httpserver

def app(environ, start_response):
    start_response("200 OK", [])
    return "Thanks for your secrets!"


httpserver.serve(app, host='127.0.0.1', port='8080', ssl_pem='*')

&lt;/pre&gt;

This little web app will generate a random SSL certificate for you each time it's run. A self-signed, completely untrustworthy certificate.



Now modify your first script to look at https://localhost:8080 instead. Or, for more fun, keep it pointing at google and mess with your IP routing to redirect google.com:443 to localhost:8080. 



&lt;pre lang="sh"&gt;

iptables -t nat -A OUTPUT -d google.com -p tcp --dport 443 -j DNAT --to-destination 127.0.0.1:8080

&lt;/pre&gt;



Run your script again, and see what it says.



Instead of the raw HTML of google.com, you now get "Thanks for your secrets!". That's right, python will happily accept without complaint or warning the random certificate generated this little python app pretending to be google.com.



Sometimes you want to know who you're talking to, you know?



&lt;pre lang="python"&gt;

import httplib, socket, ssl, urllib2

def buildValidatingOpener(ca_certs):
    class VerifiedHTTPSConnection(httplib.HTTPSConnection):
        def connect(self):
            # overrides the version in httplib so that we do
            #    certificate verification
            sock = socket.create_connection((self.host, self.port),
                                            self.timeout)
            if self._tunnel_host:
                self.sock = sock
                self._tunnel()

            # wrap the socket using verification with the root
            #    certs in trusted_root_certs
            self.sock = ssl.wrap_socket(sock,
                                        self.key_file,
                                        self.cert_file,
                                        cert_reqs=ssl.CERT_REQUIRED,
                                        ca_certs=ca_certs,
                                        )

    # wraps https connections with ssl certificate verification
    class VerifiedHTTPSHandler(urllib2.HTTPSHandler):
        def __init__(self, connection_class=VerifiedHTTPSConnection):
            self.specialized_conn_class = connection_class
            urllib2.HTTPSHandler.__init__(self)

        def https_open(self, req):
            return self.do_open(self.specialized_conn_class, req)

    https_handler = VerifiedHTTPSHandler()
    url_opener = urllib2.build_opener(https_handler)

    return url_opener


opener = buildValidatingOpener("/usr/lib/ssl/certs/ca-certificates.crt")

req = urllib2.Request("https://www.google.com")

print opener.open(req).read()

&lt;/pre&gt;



Using the this new validating url opener, we can make sure we're talking to someone with a validly signed certificate. With our IP redirection in place, or pointing at localhost:8080 explicitly we get a certificate invalid error. We &lt;em&gt;still&lt;/em&gt; don't know for sure that it's google (could be some other site with a valid ssl certificate), but maybe we'll tackle that in a future post!</description><category>mozilla</category><category>python</category><guid>https://atlee.ca/posts/blog20110210verifying-https-python/</guid><pubDate>Thu, 10 Feb 2011 20:43:58 GMT</pubDate></item></channel></rss>