<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom"><title>S.Lott -- Software Architect - Python</title><link href="https://slott56.github.io/" rel="alternate"></link><link href="/feeds/python.atom.xml" rel="self"></link><id>https://slott56.github.io/</id><updated>2025-10-17T13:08:00-04:00</updated><entry><title>A get_object_size() Function -- Again</title><link href="https://slott56.github.io/2025-10-17-get_object_size_function_again.html" rel="alternate"></link><published>2025-10-17T13:08:00-04:00</published><updated>2025-10-17T13:08:00-04:00</updated><author><name>S.Lott</name></author><id>tag:slott56.github.io,2025-10-17:/2025-10-17-get_object_size_function_again.html</id><summary type="html">&lt;div class="section" id="bluf"&gt;
&lt;h2&gt;BLUF&lt;/h2&gt;
&lt;p&gt;You rarely need this.
Python handles memory management for you.
Except in an edge case where you have a &lt;strong&gt;lot&lt;/strong&gt; of objects to work with.&lt;/p&gt;
&lt;p&gt;How many is a lot?  Enough that your app crashes with &lt;tt class="docutils literal"&gt;MemoryError&lt;/tt&gt; exception.
Or, is consuming so much memory other processes have trouble working …&lt;/p&gt;&lt;/div&gt;</summary><content type="html">&lt;div class="section" id="bluf"&gt;
&lt;h2&gt;BLUF&lt;/h2&gt;
&lt;p&gt;You rarely need this.
Python handles memory management for you.
Except in an edge case where you have a &lt;strong&gt;lot&lt;/strong&gt; of objects to work with.&lt;/p&gt;
&lt;p&gt;How many is a lot?  Enough that your app crashes with &lt;tt class="docutils literal"&gt;MemoryError&lt;/tt&gt; exception.
Or, is consuming so much memory other processes have trouble working.
Or, it's slow because of all the garbage collection going on.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="background"&gt;
&lt;h2&gt;Background&lt;/h2&gt;
&lt;p&gt;See &lt;a class="reference external" href="https://slott56.github.io/2025-10-16-get_object_size_function.html"&gt;A get_object_size() function&lt;/a&gt;. This shows a totally non-recursive and not-too-smart approach.&lt;/p&gt;
&lt;p&gt;The previous example avoids recursion.
This is not &lt;em&gt;really&lt;/em&gt; helpful.
While structures can be very large, they are rarely deeply nested.
The ordinary Python stack limit would prevent us from walking a structure with over 1,000 layers of nesting.
Even creating a test case is a pain in the neck.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="what-i-did"&gt;
&lt;h2&gt;What I Did&lt;/h2&gt;
&lt;p&gt;This function computes the total size of just about anything.
This includes all the built-in collections.
It also includes &amp;quot;custom classes&amp;quot;, both the &lt;tt class="docutils literal"&gt;__slots__&lt;/tt&gt; and the non-&lt;tt class="docutils literal"&gt;__slots__&lt;/tt&gt; variants.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;collections.abc&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Sequence&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Mapping&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Set&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Iterable&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Callable&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;itertools&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;sys&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;textwrap&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;shorten&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;typing&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Any&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;get_object_size&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;some_object&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Any&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;additional_types&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Callable&lt;/span&gt;&lt;span class="p"&gt;[[&lt;/span&gt;&lt;span class="n"&gt;Any&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="kc"&gt;None&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="kc"&gt;None&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;None&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;verbose&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;bool&lt;/span&gt; &lt;span class="o"&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="o"&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="w"&gt;    &lt;/span&gt;&lt;span class="sd"&gt;&amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;
&lt;span class="sd"&gt;    Computes the size of the given object.&lt;/span&gt;
&lt;span class="sd"&gt;    This expands on the recipe cited in the documentation for :py:func:`sys.getsizeof`.&lt;/span&gt;

&lt;span class="sd"&gt;    :param some_object: Any Python object.&lt;/span&gt;
&lt;span class="sd"&gt;    :param additional_types: A function that can return the size for an object for a type not handled here.&lt;/span&gt;
&lt;span class="sd"&gt;    :param verbose: True to print object information as the size is computed.&lt;/span&gt;
&lt;span class="sd"&gt;    :return: aggregate size of the object and all the related objects.&lt;/span&gt;

&lt;span class="sd"&gt;    The sizes are **highly** implementation specific.&lt;/span&gt;

&lt;span class="sd"&gt;    The types handled here are the built-in collections&lt;/span&gt;
&lt;span class="sd"&gt;    defined in :py:mod:`collections.abc`:&lt;/span&gt;
&lt;span class="sd"&gt;    ``str``, ``Sequence``, ``Set``, ``Mapping``.&lt;/span&gt;
&lt;span class="sd"&gt;    Additionally, this will look at any instance of class derived from :py:class:`object`,&lt;/span&gt;
&lt;span class="sd"&gt;    handling the default ``__dict__`` as well as ``__slots__``.&lt;/span&gt;

&lt;span class="sd"&gt;    &amp;gt;&amp;gt;&amp;gt; get_object_size(&amp;quot;Hello, world!&amp;quot;)&lt;/span&gt;
&lt;span class="sd"&gt;    54&lt;/span&gt;
&lt;span class="sd"&gt;    &amp;gt;&amp;gt;&amp;gt; get_object_size(&amp;quot;!&amp;quot;)&lt;/span&gt;
&lt;span class="sd"&gt;    42&lt;/span&gt;
&lt;span class="sd"&gt;    &amp;gt;&amp;gt;&amp;gt; get_object_size(list(range(10)))&lt;/span&gt;
&lt;span class="sd"&gt;    416&lt;/span&gt;
&lt;span class="sd"&gt;    &amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;
    &lt;span class="n"&gt;default_size&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;sys&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;getsizeof&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;seen&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="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;]&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="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;component_size&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="n"&gt;Any&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&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="k"&gt;nonlocal&lt;/span&gt; &lt;span class="n"&gt;seen&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nb"&gt;id&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="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;seen&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
        &lt;span class="n"&gt;seen&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;id&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;obj&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;verbose&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="sa"&gt;f&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nb"&gt;id&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="si"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;8x&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; &lt;/span&gt;&lt;span class="si"&gt;{&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;obj&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;, &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;shorten&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;repr&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="mi"&gt;32&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;file&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;sys&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;stderr&lt;/span&gt;
            &lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="n"&gt;items&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Iterable&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="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;iter&lt;/span&gt;&lt;span class="p"&gt;([])&lt;/span&gt;
        &lt;span class="k"&gt;match&lt;/span&gt; &lt;span class="n"&gt;obj&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
                &lt;span class="k"&gt;pass&lt;/span&gt;
            &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="n"&gt;Sequence&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="n"&gt;Set&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;sequence&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="n"&gt;items&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;component_size&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;sequence&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="n"&gt;Mapping&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;mapping&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="n"&gt;items&lt;/span&gt; &lt;span class="o"&gt;=&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;chain&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                    &lt;span class="nb"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;component_size&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;mapping&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="nb"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;component_size&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;mapping&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;span class="k"&gt;case&lt;/span&gt; &lt;span class="nb"&gt;object&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;obj_dict&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nb"&gt;hasattr&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="s2"&gt;&amp;quot;__dict__&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
                &lt;span class="n"&gt;items&lt;/span&gt; &lt;span class="o"&gt;=&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;chain&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                    &lt;span class="nb"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;component_size&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;obj_dict&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="vm"&gt;__dict__&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="nb"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;component_size&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;obj_dict&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="vm"&gt;__dict__&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;span class="k"&gt;case&lt;/span&gt; &lt;span class="nb"&gt;object&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;obj_slot&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nb"&gt;hasattr&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="s2"&gt;&amp;quot;__slots__&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
                &lt;span class="n"&gt;values&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
                    &lt;span class="nb"&gt;getattr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;obj_slot&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="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;obj_slot&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="vm"&gt;__slots__&lt;/span&gt;
                    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nb"&gt;hasattr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;obj_slot&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="p"&gt;)&lt;/span&gt;
                &lt;span class="n"&gt;items&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;component_size&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;values&lt;/span&gt;&lt;span class="p"&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;_&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;additional_types&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;obj_size&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;additional_types&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="ow"&gt;is&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="kc"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                    &lt;span class="n"&gt;items&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;iter&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="n"&gt;obj_size&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;

        &lt;span class="n"&gt;base&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;sys&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;getsizeof&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="n"&gt;default_size&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;
        &lt;span class="n"&gt;sizes&lt;/span&gt; &lt;span class="o"&gt;=&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;chain&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;base&lt;/span&gt;&lt;span class="p"&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;return&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;sizes&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;component_size&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;some_object&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;This variant walks an entire structure recursively.
It creates iterable generators with size details.&lt;/p&gt;
&lt;p&gt;You won't often need this.
But. I've posted it here so I won't lose it.&lt;/p&gt;
&lt;p&gt;And. I like to think through alternative implementations.
One of these is probably faster.&lt;/p&gt;
&lt;/div&gt;
</content><category term="Python"></category><category term="python"></category><category term="oo"></category><category term="oodesign"></category></entry><entry><title>A get_object_size() Function [Updated]</title><link href="https://slott56.github.io/2025-10-16-get_object_size_function.html" rel="alternate"></link><published>2025-10-16T13:08:00-04:00</published><updated>2025-10-16T13:08:00-04:00</updated><author><name>S.Lott</name></author><id>tag:slott56.github.io,2025-10-16:/2025-10-16-get_object_size_function.html</id><summary type="html">&lt;div class="section" id="bluf"&gt;
&lt;h2&gt;BLUF&lt;/h2&gt;
&lt;p&gt;You rarely need this.
Python handles memory management for you.
Except in an edge case where you have a &lt;strong&gt;lot&lt;/strong&gt; of objects to work with.&lt;/p&gt;
&lt;p&gt;How many is a lot?  Enough that your app crashes with &lt;tt class="docutils literal"&gt;MemoryError&lt;/tt&gt; exception.
Or, is consuming so much memory other processes have trouble working …&lt;/p&gt;&lt;/div&gt;</summary><content type="html">&lt;div class="section" id="bluf"&gt;
&lt;h2&gt;BLUF&lt;/h2&gt;
&lt;p&gt;You rarely need this.
Python handles memory management for you.
Except in an edge case where you have a &lt;strong&gt;lot&lt;/strong&gt; of objects to work with.&lt;/p&gt;
&lt;p&gt;How many is a lot?  Enough that your app crashes with &lt;tt class="docutils literal"&gt;MemoryError&lt;/tt&gt; exception.
Or, is consuming so much memory other processes have trouble working.
Or, it's slow because of all the garbage collection going on.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="background"&gt;
&lt;h2&gt;Background&lt;/h2&gt;
&lt;p&gt;One of the significant benefits of using Python is memory management.
Python creates and disposes of objects as needed, without a lot of complicated-looking code.
The reference counting often works flawlessly.&lt;/p&gt;
&lt;p&gt;Often.&lt;/p&gt;
&lt;p&gt;Sometimes there are circular references, and objects can't (trivially) be collected.
Object A refers to object B and (sadly) object B &lt;em&gt;also&lt;/em&gt; refers to object A.
They both have non-zero reference counts.&lt;/p&gt;
&lt;p&gt;Weak references can sort this out, preventing a core leak that leads to unreliable software.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="use-case"&gt;
&lt;h2&gt;Use Case&lt;/h2&gt;
&lt;p&gt;Python has a way to reduce the size of objects.
It alters a few minor details of how an object works, but is generally transparent.&lt;/p&gt;
&lt;p&gt;This is an example of a rare case where you are doing memory management in Python.&lt;/p&gt;
&lt;p&gt;You can use the &lt;tt class="docutils literal"&gt;__slots__&lt;/tt&gt; feature of a class to name the attributes that are present,
and prevent the creation of the usual &lt;tt class="docutils literal"&gt;__dict__&lt;/tt&gt; structure to hold the attributes.&lt;/p&gt;
&lt;p&gt;When playing with this, it's often easiest to use the &lt;tt class="docutils literal"&gt;&amp;#64;dataclass(slots=True)&lt;/tt&gt; decorator apply this to a class.&lt;/p&gt;
&lt;p&gt;This will save memory.&lt;/p&gt;
&lt;p&gt;How much memory?&lt;/p&gt;
&lt;p&gt;It's hard to say, because you need to know the &amp;quot;without &lt;tt class="docutils literal"&gt;slots=True&lt;/tt&gt;&amp;quot; and &amp;quot;with &lt;tt class="docutils literal"&gt;slots=True&lt;/tt&gt;&amp;quot; sizes.
Generally, &lt;tt class="docutils literal"&gt;slots=True&lt;/tt&gt; will be smaller.
If you've chosen the right class to shrink, you may find improved performance, also.&lt;/p&gt;
&lt;p&gt;To determine the savings, we need to know the actual size of the collection of objects that are crushing the life out of our application and leading to &lt;tt class="docutils literal"&gt;MemoryError&lt;/tt&gt; exceptions.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="the-object-size-problem"&gt;
&lt;h2&gt;The Object Size Problem&lt;/h2&gt;
&lt;p&gt;The &lt;tt class="docutils literal"&gt;sys&lt;/tt&gt; module has a function, &lt;tt class="docutils literal"&gt;sys.getsizeof()&lt;/tt&gt;, that will provide the size of an object.&lt;/p&gt;
&lt;p&gt;This is the object &lt;strong&gt;in isolation&lt;/strong&gt;.&lt;/p&gt;
&lt;div class="admonition important"&gt;
&lt;p class="first admonition-title"&gt;Important&lt;/p&gt;
&lt;p class="last"&gt;&lt;tt class="docutils literal"&gt;sys.getsizeof()&lt;/tt&gt; doesn't include contained objects&lt;/p&gt;
&lt;/div&gt;
&lt;p&gt;What does it matter of it doesn't include contained objects?&lt;/p&gt;
&lt;p&gt;Consider a &lt;tt class="docutils literal"&gt;list[int]&lt;/tt&gt;.&lt;/p&gt;
&lt;pre class="literal-block"&gt;
&amp;gt;&amp;gt;&amp;gt; import sys
&amp;gt;&amp;gt;&amp;gt; small_list = list(range(10))
&amp;gt;&amp;gt;&amp;gt; large_list = list(range(10_000))
&amp;gt;&amp;gt;&amp;gt; sys.getsizeof(small_list)
136
&amp;gt;&amp;gt;&amp;gt; sys.getsizeof(large_list)
80056
&lt;/pre&gt;
&lt;p&gt;Okay. Superficially, it seems like each integer in the small list takes up about 13 bytes.&lt;/p&gt;
&lt;p&gt;Weirdly, each integer in the large list seems to take about 8 bytes.&lt;/p&gt;
&lt;p&gt;This can't be right.&lt;/p&gt;
&lt;p&gt;Consider an &lt;tt class="docutils literal"&gt;int&lt;/tt&gt;.&lt;/p&gt;
&lt;pre class="literal-block"&gt;
&amp;gt;&amp;gt;&amp;gt; sys.getsizeof(42)
28
&lt;/pre&gt;
&lt;p&gt;Okay.
That's really, really weird.&lt;/p&gt;
&lt;p&gt;The &lt;tt class="docutils literal"&gt;list[int]&lt;/tt&gt; size is the size of the &lt;tt class="docutils literal"&gt;list&lt;/tt&gt; object.
It doesn't include the 10 (or 10,000) &lt;tt class="docutils literal"&gt;int&lt;/tt&gt; objects that are members of the list.&lt;/p&gt;
&lt;p&gt;Total memory for the short list, then is &lt;span class="math"&gt;\(136 + 10 \times 28 = 416\)&lt;/span&gt;.
Total memory for the large list would be &lt;span class="math"&gt;\(80,\!056 + 10,\!000 \times 28 = 360,\!056\)&lt;/span&gt;.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="the-documentation-says"&gt;
&lt;h2&gt;The Documentation Says&lt;/h2&gt;
&lt;p&gt;Check the documentation for &lt;tt class="docutils literal"&gt;sys.getsizeof()&lt;/tt&gt;. You'll see this.&lt;/p&gt;
&lt;blockquote&gt;
&amp;quot;See &lt;a class="reference external" href="https://code.activestate.com/recipes/577504-compute-memory-footprint-of-an-object-and-its-cont/"&gt;recursive sizeof recipe&lt;/a&gt; for an example of using getsizeof() recursively to find the size of containers and all their contents.&amp;quot;&lt;/blockquote&gt;
&lt;p&gt;The documentation doesn't say &amp;quot;And read all the comments and integrate all those ideas into one function.&amp;quot;&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="what-i-did"&gt;
&lt;h2&gt;What I Did&lt;/h2&gt;
&lt;p&gt;This function computes the total size of just about anything.
This includes all the built-in collections.
It also includes &amp;quot;custom classes&amp;quot;, both the &lt;tt class="docutils literal"&gt;__slots__&lt;/tt&gt; and the non-&lt;tt class="docutils literal"&gt;__slots__&lt;/tt&gt; variants.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&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;deque&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;collections.abc&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Sequence&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Mapping&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Set&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Callable&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Iterator&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;sys&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;textwrap&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;shorten&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;typing&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Any&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;get_object_size&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;some_object&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Any&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;additional_types&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Callable&lt;/span&gt;&lt;span class="p"&gt;[[&lt;/span&gt;&lt;span class="n"&gt;Any&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="kc"&gt;None&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="kc"&gt;None&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;None&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;verbose&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;bool&lt;/span&gt; &lt;span class="o"&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="o"&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="w"&gt;    &lt;/span&gt;&lt;span class="sd"&gt;&amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;
&lt;span class="sd"&gt;    Computes the size of the given object.&lt;/span&gt;
&lt;span class="sd"&gt;    This expands on the recipe cited in the documentation for :py:func:`sys.getsizeof`.&lt;/span&gt;

&lt;span class="sd"&gt;    :param some_object: Any Python object.&lt;/span&gt;
&lt;span class="sd"&gt;    :param additional_types: A function that can return the size for an object for a type not handled here.&lt;/span&gt;
&lt;span class="sd"&gt;    :param verbose: True to print object information as the size is computed.&lt;/span&gt;
&lt;span class="sd"&gt;    :return: aggregate size of the object and all the related objects.&lt;/span&gt;

&lt;span class="sd"&gt;    The sizes are **highly** implementation specific.&lt;/span&gt;

&lt;span class="sd"&gt;    The types handled here are the built-in collections&lt;/span&gt;
&lt;span class="sd"&gt;    defined in :py:mod:`collections.abc`:&lt;/span&gt;
&lt;span class="sd"&gt;    ``str``, ``Sequence``, ``Set``, ``Mapping``.&lt;/span&gt;
&lt;span class="sd"&gt;    Additionally, this will look at any instance of class derived from :py:class:`object`,&lt;/span&gt;
&lt;span class="sd"&gt;    handling the default ``__dict__`` as well as ``__slots__``.&lt;/span&gt;

&lt;span class="sd"&gt;    &amp;gt;&amp;gt;&amp;gt; get_object_size(&amp;quot;Hello, world!&amp;quot;)&lt;/span&gt;
&lt;span class="sd"&gt;    54&lt;/span&gt;
&lt;span class="sd"&gt;    &amp;gt;&amp;gt;&amp;gt; get_object_size(&amp;quot;!&amp;quot;)&lt;/span&gt;
&lt;span class="sd"&gt;    42&lt;/span&gt;
&lt;span class="sd"&gt;    &amp;gt;&amp;gt;&amp;gt; get_object_size(list(range(10)))&lt;/span&gt;
&lt;span class="sd"&gt;    416&lt;/span&gt;
&lt;span class="sd"&gt;    &amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;
    &lt;span class="n"&gt;default_size&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;sys&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;getsizeof&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;seen&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;size&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;elements&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;deque&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="n"&gt;some_object&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
    &lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="n"&gt;elements&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;obj&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;elements&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;popleft&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;id&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="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;seen&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;continue&lt;/span&gt;
        &lt;span class="n"&gt;seen&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;id&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;obj&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;verbose&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="sa"&gt;f&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nb"&gt;id&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="si"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;8x&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; &lt;/span&gt;&lt;span class="si"&gt;{&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;obj&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;, &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;shorten&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;repr&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="mi"&gt;32&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;file&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;sys&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;stderr&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="n"&gt;sys&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;getsizeof&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="n"&gt;default_size&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;match&lt;/span&gt; &lt;span class="n"&gt;obj&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
                &lt;span class="k"&gt;pass&lt;/span&gt;
            &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="n"&gt;Sequence&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="n"&gt;Set&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="n"&gt;extend&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;iter&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="k"&gt;case&lt;/span&gt; &lt;span class="n"&gt;Mapping&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="n"&gt;extend&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;obj&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;elements&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;extend&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;obj&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="k"&gt;case&lt;/span&gt; &lt;span class="nb"&gt;object&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;hasattr&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="s2"&gt;&amp;quot;__dict__&amp;quot;&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="n"&gt;extend&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;obj&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="vm"&gt;__dict__&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;elements&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;extend&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;obj&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="vm"&gt;__dict__&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="k"&gt;case&lt;/span&gt; &lt;span class="nb"&gt;object&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;hasattr&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="s2"&gt;&amp;quot;__slots__&amp;quot;&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="n"&gt;extend&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                    &lt;span class="nb"&gt;getattr&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="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;obj&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="vm"&gt;__slots__&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nb"&gt;hasattr&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="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                &lt;span class="p"&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;_&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;additional_types&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;obj_size&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;additional_types&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="ow"&gt;is&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="kc"&gt;None&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="n"&gt;obj_size&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;size&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Note that this walks an entire structure without &lt;em&gt;actually&lt;/em&gt; being recursive.
If you've got a complicated application, and a &lt;strong&gt;very&lt;/strong&gt; deeply-nested data structure,
the overhead of a lot of stack frames may be unmanageable.&lt;/p&gt;
&lt;p&gt;(There are other optimization approaches to this problem.)&lt;/p&gt;
&lt;p&gt;This assumes that a collection &lt;strong&gt;always&lt;/strong&gt; contains heterogeneous types.
This means computing the size of each item in the list.&lt;/p&gt;
&lt;p&gt;This uses a big &lt;tt class="docutils literal"&gt;deque&lt;/tt&gt;, which can involve impossible overhead, also.&lt;/p&gt;
&lt;p&gt;In some cases, you may need to create a more complicated special-purpose benchmark app that builds your big data structure using your distinct storage alternatives.
Use your special benchmark test-bed to uncover the implementation that meets all the criteria for storage use and CPU time.&lt;/p&gt;
&lt;p&gt;The data that is used for the benchmark would need to reflect real-world data with respect to string lengths, and collection sizes.
Creating synthetic data for an object size benchmark can be a challenge.
See &lt;a class="reference external" href="https://slott56.github.io/2024-06-29-synthetic_data.html"&gt;Synthetic Data&lt;/a&gt;.
And, also see &lt;a class="reference external" href="https://slott56.github.io/2024-07-25-synthetic_data_tool.html"&gt;Synthetic Data Tools&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;You won't often need this.
But. I've posted it here so I won't lose it.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="todo"&gt;
&lt;h2&gt;TODO&lt;/h2&gt;
&lt;p&gt;Handle &lt;tt class="docutils literal"&gt;numpy&lt;/tt&gt; types, also.&lt;/p&gt;
&lt;/div&gt;
&lt;script type='text/javascript'&gt;if (!document.getElementById('mathjaxscript_pelican_#%@#$@#')) {
    var align = "center",
        indent = "0em",
        linebreak = "false";

    if (false) {
        align = (screen.width &lt; 768) ? "left" : align;
        indent = (screen.width &lt; 768) ? "0em" : indent;
        linebreak = (screen.width &lt; 768) ? 'true' : linebreak;
    }

    var mathjaxscript = document.createElement('script');
    mathjaxscript.id = 'mathjaxscript_pelican_#%@#$@#';
    mathjaxscript.type = 'text/javascript';
    mathjaxscript.src = 'https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.3/latest.js?config=TeX-AMS-MML_HTMLorMML';

    var configscript = document.createElement('script');
    configscript.type = 'text/x-mathjax-config';
    configscript[(window.opera ? "innerHTML" : "text")] =
        "MathJax.Hub.Config({" +
        "    config: ['MMLorHTML.js']," +
        "    TeX: { extensions: ['AMSmath.js','AMSsymbols.js','noErrors.js','noUndefined.js'], equationNumbers: { autoNumber: 'none' } }," +
        "    jax: ['input/TeX','input/MathML','output/HTML-CSS']," +
        "    extensions: ['tex2jax.js','mml2jax.js','MathMenu.js','MathZoom.js']," +
        "    displayAlign: '"+ align +"'," +
        "    displayIndent: '"+ indent +"'," +
        "    showMathMenu: true," +
        "    messageStyle: 'normal'," +
        "    tex2jax: { " +
        "        inlineMath: [ ['\\\\(','\\\\)'] ], " +
        "        displayMath: [ ['$$','$$'] ]," +
        "        processEscapes: true," +
        "        preview: 'TeX'," +
        "    }, " +
        "    'HTML-CSS': { " +
        "        availableFonts: ['STIX', 'TeX']," +
        "        preferredFont: 'STIX'," +
        "        styles: { '.MathJax_Display, .MathJax .mo, .MathJax .mi, .MathJax .mn': {color: 'inherit ! important'} }," +
        "        linebreaks: { automatic: "+ linebreak +", width: '90% container' }," +
        "    }, " +
        "}); " +
        "if ('default' !== 'default') {" +
            "MathJax.Hub.Register.StartupHook('HTML-CSS Jax Ready',function () {" +
                "var VARIANT = MathJax.OutputJax['HTML-CSS'].FONTDATA.VARIANT;" +
                "VARIANT['normal'].fonts.unshift('MathJax_default');" +
                "VARIANT['bold'].fonts.unshift('MathJax_default-bold');" +
                "VARIANT['italic'].fonts.unshift('MathJax_default-italic');" +
                "VARIANT['-tex-mathit'].fonts.unshift('MathJax_default-italic');" +
            "});" +
            "MathJax.Hub.Register.StartupHook('SVG Jax Ready',function () {" +
                "var VARIANT = MathJax.OutputJax.SVG.FONTDATA.VARIANT;" +
                "VARIANT['normal'].fonts.unshift('MathJax_default');" +
                "VARIANT['bold'].fonts.unshift('MathJax_default-bold');" +
                "VARIANT['italic'].fonts.unshift('MathJax_default-italic');" +
                "VARIANT['-tex-mathit'].fonts.unshift('MathJax_default-italic');" +
            "});" +
        "}";

    (document.body || document.getElementsByTagName('head')[0]).appendChild(configscript);
    (document.body || document.getElementsByTagName('head')[0]).appendChild(mathjaxscript);
}
&lt;/script&gt;</content><category term="Python"></category><category term="python"></category><category term="oo"></category><category term="oodesign"></category></entry><entry><title>OO Design Principles: GRASP patterns</title><link href="https://slott56.github.io/2025-10-04-oo_design_principles_grasp_patterns.html" rel="alternate"></link><published>2025-10-04T09:46:00-04:00</published><updated>2025-10-04T09:46:00-04:00</updated><author><name>S.Lott</name></author><id>tag:slott56.github.io,2025-10-04:/2025-10-04-oo_design_principles_grasp_patterns.html</id><summary type="html">&lt;!-- background:

I’ve been reading quite a lot in the book “Fluent Python”. It's a brilliant resource and is helping me understand details of Python that I hadn't even looked at before. In the last few chapters I read several times that ideally I should avoid inheritance with ABCs and if I do, then from a standard class. That is, I should avoid creating a typical base and subclass construct that is used in a strategy pattern, for example.

If I look at a principle like SOLID, where we have the Liskov Substitution Principle, the interface separation principle, and the dependency inversion principle, they all rely on a base class, which of course is created by inheriting from ABC. I think as long as we do not have concrete methods in the base class, this could be a protocol instead.

However, I've been thinking about the general meaning of SOLID for Python. I can see these principles in an object-oriented only language like Java. Since this is the first language I learned, my intuition tells me that the language is perfect for following the principles. Python, on the other hand, does not have the strong need to be object-oriented, but most of the principles assume that we are working with classes. Often it's just easier to use a functional approach. An example would be the strategy pattern, where instead of having a construct of different classes that handle a specific strategy, we just have different functions. The intent for a developer is very explicit when using those, and I don't see the need for an ABC and inheritance from it. The only advantage I would see is to put it in a class that uses Protocols to enable type hints.

I've also read about the GRASP principles, but some parts of it don't feel very natural to me in Python either. For example, very obviously, polymorphism, which is also not needed in Python. Of course, low coupling and high cohesion are rather language-agnostic concepts, and they seem to fit very well with Python.

I know that these principles are not a religion, and I don't have to follow any specific one. I know I can take ideas from multiple ones and stack them together. However, I like the basic idea of dependency inversion, interface segregation and Liskov Substitution and used it in one of my products. Working with Liskov Substitution can make it difficult to navigate through an IDE. This plus explanations I’ve read in Fluent Python make me think that I've made my life more difficult with it, but I wonder what would be pythonic here?

Since I couldn't find a helpful discussion online, I was wondering what others experience is and how they approach this? Do you follow any principles like SOLID or GRASP or do you find a mix to be the best option? I've been looking for articles that discuss this but haven't found a great resource. I'd love to hear your thoughts and experiences and if you know of a great resource. --&gt;
&lt;p&gt;Some quotes to provide context.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&amp;quot;I read several times that ideally I should avoid inheritance with ABCs and if I do, then from a standard class. That is, I should avoid creating a typical base and subclass construct that is used in a strategy pattern, for example.&amp;quot;&lt;/p&gt;
&lt;p&gt;&amp;quot;If I look …&lt;/p&gt;&lt;/blockquote&gt;</summary><content type="html">&lt;!-- background:

I’ve been reading quite a lot in the book “Fluent Python”. It's a brilliant resource and is helping me understand details of Python that I hadn't even looked at before. In the last few chapters I read several times that ideally I should avoid inheritance with ABCs and if I do, then from a standard class. That is, I should avoid creating a typical base and subclass construct that is used in a strategy pattern, for example.

If I look at a principle like SOLID, where we have the Liskov Substitution Principle, the interface separation principle, and the dependency inversion principle, they all rely on a base class, which of course is created by inheriting from ABC. I think as long as we do not have concrete methods in the base class, this could be a protocol instead.

However, I've been thinking about the general meaning of SOLID for Python. I can see these principles in an object-oriented only language like Java. Since this is the first language I learned, my intuition tells me that the language is perfect for following the principles. Python, on the other hand, does not have the strong need to be object-oriented, but most of the principles assume that we are working with classes. Often it's just easier to use a functional approach. An example would be the strategy pattern, where instead of having a construct of different classes that handle a specific strategy, we just have different functions. The intent for a developer is very explicit when using those, and I don't see the need for an ABC and inheritance from it. The only advantage I would see is to put it in a class that uses Protocols to enable type hints.

I've also read about the GRASP principles, but some parts of it don't feel very natural to me in Python either. For example, very obviously, polymorphism, which is also not needed in Python. Of course, low coupling and high cohesion are rather language-agnostic concepts, and they seem to fit very well with Python.

I know that these principles are not a religion, and I don't have to follow any specific one. I know I can take ideas from multiple ones and stack them together. However, I like the basic idea of dependency inversion, interface segregation and Liskov Substitution and used it in one of my products. Working with Liskov Substitution can make it difficult to navigate through an IDE. This plus explanations I’ve read in Fluent Python make me think that I've made my life more difficult with it, but I wonder what would be pythonic here?

Since I couldn't find a helpful discussion online, I was wondering what others experience is and how they approach this? Do you follow any principles like SOLID or GRASP or do you find a mix to be the best option? I've been looking for articles that discuss this but haven't found a great resource. I'd love to hear your thoughts and experiences and if you know of a great resource. --&gt;
&lt;p&gt;Some quotes to provide context.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&amp;quot;I read several times that ideally I should avoid inheritance with ABCs and if I do, then from a standard class. That is, I should avoid creating a typical base and subclass construct that is used in a strategy pattern, for example.&amp;quot;&lt;/p&gt;
&lt;p&gt;&amp;quot;If I look at a principle like SOLID, where we have the Liskov Substitution Principle, the interface separation principle, and the dependency inversion principle, they all rely on a base class, which of course is created by inheriting from ABC.&amp;quot;&lt;/p&gt;
&lt;p&gt;&amp;quot;Python, on the other hand, does not have the strong need to be object-oriented, but most of the principles assume that we are working with classes.&amp;quot;&lt;/p&gt;
&lt;p&gt;&amp;quot;Working with Liskov Substitution can make it difficult to navigate through an IDE.&amp;quot;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;div class="section" id="bluf"&gt;
&lt;h2&gt;BLUF&lt;/h2&gt;
&lt;p&gt;The GRASP patterns can be helpful reminders.&lt;/p&gt;
&lt;p&gt;Some GRASP patterns sound like design principles, and seem to overlap with the SOLID principles.&lt;/p&gt;
&lt;p&gt;Other GRASP patterns seem like summaries, with a number of fine-grained implementation choices.&lt;/p&gt;
&lt;p&gt;Controller and Pure Fabrication seem most helpful.&lt;/p&gt;
&lt;p&gt;The &amp;quot;Gang of Four&amp;quot; Object-Oriented Design Patterns seem to be more useful and have more supporting details.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="grasp-patterns"&gt;
&lt;h2&gt;GRASP Patterns&lt;/h2&gt;
&lt;p&gt;See &lt;a class="reference external" href="https://www.geeksforgeeks.org/system-design/grasp-design-principles-in-ooad/"&gt;https://www.geeksforgeeks.org/system-design/grasp-design-principles-in-ooad/&lt;/a&gt;&lt;/p&gt;
&lt;div class="section" id="controller"&gt;
&lt;h3&gt;Controller&lt;/h3&gt;
&lt;p&gt;See also the &amp;quot;Model-View-Control&amp;quot; pattern.&lt;/p&gt;
&lt;p&gt;There's an internal model of reality. A description of the real-world stuff.
Maybe this is simply CRUD rules over a database (as if a database is reality -- it's only a model.)
Maybe this is an OO model that gets serialized into something persistent when the application exits (you know, like a word processor or spreadsheet.)
Maybe this is an OO model of a game where there's a state-of-play saved when you specifically ask to save.
Maybe this is a real-time controller of a device like a cruise control on a car where the model has the car's current speed and the target speed (and not much else, TBH.)
Maybe this is an anchoring position indicator with a position where the boat is expected to be and the most recent few minutes of GPS positions indicating if it's within a safe distance of the anchor or not and what direction the boat seems to be going (i.e., it is adrift?)&lt;/p&gt;
&lt;p&gt;There's a view of reality, displaying the state of the model.
You fiddle with the view to update the model.
Maybe look at the number on the dashboard or push buttons on the cruise control.
Maybe type a bunch of stuff into fields on a form.&lt;/p&gt;
&lt;p&gt;And there's the controller.&lt;/p&gt;
&lt;blockquote&gt;
&amp;quot;The controller is defined as the first object beyond the UI layer that receives and coordinates (&amp;quot;controls&amp;quot;) a system operation. &amp;quot;&lt;/blockquote&gt;
&lt;p&gt;This applies widely.
In some applications -- programs like &lt;tt class="docutils literal"&gt;grep&lt;/tt&gt; and &lt;tt class="docutils literal"&gt;sed&lt;/tt&gt; -- the UI is really tiny and not interactive.&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;The input is command-line parsing (via &lt;tt class="docutils literal"&gt;argparse&lt;/tt&gt; or &lt;tt class="docutils literal"&gt;click&lt;/tt&gt; or whatever).&lt;/li&gt;
&lt;li&gt;The output is stdout and stderr (via &lt;tt class="docutils literal"&gt;logging&lt;/tt&gt; or &lt;tt class="docutils literal"&gt;print()&lt;/tt&gt; or whatever).&lt;/li&gt;
&lt;li&gt;There's a model (the file being processed, the text in that file, the patterns and commands).&lt;/li&gt;
&lt;li&gt;There's a &lt;strong&gt;tiny&lt;/strong&gt; controller that gets the input, then iterates through the model's visible state changes, writing the outputs.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This is one essence of &lt;strong&gt;Clean Design&lt;/strong&gt;. Separating the inputs and outputs from the model and control.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="creator"&gt;
&lt;h3&gt;Creator&lt;/h3&gt;
&lt;p&gt;There's potential overlap between Creator and &lt;a class="reference internal" href="#information-expert"&gt;Information Expert&lt;/a&gt;.
Creators have the information required to create an instance of a collaborator.
Which sometimes means the creator is also an expert.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="indirection"&gt;
&lt;h3&gt;Indirection&lt;/h3&gt;
&lt;p&gt;Sometimes helpful. The Gang-of-Four design patterns subdivide this many ways.
You might have an Adapter or Facade to wrap one (or more) collaborators.
You might have a State or Strategy class hierarchy that wraps up alternative implementation choices into a tidy structure.&lt;/p&gt;
&lt;p&gt;Note there are two layers of indirection.
Objects can have indirect access to other objects, perhaps mediated by the current state.
Classes &lt;strong&gt;should&lt;/strong&gt; have indirect access to other class definitions, per the SOLID &lt;strong&gt;Dependency Inversion Principle&lt;/strong&gt;.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="information-expert"&gt;
&lt;h3&gt;Information Expert&lt;/h3&gt;
&lt;p&gt;The SOLID &lt;strong&gt;Single Responsibility Principle&lt;/strong&gt;, restated. Nice to see everyone agrees on this.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="low-coupling"&gt;
&lt;h3&gt;Low Coupling&lt;/h3&gt;
&lt;p&gt;This is a desirable feature of object design.
This is more of a principle that echoes the SOLID &lt;strong&gt;Interface Segregation Principle&lt;/strong&gt;.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="high-cohesion"&gt;
&lt;h3&gt;High Cohesion&lt;/h3&gt;
&lt;p&gt;This is also a very desirable feature of object design.
This isn't (explicitly) a SOLID design principle, but lurks in she shadows of the SOLID &lt;strong&gt;Single Responsibility Principle&lt;/strong&gt;.
In order to have a single Responsibility, there must be a cohesive design to the class.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="polymorphism"&gt;
&lt;h3&gt;Polymorphism&lt;/h3&gt;
&lt;p&gt;This is a mechanism as well as a &lt;strong&gt;large&lt;/strong&gt; number of patterns.
The mechanism is a way to reuse code in a class hierarchy.
It saves repeating things when we have related classes.&lt;/p&gt;
&lt;p&gt;The reason why we have a SOLID &lt;strong&gt;Liskov Substitution Principle&lt;/strong&gt; is to manage polymorphism.&lt;/p&gt;
&lt;p&gt;The entire Gang-of-Four design patterns book is an extended set of patterns for applying polymorphism.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="protected-variations"&gt;
&lt;h3&gt;Protected Variations&lt;/h3&gt;
&lt;p&gt;This is a restatement of the SOLID &lt;strong&gt;Open/Closed Principle&lt;/strong&gt;.
Open to extension (via variations.)
Closed to modification (the &amp;quot;protected&amp;quot; part of this.)&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="pure-fabrication"&gt;
&lt;h3&gt;Pure Fabrication&lt;/h3&gt;
&lt;p&gt;I often call these fabricated classes part of the &amp;quot;Solution Domain&amp;quot; distinct from the &amp;quot;Problem Domain.&amp;quot;&lt;/p&gt;
&lt;p&gt;The model is part of the Problem Domain.
It represents the real-world things in software space.
This should map to the problem domain with a high degree of fidelity.&lt;/p&gt;
&lt;p&gt;The view presents this to users.
It's part of the UI, which is part of the solution.
It can overlap with the problem domain, however, and may happen to match the model.
A CRUD application, for example, may display the data more-or-less directly and the view may match the model.&lt;/p&gt;
&lt;p&gt;In some cases, the view twists and transforms the model into what people think they want to see.
Think of the aggregated data in a data warehouse.
Details are elided. It's a summary -- it's not the reality.
A great deal of twisting and turning happens during ETL and aggregation processing to provide a view the users can understand and act on.&lt;/p&gt;
&lt;p&gt;A controller, however, has nothing to do with the problem domain.
It's &lt;em&gt;purely&lt;/em&gt; part of the solution domain.
It reflects the purpose behind the software -- show status, update things, allow interaction, summarize, whatever.&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;The GRASP patterns can be helpful reminders of different kinds of solutions.&lt;/p&gt;
&lt;p&gt;They overlap with -- and can provide some amplification for -- the SOLID design patterns.&lt;/p&gt;
&lt;p&gt;The Pure Fabrication and Controller GRASP patterns seem to be more significant than the others.&lt;/p&gt;
&lt;/div&gt;
</content><category term="Python"></category><category term="python"></category><category term="oo"></category><category term="oodesign"></category><category term="design principles"></category><category term="grasp"></category><category term="patterns"></category></entry><entry><title>OO Design Principles: CUPID</title><link href="https://slott56.github.io/2025-10-03-oo_design_principles_cupid.html" rel="alternate"></link><published>2025-10-03T09:46:00-04:00</published><updated>2025-10-03T09:46:00-04:00</updated><author><name>S.Lott</name></author><id>tag:slott56.github.io,2025-10-03:/2025-10-03-oo_design_principles_cupid.html</id><summary type="html">&lt;!-- background:

I’ve been reading quite a lot in the book “Fluent Python”. It's a brilliant resource and is helping me understand details of Python that I hadn't even looked at before. In the last few chapters I read several times that ideally I should avoid inheritance with ABCs and if I do, then from a standard class. That is, I should avoid creating a typical base and subclass construct that is used in a strategy pattern, for example.

If I look at a principle like SOLID, where we have the Liskov Substitution Principle, the interface separation principle, and the dependency inversion principle, they all rely on a base class, which of course is created by inheriting from ABC. I think as long as we do not have concrete methods in the base class, this could be a protocol instead.

However, I've been thinking about the general meaning of SOLID for Python. I can see these principles in an object-oriented only language like Java. Since this is the first language I learned, my intuition tells me that the language is perfect for following the principles. Python, on the other hand, does not have the strong need to be object-oriented, but most of the principles assume that we are working with classes. Often it's just easier to use a functional approach. An example would be the strategy pattern, where instead of having a construct of different classes that handle a specific strategy, we just have different functions. The intent for a developer is very explicit when using those, and I don't see the need for an ABC and inheritance from it. The only advantage I would see is to put it in a class that uses Protocols to enable type hints.

I've also read about the GRASP principles, but some parts of it don't feel very natural to me in Python either. For example, very obviously, polymorphism, which is also not needed in Python. Of course, low coupling and high cohesion are rather language-agnostic concepts, and they seem to fit very well with Python.

I know that these principles are not a religion, and I don't have to follow any specific one. I know I can take ideas from multiple ones and stack them together. However, I like the basic idea of dependency inversion, interface segregation and Liskov Substitution and used it in one of my products. Working with Liskov Substitution can make it difficult to navigate through an IDE. This plus explanations I’ve read in Fluent Python make me think that I've made my life more difficult with it, but I wonder what would be pythonic here?

Since I couldn't find a helpful discussion online, I was wondering what others experience is and how they approach this? Do you follow any principles like SOLID or GRASP or do you find a mix to be the best option? I've been looking for articles that discuss this but haven't found a great resource. I'd love to hear your thoughts and experiences and if you know of a great resource. --&gt;
&lt;p&gt;Some quotes to provide context.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&amp;quot;I read several times that ideally I should avoid inheritance with ABCs and if I do, then from a standard class. That is, I should avoid creating a typical base and subclass construct that is used in a strategy pattern, for example.&amp;quot;&lt;/p&gt;
&lt;p&gt;&amp;quot;If I look …&lt;/p&gt;&lt;/blockquote&gt;</summary><content type="html">&lt;!-- background:

I’ve been reading quite a lot in the book “Fluent Python”. It's a brilliant resource and is helping me understand details of Python that I hadn't even looked at before. In the last few chapters I read several times that ideally I should avoid inheritance with ABCs and if I do, then from a standard class. That is, I should avoid creating a typical base and subclass construct that is used in a strategy pattern, for example.

If I look at a principle like SOLID, where we have the Liskov Substitution Principle, the interface separation principle, and the dependency inversion principle, they all rely on a base class, which of course is created by inheriting from ABC. I think as long as we do not have concrete methods in the base class, this could be a protocol instead.

However, I've been thinking about the general meaning of SOLID for Python. I can see these principles in an object-oriented only language like Java. Since this is the first language I learned, my intuition tells me that the language is perfect for following the principles. Python, on the other hand, does not have the strong need to be object-oriented, but most of the principles assume that we are working with classes. Often it's just easier to use a functional approach. An example would be the strategy pattern, where instead of having a construct of different classes that handle a specific strategy, we just have different functions. The intent for a developer is very explicit when using those, and I don't see the need for an ABC and inheritance from it. The only advantage I would see is to put it in a class that uses Protocols to enable type hints.

I've also read about the GRASP principles, but some parts of it don't feel very natural to me in Python either. For example, very obviously, polymorphism, which is also not needed in Python. Of course, low coupling and high cohesion are rather language-agnostic concepts, and they seem to fit very well with Python.

I know that these principles are not a religion, and I don't have to follow any specific one. I know I can take ideas from multiple ones and stack them together. However, I like the basic idea of dependency inversion, interface segregation and Liskov Substitution and used it in one of my products. Working with Liskov Substitution can make it difficult to navigate through an IDE. This plus explanations I’ve read in Fluent Python make me think that I've made my life more difficult with it, but I wonder what would be pythonic here?

Since I couldn't find a helpful discussion online, I was wondering what others experience is and how they approach this? Do you follow any principles like SOLID or GRASP or do you find a mix to be the best option? I've been looking for articles that discuss this but haven't found a great resource. I'd love to hear your thoughts and experiences and if you know of a great resource. --&gt;
&lt;p&gt;Some quotes to provide context.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&amp;quot;I read several times that ideally I should avoid inheritance with ABCs and if I do, then from a standard class. That is, I should avoid creating a typical base and subclass construct that is used in a strategy pattern, for example.&amp;quot;&lt;/p&gt;
&lt;p&gt;&amp;quot;If I look at a principle like SOLID, where we have the Liskov Substitution Principle, the interface separation principle, and the dependency inversion principle, they all rely on a base class, which of course is created by inheriting from ABC.&amp;quot;&lt;/p&gt;
&lt;p&gt;&amp;quot;Python, on the other hand, does not have the strong need to be object-oriented, but most of the principles assume that we are working with classes.&amp;quot;&lt;/p&gt;
&lt;p&gt;&amp;quot;Working with Liskov Substitution can make it difficult to navigate through an IDE.&amp;quot;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;div class="section" id="bluf"&gt;
&lt;h2&gt;BLUF&lt;/h2&gt;
&lt;p&gt;Very important: Composable, Predictable, Domain-based.&lt;/p&gt;
&lt;p&gt;These are consistent with the idea that software is knowledge capture.&lt;/p&gt;
&lt;p&gt;Maybe less important: Unix-Philosophy and Idiomatic.
While nice ideas, they feel a bit redundant.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="cupid"&gt;
&lt;h2&gt;CUPID&lt;/h2&gt;
&lt;p&gt;See &lt;a class="reference external" href="https://cupid.dev/properties/"&gt;https://cupid.dev/properties/&lt;/a&gt;&lt;/p&gt;
&lt;div class="section" id="composable"&gt;
&lt;h3&gt;Composable&lt;/h3&gt;
&lt;p&gt;Highly desirable to have small pieces that compose into something useful.
This fits with SOLID &lt;strong&gt;Interface Segregation Principle&lt;/strong&gt;, and provides an answer to &amp;quot;but, why?&amp;quot;
We segregate the interfaces to create something composable.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="unix-philosophy"&gt;
&lt;h3&gt;Unix-Philosophy&lt;/h3&gt;
&lt;p&gt;This &lt;strong&gt;also&lt;/strong&gt; implies composability.
It goes a step further to incorporate the idea of the SOLID Single Responsibility Principle.
The Unix design patterns provides concrete examples of how to decompose a large problem into smaller pieces;
each piece does one thing well.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="predictable"&gt;
&lt;h3&gt;Predictable&lt;/h3&gt;
&lt;p&gt;This decomposes into three distinct aspects:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;A component behaves as expected. This is sometimes called the Principle of Least Astonishment.&lt;/li&gt;
&lt;li&gt;A component's behavior is consistent and deterministic.&lt;/li&gt;
&lt;li&gt;A component's behavior needs to be observable, also.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;These are all critical features, especially observability.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="idiomatic"&gt;
&lt;h3&gt;Idiomatic&lt;/h3&gt;
&lt;p&gt;Yes, follow language idioms. Please do not boldly go where no programmer has gone before.&lt;/p&gt;
&lt;p&gt;This seems to go without saying. It does help fill up the acronym, though.
And, perhaps, it's necessary advice.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="domain-based"&gt;
&lt;h3&gt;Domain-based&lt;/h3&gt;
&lt;p&gt;The solution code should mirror the problem domain. This is really important, and not present in other sets of design principles.&lt;/p&gt;
&lt;p&gt;Indeed, I don't think it can be emphasized enough.&lt;/p&gt;
&lt;p&gt;OO modeling and design isn't about optimization or code reuse.
It's about fidelity to the problem domain.&lt;/p&gt;
&lt;p&gt;Code reuse is nice to have.
Providing common behavior among the problem domain objects that are being modelled is the point.&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;These are nice. They're very useful in conjunction with SOLID design principles.&lt;/p&gt;
&lt;p&gt;It helps to have guideposts to help clarify an underlying &amp;quot;why&amp;quot; we design software.&lt;/p&gt;
&lt;/div&gt;
</content><category term="Python"></category><category term="python"></category><category term="oo"></category><category term="oodesign"></category><category term="design principles"></category><category term="cupid"></category></entry><entry><title>OO Design Principles: SOLID</title><link href="https://slott56.github.io/2025-10-02-oo_design_principles_solid.html" rel="alternate"></link><published>2025-10-02T09:46:00-04:00</published><updated>2025-10-02T09:46:00-04:00</updated><author><name>S.Lott</name></author><id>tag:slott56.github.io,2025-10-02:/2025-10-02-oo_design_principles_solid.html</id><summary type="html">&lt;!-- background:

I’ve been reading quite a lot in the book “Fluent Python”. It's a brilliant resource and is helping me understand details of Python that I hadn't even looked at before. In the last few chapters I read several times that ideally I should avoid inheritance with ABCs and if I do, then from a standard class. That is, I should avoid creating a typical base and subclass construct that is used in a strategy pattern, for example.

If I look at a principle like SOLID, where we have the Liskov Substitution Principle, the interface separation principle, and the dependency inversion principle, they all rely on a base class, which of course is created by inheriting from ABC. I think as long as we do not have concrete methods in the base class, this could be a protocol instead.

However, I've been thinking about the general meaning of SOLID for Python. I can see these principles in an object-oriented only language like Java. Since this is the first language I learned, my intuition tells me that the language is perfect for following the principles. Python, on the other hand, does not have the strong need to be object-oriented, but most of the principles assume that we are working with classes. Often it's just easier to use a functional approach. An example would be the strategy pattern, where instead of having a construct of different classes that handle a specific strategy, we just have different functions. The intent for a developer is very explicit when using those, and I don't see the need for an ABC and inheritance from it. The only advantage I would see is to put it in a class that uses Protocols to enable type hints.

I've also read about the GRASP principles, but some parts of it don't feel very natural to me in Python either. For example, very obviously, polymorphism, which is also not needed in Python. Of course, low coupling and high cohesion are rather language-agnostic concepts, and they seem to fit very well with Python.

I know that these principles are not a religion, and I don't have to follow any specific one. I know I can take ideas from multiple ones and stack them together. However, I like the basic idea of dependency inversion, interface segregation and Liskov Substitution and used it in one of my products. Working with Liskov Substitution can make it difficult to navigate through an IDE. This plus explanations I’ve read in Fluent Python make me think that I've made my life more difficult with it, but I wonder what would be pythonic here?

Since I couldn't find a helpful discussion online, I was wondering what others experience is and how they approach this? Do you follow any principles like SOLID or GRASP or do you find a mix to be the best option? I've been looking for articles that discuss this but haven't found a great resource. I'd love to hear your thoughts and experiences and if you know of a great resource. --&gt;
&lt;p&gt;Some quotes to provide context.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&amp;quot;I read several times that ideally I should avoid inheritance with ABCs and if I do, then from a standard class. That is, I should avoid creating a typical base and subclass construct that is used in a strategy pattern, for example.&amp;quot;&lt;/p&gt;
&lt;p&gt;&amp;quot;If I look …&lt;/p&gt;&lt;/blockquote&gt;</summary><content type="html">&lt;!-- background:

I’ve been reading quite a lot in the book “Fluent Python”. It's a brilliant resource and is helping me understand details of Python that I hadn't even looked at before. In the last few chapters I read several times that ideally I should avoid inheritance with ABCs and if I do, then from a standard class. That is, I should avoid creating a typical base and subclass construct that is used in a strategy pattern, for example.

If I look at a principle like SOLID, where we have the Liskov Substitution Principle, the interface separation principle, and the dependency inversion principle, they all rely on a base class, which of course is created by inheriting from ABC. I think as long as we do not have concrete methods in the base class, this could be a protocol instead.

However, I've been thinking about the general meaning of SOLID for Python. I can see these principles in an object-oriented only language like Java. Since this is the first language I learned, my intuition tells me that the language is perfect for following the principles. Python, on the other hand, does not have the strong need to be object-oriented, but most of the principles assume that we are working with classes. Often it's just easier to use a functional approach. An example would be the strategy pattern, where instead of having a construct of different classes that handle a specific strategy, we just have different functions. The intent for a developer is very explicit when using those, and I don't see the need for an ABC and inheritance from it. The only advantage I would see is to put it in a class that uses Protocols to enable type hints.

I've also read about the GRASP principles, but some parts of it don't feel very natural to me in Python either. For example, very obviously, polymorphism, which is also not needed in Python. Of course, low coupling and high cohesion are rather language-agnostic concepts, and they seem to fit very well with Python.

I know that these principles are not a religion, and I don't have to follow any specific one. I know I can take ideas from multiple ones and stack them together. However, I like the basic idea of dependency inversion, interface segregation and Liskov Substitution and used it in one of my products. Working with Liskov Substitution can make it difficult to navigate through an IDE. This plus explanations I’ve read in Fluent Python make me think that I've made my life more difficult with it, but I wonder what would be pythonic here?

Since I couldn't find a helpful discussion online, I was wondering what others experience is and how they approach this? Do you follow any principles like SOLID or GRASP or do you find a mix to be the best option? I've been looking for articles that discuss this but haven't found a great resource. I'd love to hear your thoughts and experiences and if you know of a great resource. --&gt;
&lt;p&gt;Some quotes to provide context.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&amp;quot;I read several times that ideally I should avoid inheritance with ABCs and if I do, then from a standard class. That is, I should avoid creating a typical base and subclass construct that is used in a strategy pattern, for example.&amp;quot;&lt;/p&gt;
&lt;p&gt;&amp;quot;If I look at a principle like SOLID, where we have the Liskov Substitution Principle, the interface separation principle, and the dependency inversion principle, they all rely on a base class, which of course is created by inheriting from ABC.&amp;quot;&lt;/p&gt;
&lt;p&gt;&amp;quot;Python, on the other hand, does not have the strong need to be object-oriented, but most of the principles assume that we are working with classes.&amp;quot;&lt;/p&gt;
&lt;p&gt;&amp;quot;Working with Liskov Substitution can make it difficult to navigate through an IDE.&amp;quot;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;div class="section" id="bluf"&gt;
&lt;h2&gt;BLUF&lt;/h2&gt;
&lt;p&gt;Apply the design principles with a kind of priority mind-set.&lt;/p&gt;
&lt;ol class="arabic simple"&gt;
&lt;li&gt;Interface Segregation. Always.&lt;/li&gt;
&lt;li&gt;Liskov Substitution where you actually have base classes or protocols.&lt;/li&gt;
&lt;li&gt;Open/Closed. Always.&lt;/li&gt;
&lt;li&gt;Dependency &amp;quot;Inversion&amp;quot; (I prefer &amp;quot;Injection&amp;quot;). Think of this as an optimization.&lt;/li&gt;
&lt;li&gt;&lt;dl class="first docutils"&gt;
&lt;dt&gt;Single Responsibility. Here is where various GRASP patterns apply at this point here to help implement a &amp;quot;single&amp;quot; responsibility.&lt;/dt&gt;
&lt;dd&gt;Remember, GRASP are implementation patterns not quite the same as design principles.&lt;/dd&gt;
&lt;/dl&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;
&lt;div class="section" id="solid-principles"&gt;
&lt;h2&gt;SOLID Principles&lt;/h2&gt;
&lt;p&gt;I find the SOLID acronym is confusing. Mostly because the principles are presented in a funny order.
While the &amp;quot;Single Responsibility&amp;quot; principle is first, and seems most important, it's really more of a summary.&lt;/p&gt;
&lt;div class="section" id="isp"&gt;
&lt;h3&gt;ISP&lt;/h3&gt;
&lt;p&gt;The &amp;quot;Interface Segregation&amp;quot; principle is -- perhaps -- the most important.&lt;/p&gt;
&lt;div class="admonition important"&gt;
&lt;p class="first admonition-title"&gt;Important&lt;/p&gt;
&lt;p&gt;Interface Segregation Principle&lt;/p&gt;
&lt;p class="last"&gt;ISP == Cut the fluff seen by collaborators.&lt;/p&gt;
&lt;/div&gt;
&lt;p&gt;In a C++ or Java context, the more that's exposed to a collaborator, the wider the ripples from a change.&lt;/p&gt;
&lt;p&gt;In a Python context, a too-rich interface is just annoying to understand, maintain, extend, and test.&lt;/p&gt;
&lt;p&gt;One metric for paring down an interface is the number of mock objects needed for an isolated unit test.&lt;/p&gt;
&lt;p&gt;(Some Facade and Adapter designs will have a lot of mocks because they're wrapping complicated things in a simple interface.
Don't apply the &amp;quot;minimize mocks&amp;quot; metric blindly.)&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="lsp"&gt;
&lt;h3&gt;LSP&lt;/h3&gt;
&lt;p&gt;Remember, Python has duck typing. LSP is something that applies if -- and only if -- inheritance is being used.
A bunch of classes can all implement a common protocol without being subclasses of each other.&lt;/p&gt;
&lt;p&gt;An Abstract Base Class has the abstract methods marked so that an object cannot be created.
When all of the methods are defined by some subclass, it's now concrete and an object &lt;strong&gt;can&lt;/strong&gt; be created.&lt;/p&gt;
&lt;p&gt;A concrete base class can be extended as needed, and any class in the hierarchy is usable.
There won't be any &amp;quot;Can't instantiate abstract class&amp;quot; exception.&lt;/p&gt;
&lt;p&gt;In order to make LSP work, it's very helpful for subclasses to &lt;strong&gt;add&lt;/strong&gt; features to base classes.
Base classes should be a minimal foundation; think of them as a generalization.
The extensions should add features; they will be specializations.
(It's not mandatory, it's just kind of tedious to write &amp;quot;do nothing&amp;quot; methods for a subclass to take away a base class feature.)&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="ocp"&gt;
&lt;h3&gt;OCP&lt;/h3&gt;
&lt;p&gt;This requires some consideration of the sources of change and the kinds of extensions that might be necessary.&lt;/p&gt;
&lt;p&gt;It's rather difficult to do well when working on a problem domain that's not well understood.
After creating some code -- and living with it -- it's easier to see what's likely to change and what's likely essential.
Once the change vectors are more clear, it becomes easier to see what parts of a class are likely to be extended.&lt;/p&gt;
&lt;div class="admonition important"&gt;
&lt;p class="first admonition-title"&gt;Important&lt;/p&gt;
&lt;p&gt;Open/Closed Principle&lt;/p&gt;
&lt;p&gt;There are no &amp;quot;bug fix&amp;quot; changes where you modify a class.&lt;/p&gt;
&lt;p&gt;Instead, think about extending a broken class with a subclass that has less buggy implementations of methods.
The app then needs to use the subclass that's not as broken.
Change happens through extension, not modification.&lt;/p&gt;
&lt;p class="last"&gt;Doing this means &amp;quot;Dependency Injection Principle&amp;quot; needs to be used &lt;strong&gt;also.&lt;/strong&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="section" id="dip"&gt;
&lt;h3&gt;DIP&lt;/h3&gt;
&lt;p&gt;Dependency &amp;quot;Inversion&amp;quot; really means don't have simple names of classes everywhere.&lt;/p&gt;
&lt;p&gt;In Java and C++ collaborators with a class would have a reference to that class compiled into them.
Change some class, and the collaborators all need to be recompiled.
No one likes this.&lt;/p&gt;
&lt;p&gt;In Python, the name resolution happens at run-time, and there's no additional overhead from making a change.
(The name lookups are an overhead that's inherent in Python.)&lt;/p&gt;
&lt;p&gt;In Python, the dependency injection means assigning a target class to a variable instead of simply using it.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;SomeThing&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="fm"&gt;__init__&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;this&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;that&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="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="kc"&gt;None&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;this&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;this&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;that&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;that&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Collaborator&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;what_to_build&lt;/span&gt;&lt;span class="p"&gt;:&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;SomeThing&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;SomeThing&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="fm"&gt;__init__&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;arg&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="kc"&gt;None&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;something&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;SomeThing&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;what_to_build&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;this&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;arg&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;The name of the thing to build is a class variable in the &lt;tt class="docutils literal"&gt;Collaborator&lt;/tt&gt; class.
Making a change to the class used internally by the &lt;tt class="docutils literal"&gt;Collaborator&lt;/tt&gt; class is isolated to this variable.&lt;/p&gt;
&lt;p&gt;We can make a subclass of &lt;tt class="docutils literal"&gt;Collaborator&lt;/tt&gt; with a new value for &lt;tt class="docutils literal"&gt;what_to_build&lt;/tt&gt; and change it's behavior.
We can go further, of course, and have some centralized configuration that names the classes to use.
That can be handy in very complicated applications where a lot of things are likely to change.&lt;/p&gt;
&lt;p&gt;As a practical matter, very few things change.
A small configuration object with a few critical class references is all that's really required.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="srp"&gt;
&lt;h3&gt;SRP&lt;/h3&gt;
&lt;p&gt;The most difficult of of the SOLID design principles is identifying a &amp;quot;single&amp;quot; responsibility.
The question of responsibility often requires some qualifiers.
It's important to consider responsibilities from which collaborator's perspective.&lt;/p&gt;
&lt;p&gt;A class may do a bunch of things internally.
But -- viewed from outside -- it's a single, atomic behavior.&lt;/p&gt;
&lt;p&gt;This is where the nine GRASP patterns can come in handy, to implement a class with a single responsibility.&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;Apply the SOLID principles carefully.&lt;/p&gt;
&lt;p&gt;Always apply ISP, OCD, and DIP.  Use LSP when there's inheritance involved.
The SRP requires some careful thought, and -- from different perspectives -- can be awkwardly complicated.&lt;/p&gt;
&lt;p&gt;The GRASP patterns can be helpful for implementation. Sometimes they are overly focused on Java and C++.&lt;/p&gt;
&lt;/div&gt;
</content><category term="Python"></category><category term="python"></category><category term="oo"></category><category term="oodesign"></category><category term="design principles"></category><category term="solid"></category></entry><entry><title>OO Design Principles: ABC's</title><link href="https://slott56.github.io/2025-10-01-oo_design_principles_abcs.html" rel="alternate"></link><published>2025-10-01T09:46:00-04:00</published><updated>2025-10-01T09:46:00-04:00</updated><author><name>S.Lott</name></author><id>tag:slott56.github.io,2025-10-01:/2025-10-01-oo_design_principles_abcs.html</id><summary type="html">&lt;!-- background:

I’ve been reading quite a lot in the book “Fluent Python”. It's a brilliant resource and is helping me understand details of Python that I hadn't even looked at before. In the last few chapters I read several times that ideally I should avoid inheritance with ABCs and if I do, then from a standard class. That is, I should avoid creating a typical base and subclass construct that is used in a strategy pattern, for example.

If I look at a principle like SOLID, where we have the Liskov Substitution Principle, the interface separation principle, and the dependency inversion principle, they all rely on a base class, which of course is created by inheriting from ABC. I think as long as we do not have concrete methods in the base class, this could be a protocol instead.

However, I've been thinking about the general meaning of SOLID for Python. I can see these principles in an object-oriented only language like Java. Since this is the first language I learned, my intuition tells me that the language is perfect for following the principles. Python, on the other hand, does not have the strong need to be object-oriented, but most of the principles assume that we are working with classes. Often it's just easier to use a functional approach. An example would be the strategy pattern, where instead of having a construct of different classes that handle a specific strategy, we just have different functions. The intent for a developer is very explicit when using those, and I don't see the need for an ABC and inheritance from it. The only advantage I would see is to put it in a class that uses Protocols to enable type hints.

I've also read about the GRASP principles, but some parts of it don't feel very natural to me in Python either. For example, very obviously, polymorphism, which is also not needed in Python. Of course, low coupling and high cohesion are rather language-agnostic concepts, and they seem to fit very well with Python.

I know that these principles are not a religion, and I don't have to follow any specific one. I know I can take ideas from multiple ones and stack them together. However, I like the basic idea of dependency inversion, interface segregation and Liskov Substitution and used it in one of my products. Working with Liskov Substitution can make it difficult to navigate through an IDE. This plus explanations I’ve read in Fluent Python make me think that I've made my life more difficult with it, but I wonder what would be pythonic here?

Since I couldn't find a helpful discussion online, I was wondering what others experience is and how they approach this? Do you follow any principles like SOLID or GRASP or do you find a mix to be the best option? I've been looking for articles that discuss this but haven't found a great resource. I'd love to hear your thoughts and experiences and if you know of a great resource. --&gt;
&lt;p&gt;Some quotes to provide context.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&amp;quot;I read several times that ideally I should avoid inheritance with ABCs and if I do, then from a standard class. That is, I should avoid creating a typical base and subclass construct that is used in a strategy pattern, for example.&amp;quot;&lt;/p&gt;
&lt;p&gt;&amp;quot;If I look …&lt;/p&gt;&lt;/blockquote&gt;</summary><content type="html">&lt;!-- background:

I’ve been reading quite a lot in the book “Fluent Python”. It's a brilliant resource and is helping me understand details of Python that I hadn't even looked at before. In the last few chapters I read several times that ideally I should avoid inheritance with ABCs and if I do, then from a standard class. That is, I should avoid creating a typical base and subclass construct that is used in a strategy pattern, for example.

If I look at a principle like SOLID, where we have the Liskov Substitution Principle, the interface separation principle, and the dependency inversion principle, they all rely on a base class, which of course is created by inheriting from ABC. I think as long as we do not have concrete methods in the base class, this could be a protocol instead.

However, I've been thinking about the general meaning of SOLID for Python. I can see these principles in an object-oriented only language like Java. Since this is the first language I learned, my intuition tells me that the language is perfect for following the principles. Python, on the other hand, does not have the strong need to be object-oriented, but most of the principles assume that we are working with classes. Often it's just easier to use a functional approach. An example would be the strategy pattern, where instead of having a construct of different classes that handle a specific strategy, we just have different functions. The intent for a developer is very explicit when using those, and I don't see the need for an ABC and inheritance from it. The only advantage I would see is to put it in a class that uses Protocols to enable type hints.

I've also read about the GRASP principles, but some parts of it don't feel very natural to me in Python either. For example, very obviously, polymorphism, which is also not needed in Python. Of course, low coupling and high cohesion are rather language-agnostic concepts, and they seem to fit very well with Python.

I know that these principles are not a religion, and I don't have to follow any specific one. I know I can take ideas from multiple ones and stack them together. However, I like the basic idea of dependency inversion, interface segregation and Liskov Substitution and used it in one of my products. Working with Liskov Substitution can make it difficult to navigate through an IDE. This plus explanations I’ve read in Fluent Python make me think that I've made my life more difficult with it, but I wonder what would be pythonic here?

Since I couldn't find a helpful discussion online, I was wondering what others experience is and how they approach this? Do you follow any principles like SOLID or GRASP or do you find a mix to be the best option? I've been looking for articles that discuss this but haven't found a great resource. I'd love to hear your thoughts and experiences and if you know of a great resource. --&gt;
&lt;p&gt;Some quotes to provide context.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&amp;quot;I read several times that ideally I should avoid inheritance with ABCs and if I do, then from a standard class. That is, I should avoid creating a typical base and subclass construct that is used in a strategy pattern, for example.&amp;quot;&lt;/p&gt;
&lt;p&gt;&amp;quot;If I look at a principle like SOLID, where we have the Liskov Substitution Principle, the interface separation principle, and the dependency inversion principle, they all rely on a base class, which of course is created by inheriting from ABC.&amp;quot;&lt;/p&gt;
&lt;p&gt;&amp;quot;Python, on the other hand, does not have the strong need to be object-oriented, but most of the principles assume that we are working with classes.&amp;quot;&lt;/p&gt;
&lt;p&gt;&amp;quot;Working with Liskov Substitution can make it difficult to navigate through an IDE.&amp;quot;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;div class="section" id="bluf"&gt;
&lt;h2&gt;BLUF&lt;/h2&gt;
&lt;p&gt;ABC's are only &lt;strong&gt;required&lt;/strong&gt; as a way to do earlier validation that a class is complete.&lt;/p&gt;
&lt;p&gt;Otherwise, they're not required.&lt;/p&gt;
&lt;p&gt;They can be helpful when planning dependency injection.
The SOLID Dependency Inversion Principle advises us to depend on abstractions, not concrete, specialized subclasses.&lt;/p&gt;
&lt;p&gt;In Python, we don't &lt;strong&gt;need&lt;/strong&gt; an abstraction at the base of a class hierarchy.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="purely-oo-design"&gt;
&lt;h2&gt;Purely OO Design?&lt;/h2&gt;
&lt;p&gt;First, I want to talk a little bit about OO design and &amp;quot;purely&amp;quot; OO languages.&lt;/p&gt;
&lt;p&gt;Unlike Java or C++, Python is purely object-oriented. Not that it actually matters.&lt;/p&gt;
&lt;p&gt;A functional style of Python depends on functions which are -- essentially -- callable objects.
They have one method, &lt;tt class="docutils literal"&gt;__call__()&lt;/tt&gt;.
They have attributes.
Because they descend from the &lt;tt class="docutils literal"&gt;object&lt;/tt&gt; class, you can add attributes to a function.
They're instances of a type, &lt;tt class="docutils literal"&gt;function&lt;/tt&gt;.&lt;/p&gt;
&lt;p&gt;Java and C++ have &amp;quot;primitive&amp;quot; types which aren't objects; they're not &amp;quot;purely&amp;quot; OO.
Python doesn't have this quirk.
Python is more purely object-oriented than Java.
Which suggests any OO purity test doesn't really matter.&lt;/p&gt;
&lt;p&gt;OO Design Principles and Functional Design Principles can all be used with Python.
Indeed, some of the old COBOL design patterns can be used, too.
(Not all, of course. COBOL had GOTO's, an ALTER statement, and a very weird PERFORM THRU that make it right weird to map to Python.)&lt;/p&gt;
&lt;p&gt;OO Purity? Doesn't matter.&lt;/p&gt;
&lt;p&gt;Let's move on to look at Python's ABC's.
After that, we'll look at the SOLID principles in general.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="to-abc-or-not-to-abc"&gt;
&lt;h2&gt;To ABC or not to ABC?&lt;/h2&gt;
&lt;p&gt;C++ and Java (and many other languages) are built around separately-built binaries that are linked together.
Some linkage can be done at compile time, some can be done at run time by the OS loader.
Since they're separately-built binaries, everyone &lt;strong&gt;must&lt;/strong&gt; agree on the binary interface.
Change cannot be tolerated.&lt;/p&gt;
&lt;p&gt;I'll emphasize that.&lt;/p&gt;
&lt;div class="admonition admonition-emphasis"&gt;
&lt;p class="first admonition-title"&gt;Emphasis&lt;/p&gt;
&lt;p class="last"&gt;C++ and Java emphasize a style of design where interface changes cannot be tolerated.&lt;/p&gt;
&lt;/div&gt;
&lt;p&gt;Pragmatically, we now have computers that are so fast that recompiling a very large Java or C++ app is no longer a nightmare of waiting for hours.
When these languages were designed, a development team might do one nightly build of everything.
During the day, you were limited to compiling the classes you were working on.
Nothing more.
Getting the interfaces to be stable was an important risk reduction technique.&lt;/p&gt;
&lt;p&gt;Abstract Base Classes were a way to minimize recompilation.&lt;/p&gt;
&lt;div class="admonition important"&gt;
&lt;p class="first admonition-title"&gt;Important&lt;/p&gt;
&lt;p&gt;Abstract vs. Concrete&lt;/p&gt;
&lt;p class="last"&gt;There are abstract base classes and concrete base classes.
The &lt;tt class="docutils literal"&gt;abc&lt;/tt&gt; module introduces a whole bunch of stuff to support abstraction.
Ordinary &lt;tt class="docutils literal"&gt;class Special(Die):&lt;/tt&gt; inheritance from a concrete base class
involves no abstraction, and no &lt;tt class="docutils literal"&gt;abc&lt;/tt&gt;.&lt;/p&gt;
&lt;/div&gt;
&lt;p&gt;Python eschews strict class hierarchies, and replaces this with &amp;quot;Duck Typing&amp;quot;.
All an object requires is to have the method defined.&lt;/p&gt;
&lt;p&gt;See &lt;a class="reference external" href="https://slott56.github.io/2025-09-28-the_eval_conundrum.html"&gt;The eval() Conundrum and Python-as-DSL&lt;/a&gt;.
Way at the end is this snippet of code.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Die&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="fm"&gt;__init__&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;faces&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="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="kc"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="o"&gt;...&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="fm"&gt;__rmul__&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;n&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="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;Die&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="o"&gt;...&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="fm"&gt;__add__&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;adj&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="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;Die&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="o"&gt;...&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;roll&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&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="o"&gt;...&lt;/span&gt;
    &lt;span class="nd"&gt;@property&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;min&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="o"&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="o"&gt;...&lt;/span&gt;
    &lt;span class="nd"&gt;@property&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;max&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="o"&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="o"&gt;...&lt;/span&gt;

&lt;span class="n"&gt;D4&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Die&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="n"&gt;D6&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Die&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="n"&gt;D8&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Die&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;span class="n"&gt;etc&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;This means an object like &lt;tt class="docutils literal"&gt;D4&lt;/tt&gt; can be used with the &lt;tt class="docutils literal"&gt;*&lt;/tt&gt; and &lt;tt class="docutils literal"&gt;+&lt;/tt&gt; operators.
In very limited ways.&lt;/p&gt;
&lt;p&gt;The expression &lt;tt class="docutils literal"&gt;6 * D4&lt;/tt&gt; is legal, where &lt;tt class="docutils literal"&gt;D4 * 6&lt;/tt&gt; is not.
The way Duck Type works, there's a search for a method to implement &lt;tt class="docutils literal"&gt;*&lt;/tt&gt;.&lt;/p&gt;
&lt;p&gt;Consider &lt;tt class="docutils literal"&gt;6 * D4&lt;/tt&gt;.&lt;/p&gt;
&lt;ol class="arabic simple"&gt;
&lt;li&gt;Does &lt;tt class="docutils literal"&gt;6&lt;/tt&gt; implement &lt;tt class="docutils literal"&gt;__mul__()&lt;/tt&gt;?  It does.
However, when &lt;tt class="docutils literal"&gt;int.__mul__()&lt;/tt&gt; is evaluated with a &lt;tt class="docutils literal"&gt;Die&lt;/tt&gt; object, the result is &lt;tt class="docutils literal"&gt;NotImplemented&lt;/tt&gt;.&lt;/li&gt;
&lt;li&gt;Does &lt;tt class="docutils literal"&gt;Die&lt;/tt&gt; implement &lt;tt class="docutils literal"&gt;__rmul__()&lt;/tt&gt;?  It does.
When &lt;tt class="docutils literal"&gt;Die.__rmul__()&lt;/tt&gt; is evaluated with an &lt;tt class="docutils literal"&gt;int&lt;/tt&gt; object, the result is a new &lt;tt class="docutils literal"&gt;Die&lt;/tt&gt; object.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;(There's actually a first step, omitted for brevity. See the sidebar.)&lt;/p&gt;
&lt;p&gt;Here's the bottom line.&lt;/p&gt;
&lt;blockquote&gt;
The Duck Typing two-step search for matching method names doesn't respect the class hierarchy.&lt;/blockquote&gt;
&lt;div class="sidebar"&gt;
&lt;p class="first sidebar-title"&gt;The other step?&lt;/p&gt;
&lt;p&gt;There's an initial check which &lt;strong&gt;does&lt;/strong&gt; in a limited way, reflect the class hierarchy.&lt;/p&gt;
&lt;p&gt;Is &lt;tt class="docutils literal"&gt;Die&lt;/tt&gt; a subclass of &lt;tt class="docutils literal"&gt;int&lt;/tt&gt;?
If &lt;tt class="docutils literal"&gt;Die&lt;/tt&gt; was a subclass of &lt;tt class="docutils literal"&gt;int&lt;/tt&gt;, then &lt;tt class="docutils literal"&gt;Die&lt;/tt&gt; must be considered first to permit a subclass to override a superclass.
This reverses the &lt;strong&gt;order&lt;/strong&gt; of the other two steps.&lt;/p&gt;
&lt;p class="last"&gt;A class hierarchy can shift the order of the Duck-Typing Two-Step.&lt;/p&gt;
&lt;/div&gt;
&lt;p&gt;A &lt;tt class="docutils literal"&gt;Protocol&lt;/tt&gt; formalizes the Duck-Typing Two-Step in a way that tools can be sure the whole&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Summary&lt;/strong&gt;:&lt;/p&gt;
&lt;ol class="arabic simple"&gt;
&lt;li&gt;When using only Python, most developers don't need to care about separately-compiled binaries.
(When writing Rust or C extensions, of course, separately-compiled binaries are a big deal.)&lt;/li&gt;
&lt;li&gt;Duck typing eliminates a &lt;strong&gt;requirement&lt;/strong&gt; for ABCs.&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;
&lt;div class="section" id="conclusion"&gt;
&lt;h2&gt;Conclusion&lt;/h2&gt;
&lt;div class="section" id="is-an-abstract-base-class-still-helpful"&gt;
&lt;h3&gt;Is an Abstract Base Class &lt;strong&gt;still&lt;/strong&gt; helpful?&lt;/h3&gt;
&lt;p&gt;Yes.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="when"&gt;
&lt;h3&gt;When?&lt;/h3&gt;
&lt;p&gt;When you need it.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="when-is-it-necessary"&gt;
&lt;h3&gt;When is it necessary?&lt;/h3&gt;
&lt;p&gt;The use case for an ABC in Python is to push the Duck-Typing Two-Step so it happens earlier.&lt;/p&gt;
&lt;ol class="arabic simple"&gt;
&lt;li&gt;ABC's permit type hint checking to be sure the code is likely to work.&lt;/li&gt;
&lt;li&gt;&lt;dl class="first docutils"&gt;
&lt;dt&gt;At run time, ABC's prevent instantiating an incomplete object. Code may crash earlier.&lt;/dt&gt;
&lt;dd&gt;Most important: the exception is much more clear when a method is missing.&lt;/dd&gt;
&lt;/dl&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;ABC's promote early detection of design problems.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="when-is-it-superfluous"&gt;
&lt;h3&gt;When is it superfluous?&lt;/h3&gt;
&lt;p&gt;When your base class is concrete, don't waste time on an ABC.
Just use a concrete base class and extend it as needed.&lt;/p&gt;
&lt;p&gt;That's enough on ABC's for now. Let's move on to the SOLID principles.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
</content><category term="Python"></category><category term="python"></category><category term="oo"></category><category term="oodesign"></category><category term="design principles"></category><category term="abc"></category></entry><entry><title>Comma Comma</title><link href="https://slott56.github.io/2025-06-11-comma_comma.html" rel="alternate"></link><published>2025-06-11T07:58:00-04:00</published><updated>2025-06-11T07:58:00-04:00</updated><author><name>S.Lott</name></author><id>tag:slott56.github.io,2025-06-11:/2025-06-11-comma_comma.html</id><summary type="html">&lt;p&gt;Ugh.&lt;/p&gt;
&lt;p&gt;A painful stretch of hours looking for a problem.
Working on this: &lt;a class="reference external" href="https://github.com/cloud-custodian/cel-python/wiki/Evaluation-Design"&gt;https://github.com/cloud-custodian/cel-python/wiki/Evaluation-Design&lt;/a&gt;.
I need to address a performance problem and upgrade things generally to get them ready for 3.13 and 3.14.&lt;/p&gt;
&lt;p&gt;You know how it goes, right?&lt;/p&gt;
&lt;p&gt;I touched something …&lt;/p&gt;</summary><content type="html">&lt;p&gt;Ugh.&lt;/p&gt;
&lt;p&gt;A painful stretch of hours looking for a problem.
Working on this: &lt;a class="reference external" href="https://github.com/cloud-custodian/cel-python/wiki/Evaluation-Design"&gt;https://github.com/cloud-custodian/cel-python/wiki/Evaluation-Design&lt;/a&gt;.
I need to address a performance problem and upgrade things generally to get them ready for 3.13 and 3.14.&lt;/p&gt;
&lt;p&gt;You know how it goes, right?&lt;/p&gt;
&lt;p&gt;I touched something &amp;quot;minor&amp;quot; and all kinds of acceptance tests broke beacuse I also broke something central.&lt;/p&gt;
&lt;p&gt;The horrible realization was this:&lt;/p&gt;
&lt;blockquote&gt;
There Was No Unit Test&lt;/blockquote&gt;
&lt;p&gt;When the problem is not found first by a unit test, it means there's a feature that's only tested by the acceptance test suite.&lt;/p&gt;
&lt;div class="section" id="where-do-we-stand"&gt;
&lt;h2&gt;Where do we stand?&lt;/h2&gt;
&lt;p&gt;Here are the causes for despair.&lt;/p&gt;
&lt;ol class="arabic simple"&gt;
&lt;li&gt;We've got an acceptance test failure.&lt;/li&gt;
&lt;li&gt;This reveals a gap in the unit tests.&lt;/li&gt;
&lt;li&gt;This also reveals the stuff doesn't work because I touched &lt;em&gt;something&lt;/em&gt;.&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;
&lt;div class="section" id="what-to-do"&gt;
&lt;h2&gt;What to do?&lt;/h2&gt;
&lt;p&gt;Ideally?  Fix the unit tests.&lt;/p&gt;
&lt;p&gt;Pragmatically?  Review the git change history to see what I've touched recently.&lt;/p&gt;
&lt;p&gt;Aha. A trailing comma.&lt;/p&gt;
&lt;p&gt;There was a &lt;tt class="docutils literal"&gt;{key: lambda x: some_expression, &lt;span class="pre"&gt;...}&lt;/span&gt;&lt;/tt&gt; data structure.
I took the lambda out and replaced it with a proper &lt;tt class="docutils literal"&gt;def&lt;/tt&gt; function. With a name.&lt;/p&gt;
&lt;pre class="literal-block"&gt;
def some_new_function(x: type) -&amp;gt; type:
    some_exression,
&lt;/pre&gt;
&lt;p&gt;You know how this goes.&lt;/p&gt;
&lt;p&gt;Looks good, who can see the &lt;tt class="docutils literal"&gt;,&lt;/tt&gt;?&lt;/p&gt;
&lt;p&gt;Doesn't work. &lt;tt class="docutils literal"&gt;tuple()&lt;/tt&gt; shows up in unexpected places.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="big-lesson"&gt;
&lt;h2&gt;Big Lesson&lt;/h2&gt;
&lt;p&gt;Fix the unit tests.&lt;/p&gt;
&lt;/div&gt;
</content><category term="Python"></category><category term="#python"></category><category term="syntax"></category><category term="cel-python"></category><category term="cloud custodian"></category><category term="open source"></category></entry><entry><title>Domain-Specific Language</title><link href="https://slott56.github.io/2025-03-11-domain_specific_language.html" rel="alternate"></link><published>2025-03-11T15:52:00-04:00</published><updated>2025-03-11T15:52:00-04:00</updated><author><name>S.Lott</name></author><id>tag:slott56.github.io,2025-03-11:/2025-03-11-domain_specific_language.html</id><summary type="html">&lt;p&gt;Generally, I try to frown on Domain-Specific Languages.
Often, a tidy set of related functions, or a group of class definitions with a few decorators can create something that's every bit as expressive as a DSL in native Python syntax.&lt;/p&gt;
&lt;p&gt;There are a few cases where a DSL can be …&lt;/p&gt;</summary><content type="html">&lt;p&gt;Generally, I try to frown on Domain-Specific Languages.
Often, a tidy set of related functions, or a group of class definitions with a few decorators can create something that's every bit as expressive as a DSL in native Python syntax.&lt;/p&gt;
&lt;p&gt;There are a few cases where a DSL can be handy.&lt;/p&gt;
&lt;p&gt;One of which is when embedding complicated content in Gherkin test cases.&lt;/p&gt;
&lt;p&gt;(We'll get the context later. I want to focus on the problem at hand.)&lt;/p&gt;
&lt;p&gt;We need to write Gherkin that looks like this:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="nf"&gt;GIVEN some configuration&lt;/span&gt;
&lt;span class="nf"&gt;WHEN we run the Character UI with input of &amp;quot;&lt;/span&gt;&lt;span class="s"&gt;...&lt;/span&gt;&lt;span class="nf"&gt;&amp;quot;&lt;/span&gt;
&lt;span class="nf"&gt;THEN we see the right kind of responses in the log and what-not&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;What's essential here is the input to the Character UI (CUI).
It's a sequence of characters.&lt;/p&gt;
&lt;p&gt;In Python, it might be &lt;tt class="docutils literal"&gt;&amp;quot;o&amp;quot; + &lt;span class="pre"&gt;2*&amp;quot;mu&amp;quot;&lt;/span&gt; + &amp;quot;i&amp;quot; + &amp;quot;qy&amp;quot;&lt;/tt&gt;.&lt;/p&gt;
&lt;p&gt;The idea is to provide a meaningful sequence of commands for the given scenario.&lt;/p&gt;
&lt;p&gt;Except, of course, Gherkin isn't Python.&lt;/p&gt;
&lt;p&gt;We have two choices:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;Use &lt;tt class="docutils literal"&gt;eval()&lt;/tt&gt; to evaluate a Python expression.&lt;/li&gt;
&lt;li&gt;Use a DSL.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Since this is a test scenario, the ridiculous arguments about &lt;tt class="docutils literal"&gt;eval()&lt;/tt&gt; being &amp;quot;unsafe&amp;quot; are clearly worthy of ridicule.
Any Evil Super Genius capable of writing a Gerkin test case that includes &lt;tt class="docutils literal"&gt;import os&lt;/tt&gt; or &lt;tt class="docutils literal"&gt;import subprocess&lt;/tt&gt; has access to the source code and doesn't need to subvert the Gherkin language test scenarios.&lt;/p&gt;
&lt;p&gt;While &lt;tt class="docutils literal"&gt;eval()&lt;/tt&gt; is appealing because it's simple, it's not always ideal.
In particular, some of the scenarios are long, and the sequence of input commands needs some supplemental information to follow through the actions and the responses to work out what the expected intermediate and final states will be.&lt;/p&gt;
&lt;div class="section" id="the-context"&gt;
&lt;h2&gt;The Context&lt;/h2&gt;
&lt;p&gt;I'm working on a Rogue-Like game. The test scenarious involve walking around and collecting treasure,
bashing monsters, and avoiding traps.
The interactions aren't too complicated, but, I'd like these Gherkin-based acceptance tests to involve an absolute minimum of specialized test harness.
A code tweak to seed the random number generator seems to be all that's appropriate.&lt;/p&gt;
&lt;p&gt;In the long run, there's a potential to work through some clear definitions of the various features of the game using RDF.
SPARQL queries might provide ways to locate the features of items or behaviors of monsters.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="the-complication"&gt;
&lt;h2&gt;The Complication&lt;/h2&gt;
&lt;p&gt;What we want to have is something like this.&lt;/p&gt;
&lt;pre class="literal-block"&gt;
o                          ⍝ acknowledge splash
room 1≔ ⟨m l⟩×4 ⟨m u⟩×2 i  ⍝ to the room exit
hall 1≔ ⟨m u⟩×5 i          ⍝ to the next room entrance
done≔ q y
&lt;/pre&gt;
&lt;p&gt;We've wrapped the essential commands in a bunch of syntax to make the commands a bit more clear.
I can include a label at the left end of the line, and a &amp;quot;comment&amp;quot; on the right end.&lt;/p&gt;
&lt;p&gt;Here's what's tricky.&lt;/p&gt;
&lt;p&gt;The test case string will contain &lt;em&gt;almost&lt;/em&gt; any ASCII character found on the keyboard.
We don't want to have a lot of meta-punctuation that's &lt;strong&gt;also&lt;/strong&gt; on the keyboard.&lt;/p&gt;
&lt;p&gt;This bumps into the same problem the classic Regular Expression language suffers from.
We want to match characters.
And we need the same set of characters to have meta-level meanings.
Sometimes &lt;tt class="docutils literal"&gt;.&lt;/tt&gt; means the damn dot.  Sometimes &lt;tt class="docutils literal"&gt;.&lt;/tt&gt; means &amp;quot;any&amp;quot; character.
So we have to use &lt;tt class="docutils literal"&gt;\.&lt;/tt&gt; vs. &lt;tt class="docutils literal"&gt;.&lt;/tt&gt;  to express the distinction.&lt;/p&gt;
&lt;p&gt;I decided to do this.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="use-unicode"&gt;
&lt;h2&gt;Use Unicode&lt;/h2&gt;
&lt;p&gt;I could wrestle with some &amp;quot;meta&amp;quot; characters for grouping and repetition.
Indeed, I could parse the ordinar Regular Expression language.
These are -- technically -- regular expressions that summarize long strings of characters for the test cases.&lt;/p&gt;
&lt;p&gt;Or.&lt;/p&gt;
&lt;p&gt;I could use a few Unicode characters separate from the ASCII keyboard characters that would be input.&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;⟨ and ⟩ (MATHEMATICAL LEFT ANGLE BRACKET, and MATHEMATICAL RIGHT ANGLE BRACKET)&lt;/li&gt;
&lt;li&gt;× (MULTIPLICATION SIGN)&lt;/li&gt;
&lt;li&gt;≔ and ⍝ (COLON EQUALS, and APL FUNCTIONAL SYMBOL UP SHOE JOT)&lt;/li&gt;
&lt;li&gt;␛, ␠, ␤ for Escape, Space, and Newline.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;These eight additional characters are not on the keyboard.
The game will never use these as input.
I don't need to use fancy escape sequences to distinguish these meta-characters from the ASCII characters that are being generated.
These few characters are difficult to type: you have to pluck them off the Unicode character pop-up window that Macos offers you.&lt;/p&gt;
&lt;p&gt;The code to handle the Gherkin doesn't have to fuss around with hyper-complex-looking regular expressions to parse this DSL.
The allowed input characters are &lt;tt class="docutils literal"&gt;.&lt;/tt&gt; and the meta-characters are simply presented as literal values in the regular expression.
(As noted above, this DSL is a regular expression language. It's highly limited.)&lt;/p&gt;
&lt;/div&gt;
</content><category term="Python"></category><category term="#python"></category><category term="domain-specific language"></category><category term="dsl"></category></entry><entry><title>Joomla Conversion</title><link href="https://slott56.github.io/2025-02-08-joomla_conversion.html" rel="alternate"></link><published>2025-02-08T08:52:00-05:00</published><updated>2025-02-08T08:52:00-05:00</updated><author><name>S.Lott</name></author><id>tag:slott56.github.io,2025-02-08:/2025-02-08-joomla_conversion.html</id><summary type="html">&lt;p&gt;Recently, we talked about extracting data from complex relational databases.
This is -- in a way -- another case study for my &lt;em&gt;Unlearning SQL&lt;/em&gt; book.
This is a description of what comes next after the &amp;quot;low-level&amp;quot; conversion.
Warning: it's complicated.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;BLUF&lt;/strong&gt;: Take the time to get rid of SQL processing.&lt;/p&gt;
&lt;div class="sidebar"&gt;
&lt;p class="first sidebar-title"&gt;&lt;em&gt;Unlearning SQL …&lt;/em&gt;&lt;/p&gt;&lt;/div&gt;</summary><content type="html">&lt;p&gt;Recently, we talked about extracting data from complex relational databases.
This is -- in a way -- another case study for my &lt;em&gt;Unlearning SQL&lt;/em&gt; book.
This is a description of what comes next after the &amp;quot;low-level&amp;quot; conversion.
Warning: it's complicated.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;BLUF&lt;/strong&gt;: Take the time to get rid of SQL processing.&lt;/p&gt;
&lt;div class="sidebar"&gt;
&lt;p class="first sidebar-title"&gt;&lt;em&gt;Unlearning SQL&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;KDP &lt;a class="reference external" href="https://www.amazon.com/dp/B0DDMFMXNW"&gt;https://www.amazon.com/dp/B0DDMFMXNW&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Lulu &lt;a class="reference external" href="https://www.lulu.com/shop/steven-lott/unlearning-sql/paperback/product-yvnm8zn.html?page=1&amp;amp;pageSize=4"&gt;https://www.lulu.com/shop/steven-lott/unlearning-sql/paperback/product-yvnm8zn.html?page=1&amp;amp;pageSize=4&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Google Play &lt;a class="reference external" href="https://play.google.com/store/books/details?id=23WAEAAAQBAJ"&gt;https://play.google.com/store/books/details?id=23WAEAAAQBAJ&lt;/a&gt;&lt;/p&gt;
&lt;p class="last"&gt;Apple Books &lt;a class="reference external" href="https://books.apple.com/us/book/unlearning-sql/id6443164060"&gt;https://books.apple.com/us/book/unlearning-sql/id6443164060&lt;/a&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;p&gt;In &lt;a class="reference external" href="https://slott56.github.io/2024-12-31_database_migration_part_i.html"&gt;Part I&lt;/a&gt;, we loaded a database and queried the metadata.
In &lt;a class="reference external" href="https://slott56.github.io/2025-01-07_database_migration_part_ii.html"&gt;Part II&lt;/a&gt;, we extracted the raw tables and loaded up a TAR Archive with NDJSON documents.
In &lt;a class="reference external" href="https://slott56.github.io/2025-01-14_database_migration_part_iii.html"&gt;Part III&lt;/a&gt;, we prepared native Python objects that had a complete representation for the various kinds of tree structures.
These include assets, categories, forum topics, image galleries, amongst other things.
In &lt;a class="reference external" href="https://slott56.github.io/2025-01-21_database_migration_part_iv.html"&gt;Part IV&lt;/a&gt;, we talked about some applications to examine the converted data, looking for useful values, keys, and relationships.&lt;/p&gt;
&lt;p&gt;We're going to skip a lot of the icky Joomla! details and focus on how to create something potentially useful.&lt;/p&gt;
&lt;div class="section" id="the-goal"&gt;
&lt;h2&gt;The Goal&lt;/h2&gt;
&lt;p&gt;Recall from Part IV, we thought we had several steaming heaps of content on the legacy site.
After exploration, we think we have the following:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;A home page with a few articles.&lt;/li&gt;
&lt;li&gt;A right sidebar with two articles.&lt;/li&gt;
&lt;li&gt;A few content pages, each of which has links to a dozen or so narrowly-focused articles in a few categories.&lt;/li&gt;
&lt;li&gt;The master collection of articles, neatly organized by category. There's a hierarchy here, a SQL nightmare we've avoided by restructuring the data.&lt;/li&gt;
&lt;li&gt;The Kunena forums collection categories, topics, and messages. There's a hierarchy here, another SQL nightmare.&lt;/li&gt;
&lt;li&gt;The JoomGallery collection of images. Hierarchy.&lt;/li&gt;
&lt;li&gt;The Phoca collection of download files. You guessed it, another hierarchy.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;These aren't the only hierarchues.
Menus, modules, and assets have very tangled relationships, also.
These are a SQL-query nightmare that we've turned into simple Python references among objects.&lt;/p&gt;
&lt;p&gt;There's more, of course.&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;A hoard of Yahoo message-board posts which are not first-class parts of Joomla! but are first-class content.&lt;/li&gt;
&lt;li&gt;Scans of the old paper newsletters. These, too, are not first-class parts of Joomla!, but are clearly very important content.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;We'd like to dump all of this into a form that the &lt;a class="reference external" href="https://gohugo.io"&gt;Hugo&lt;/a&gt; tool can use to approximate the original site's content and structure.
We're not going to spend too much time on the original look and feel; we can fuss with CSS to maybe match the color scheme.&lt;/p&gt;
&lt;p&gt;What we've got are two separate kinds of things in the resulting site:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;The &amp;quot;pages&amp;quot; which are Hugo Page Bundles with an &lt;tt class="docutils literal"&gt;_index.md&lt;/tt&gt; and maybe some image resources.
Each article becomes a page.
In the case of the Home page -- which has multiple articles plastered onto it -- we will need a special-case template to include the bodies of multiple articles in one place.
The Yahoo! messages are -- essentially -- articles that require some extra effort to convert.&lt;/li&gt;
&lt;li&gt;The &amp;quot;collections&amp;quot; which are Hugo Sections, using an empty &lt;tt class="docutils literal"&gt;_index.md&lt;/tt&gt; and a section-index generated by the template.
The old newsletters are little more than downloads; these &lt;em&gt;should&lt;/em&gt; be handled gracefully as a collection of Page Bundles.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;We've also got some things we're going to set aside.
Specifically, the right side-bar for articles is a waste of screen real-estate.
It's not present for Forum or Photo Gallery.&lt;/p&gt;
&lt;p&gt;Many of the Hugo themes have a 3-column look: the top-level menu is on the left, and the page table-of-contents is on the right.
This seems to be somewhat more useful.
One very spare Hugo theme is the &lt;a class="reference external" href="https://themes.gohugo.io/themes/hugo-book"&gt;Book&lt;/a&gt; theme, which seems like a good place to start.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="the-processes"&gt;
&lt;h2&gt;The Processes&lt;/h2&gt;
&lt;p&gt;There are two separate kinds of migration processes:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p class="first"&gt;Bulk migration of the collections.  We have four, separate, unique subclasses.&lt;/p&gt;
&lt;div class="figure"&gt;
&lt;img alt="The Converter Class Hierarchy" src="https://slott56.github.io/media/joomla_bulk.png" /&gt;
&lt;p class="caption"&gt;The Converter Class Hierarchy&lt;/p&gt;
&lt;/div&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p class="first"&gt;Create the top-level pages that match the various pages of articles on the legacy site.&lt;/p&gt;
&lt;div class="figure"&gt;
&lt;img alt="The MakePage Class Hierarchy" src="https://slott56.github.io/media/joomla_makepage.png" /&gt;
&lt;p class="caption"&gt;The MakePage Class Hierarchy&lt;/p&gt;
&lt;/div&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Note that the pages depend on the bulk-conversion results.
The new path structure and new file names, and other details are (more-or-less) encapsulated in the converter classes.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="things-that-aren-t-easy"&gt;
&lt;h2&gt;Things That Aren't Easy&lt;/h2&gt;
&lt;p&gt;While Hugo handles a large number of special cases and exceptions gracefully, we have legacy content that's a bit of a mess.
Some of the mess may be my inability to ferret out all of the details of the Joomla! data model.
Other aspects of the mess also seem to be a result of the way Joomla! decides what's &amp;quot;published&amp;quot; and what's not &amp;quot;published.&amp;quot;&lt;/p&gt;
&lt;p&gt;First, and most obvious, we have HTML content.
We can -- if we want -- generate HTML pages and leave the details to Hugo.
In the long run, we'd like to move away from HTML.
We'd really like to emphasize Markdown and make HTML an exception.&lt;/p&gt;
&lt;p&gt;To do this, we state each page uses markdown, and wrap the HTML in &lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;{{&amp;lt;html&amp;gt;}}...{{&amp;lt;/html&amp;gt;}}&lt;/span&gt;&lt;/tt&gt; &amp;quot;short tags&amp;quot;.
This is -- well -- ugly.
It sequesters the HTML in those few places where it's used.&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;descriptions for galleries and downloads.&lt;/li&gt;
&lt;li&gt;articles.&lt;/li&gt;
&lt;li&gt;forum messages.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;It means a lot of code like this:&lt;/p&gt;
&lt;pre class="literal-block"&gt;
print()
print(&amp;quot;{{&amp;lt;html&amp;gt;}}&amp;quot;, article.fulltext, &amp;quot;{{&amp;lt;/html&amp;gt;}}&amp;quot;)
&lt;/pre&gt;
&lt;p&gt;This gets us started with &amp;quot;safe&amp;quot; HTML everywhere.
We can see a great deal of the site with this hack.&lt;/p&gt;
&lt;p&gt;Hugo leaves HTML comments in places where unsafe HTML shows up.
We can look for &lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;&amp;lt;!--&lt;/span&gt; raw HTML omitted &lt;span class="pre"&gt;--&amp;gt;&lt;/span&gt;&lt;/tt&gt; in the generated HTML and include needed wrappers.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="embedded-images-and-links"&gt;
&lt;h2&gt;Embedded Images and Links&lt;/h2&gt;
&lt;p&gt;There are two kinds of links that show up in articles, descriptions, and forum messages:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;tt class="docutils literal"&gt;&amp;lt;a &lt;span class="pre"&gt;href=&amp;quot;...&amp;quot;&amp;gt;&lt;/span&gt;&lt;/tt&gt; tags&lt;/li&gt;
&lt;li&gt;&lt;tt class="docutils literal"&gt;&amp;lt;img &lt;span class="pre"&gt;src=&amp;quot;...&amp;quot;&amp;gt;&lt;/span&gt;&lt;/tt&gt; tags&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;These have a variety of forms:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;Proper external references with a scheme and/or a &amp;quot;netloc&amp;quot; (host name.)&lt;/li&gt;
&lt;li&gt;Redundant internal references with a scheme and netloc of the server currently hosting the legacy content.&lt;/li&gt;
&lt;li&gt;&lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;index.php?...&lt;/span&gt;&lt;/tt&gt; queries.&lt;/li&gt;
&lt;li&gt;&lt;tt class="docutils literal"&gt;#fragment&lt;/tt&gt; fragements of the current page.&lt;/li&gt;
&lt;li&gt;&lt;tt class="docutils literal"&gt;local/path/to/content&lt;/tt&gt; paths into the legacy site content.&lt;/li&gt;
&lt;li&gt;&lt;tt class="docutils literal"&gt;userupload/whatever&lt;/tt&gt; paths into the local directory tree outside what Joomla! manages.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;These devolve to three functions for link rewriting algorithms.&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;A filter to distinguish between &amp;quot;don't bother&amp;quot;, &amp;quot;query&amp;quot;, and &amp;quot;path&amp;quot; cases.
A URL with a scheme or netloc (or both) is ignored.
A URL that's only a fragment is also ignored.
(We could try to clean up the fragments, but, there aren't many and they require divining the author's intent, something that's hard to automate.)&lt;/li&gt;
&lt;li&gt;A function to rewrite the Joomla! queries into paths into the new content structure.&lt;/li&gt;
&lt;li&gt;A function to examine the various paths that are used and restate these as part of the new content structure.
Because we have four kinds of collections, plus the local filesystem references, we have a number of &amp;quot;search&amp;quot; functions for this case:&lt;ul&gt;
&lt;li&gt;search galleries&lt;/li&gt;
&lt;li&gt;search articles&lt;/li&gt;
&lt;li&gt;search downloads&lt;/li&gt;
&lt;li&gt;search forums&lt;/li&gt;
&lt;li&gt;search image archive files&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Yes. This is a &lt;strong&gt;large&lt;/strong&gt; pain.
While there is some overlap, each collection is unique with unique names and a distinct resulting tree in the new content.
No, there's no trivial way to impose a single, unifying, &amp;quot;one-ring-to-rule-them-all&amp;quot; content structure.
The whole point is to respect the unique features of each category of content.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="what-else-oh-right-section-index"&gt;
&lt;h2&gt;What Else? Oh, Right, Section Index&lt;/h2&gt;
&lt;p&gt;The Book theme doesn't (by default) include section indexes as a default structure.&lt;/p&gt;
&lt;p&gt;If there's a &lt;tt class="docutils literal"&gt;layout/_defaults/section.html&lt;/tt&gt;, this is used for those &lt;tt class="docutils literal"&gt;_index.md&lt;/tt&gt; pages that are clearly the top of a section tree.&lt;/p&gt;
&lt;p&gt;We don't need to do anything more than define the template for the index.
Here's what we started with:&lt;/p&gt;
&lt;pre class="literal-block"&gt;
{{ define &amp;quot;main&amp;quot; }}
  &amp;lt;main&amp;gt;
    {{ .Content }}

    {{ $pages := .Sections }}
    {{ $paginator := .Paginate $pages 25 }}

      &amp;lt;ul&amp;gt;
    {{ range $paginator.Pages }}
      &amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;{{ .RelPermalink }}&amp;quot;&amp;gt;{{ .LinkTitle }}&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
    {{ end }}
      &amp;lt;/ul&amp;gt;

    {{ template &amp;quot;_internal/pagination.html&amp;quot; . }}
  &amp;lt;/main&amp;gt;
{{ end }}
&lt;/pre&gt;
&lt;p&gt;This doesn't sort things properly, so we need to add metadata with weighting.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="finally-broken-links"&gt;
&lt;h2&gt;Finally, Broken Links&lt;/h2&gt;
&lt;p&gt;We have two origins for broken links.&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;Stuff we couldn't find in the database.
Or, more properly, things which appear to be named in the database, but we can't find anywhere.&lt;/li&gt;
&lt;li&gt;Stuff we thought we found, but it still didn't work in Hugo.
These are essentially bugs, and we're still working through the last five.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The &amp;quot;stuff we could never find&amp;quot; includes things that were likely removed from the legacy site.
Since we didn't take the time to work out all the &amp;quot;pubish this--don't publish that&amp;quot; rules, we've likely included things which were &amp;quot;unpublished&amp;quot; but not deleted.&lt;/p&gt;
&lt;p&gt;Other things are &lt;tt class="docutils literal"&gt;&amp;lt;a &lt;span class="pre"&gt;href=&amp;quot;href=&amp;quot;&amp;gt;Something&amp;lt;/a&amp;gt;&lt;/span&gt;&lt;/tt&gt; kinds of HTML. That's just broken.
There aren't many of these, and they need to be addressed manually.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="conclusion"&gt;
&lt;h2&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;Note the complexity of the migration.&lt;/p&gt;
&lt;p&gt;There's not much that can be done to magically simplify all the special cases.&lt;/p&gt;
&lt;p&gt;The time spent getting the database out of SQL and into Python objects gave us pleasantly simple Python objects to work with.&lt;/p&gt;
&lt;p&gt;The class hierarchies evolved slowly.
While it seems clear from the UML diagrams that these are &amp;quot;logical&amp;quot; designs, they didn't happen first.
The initial design was not so clear and simple, leading to lots of redundant and inter-dependent code.&lt;/p&gt;
&lt;p&gt;There's still a fair number of ultra-long methods that need to be decomposed into shorter, easier-to-understand methods.
The remaining bugs involve two lost files and three &lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;index.php?...&lt;/span&gt;&lt;/tt&gt; references that the link rewriter didn't handle correctly.&lt;/p&gt;
&lt;/div&gt;
</content><category term="Python"></category><category term="#python"></category><category term="database"></category><category term="sql"></category><category term="hugo"></category></entry><entry><title>Database Migration, Part IV</title><link href="https://slott56.github.io/2025-01-21_database_migration_part_iv.html" rel="alternate"></link><published>2025-01-21T07:21:00-05:00</published><updated>2025-01-21T07:21:00-05:00</updated><author><name>S.Lott</name></author><id>tag:slott56.github.io,2025-01-21:/2025-01-21_database_migration_part_iv.html</id><summary type="html">&lt;p&gt;We're talking about extracting data from complex relational databases.
This is -- in a way -- another case study for my &lt;em&gt;Unlearning SQL&lt;/em&gt; book.&lt;/p&gt;
&lt;div class="sidebar"&gt;
&lt;p class="first sidebar-title"&gt;&lt;em&gt;Unlearning SQL&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;KDP &lt;a class="reference external" href="https://www.amazon.com/dp/B0DDMFMXNW"&gt;https://www.amazon.com/dp/B0DDMFMXNW&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Lulu &lt;a class="reference external" href="https://www.lulu.com/shop/steven-lott/unlearning-sql/paperback/product-yvnm8zn.html?page=1&amp;amp;pageSize=4"&gt;https://www.lulu.com/shop/steven-lott/unlearning-sql/paperback/product-yvnm8zn.html?page=1&amp;amp;pageSize=4&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Google Play &lt;a class="reference external" href="https://play.google.com/store/books/details?id=23WAEAAAQBAJ"&gt;https://play …&lt;/a&gt;&lt;/p&gt;&lt;/div&gt;</summary><content type="html">&lt;p&gt;We're talking about extracting data from complex relational databases.
This is -- in a way -- another case study for my &lt;em&gt;Unlearning SQL&lt;/em&gt; book.&lt;/p&gt;
&lt;div class="sidebar"&gt;
&lt;p class="first sidebar-title"&gt;&lt;em&gt;Unlearning SQL&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;KDP &lt;a class="reference external" href="https://www.amazon.com/dp/B0DDMFMXNW"&gt;https://www.amazon.com/dp/B0DDMFMXNW&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Lulu &lt;a class="reference external" href="https://www.lulu.com/shop/steven-lott/unlearning-sql/paperback/product-yvnm8zn.html?page=1&amp;amp;pageSize=4"&gt;https://www.lulu.com/shop/steven-lott/unlearning-sql/paperback/product-yvnm8zn.html?page=1&amp;amp;pageSize=4&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Google Play &lt;a class="reference external" href="https://play.google.com/store/books/details?id=23WAEAAAQBAJ"&gt;https://play.google.com/store/books/details?id=23WAEAAAQBAJ&lt;/a&gt;&lt;/p&gt;
&lt;p class="last"&gt;Apple Books &lt;a class="reference external" href="https://books.apple.com/us/book/unlearning-sql/id6443164060"&gt;https://books.apple.com/us/book/unlearning-sql/id6443164060&lt;/a&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;p&gt;In &lt;a class="reference external" href="https://slott56.github.io/2024-12-31_database_migration_part_i.html"&gt;Part I&lt;/a&gt;, we loaded a database and queried the metadata.
In &lt;a class="reference external" href="https://slott56.github.io/2025-01-07_database_migration_part_ii.html"&gt;Part II&lt;/a&gt;, we extracted the raw tables and loaded up a TAR Archive with NDJSON documents.
In &lt;a class="reference external" href="https://slott56.github.io/2025-01-14_database_migration_part_iii.html"&gt;Part III&lt;/a&gt;, we prepared native Python objects that had a complete representation for the various kinds of tree structures.
These include assets, categories, forum topics, image galleries, amongst other things.&lt;/p&gt;
&lt;p&gt;We can now explore the details of the data, looking for the original content.&lt;/p&gt;
&lt;div class="section" id="how-joomla-works"&gt;
&lt;h2&gt;How Joomla! Works&lt;/h2&gt;
&lt;p&gt;We're avoiding looking at the Jooml! elephant, wandering around the parlor.
Yes, we &lt;strong&gt;could&lt;/strong&gt; reverse engineer the PHP to figure out how the database content is used to build the web site.
We, however, don't much care about the details.&lt;/p&gt;
&lt;p&gt;We have a pile of specific kinds of content we can see.
This includes:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;A home page with a few articles.&lt;/li&gt;
&lt;li&gt;A right sidebar with some articles.&lt;/li&gt;
&lt;li&gt;A few pages with dozen or so narrowly-focused articles.&lt;/li&gt;
&lt;li&gt;The master collection of articles, neatly organized by category.&lt;/li&gt;
&lt;li&gt;Several separate pages of different kinds of links. These are actually articles with the links.&lt;/li&gt;
&lt;li&gt;The Kunena Forums.&lt;/li&gt;
&lt;li&gt;A Photo gallery that might be from RSGallery2 or JoomGallery.&lt;/li&gt;
&lt;li&gt;The Phoca Downloads.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;While there are a bunch of Joomla! Modules that seem to be used to define the site organization,
there is also a (simpler) tree of Assets.
For the most part, it appears we need a &amp;quot;grep&amp;quot;-like tool to dig through the database looking for
content.
Then we can work out what appears to be the owning asset, bypassing many Joomla! complications.&lt;/p&gt;
&lt;p&gt;Doing a grep on the raw data is actually kind of easy.&lt;/p&gt;
&lt;p&gt;The &amp;quot;raw&amp;quot; database has the following type definition:&lt;/p&gt;
&lt;pre class="literal-block"&gt;
DBTable = list[db_model.DBModel]

type Database = dict[str, DBTable]
&lt;/pre&gt;
&lt;p&gt;We can, then, use &lt;tt class="docutils literal"&gt;database.values()&lt;/tt&gt; to work all of the tables.
And for each table, all of the string columns looking for relevant rows.
It shapes up like this:&lt;/p&gt;
&lt;pre class="literal-block"&gt;
def row_match(pattern: Pattern, row: BaseModel) -&amp;gt; bool:
    for name in row.model_fields_set:
        val = getattr(row, name)
        match val:
            case str() as text if pattern.search(text):
                return True
            case _:
                pass
    return False
&lt;/pre&gt;
&lt;p&gt;Since the rows are based on &lt;tt class="docutils literal"&gt;pydantic.BaseModel&lt;/tt&gt;, we can introspect the columns and search the text-based columns to locate all rows that has a column matching the given pattern.
We search all of them because there are columns with names like &amp;quot;title&amp;quot;, &amp;quot;name&amp;quot;, and &amp;quot;alias&amp;quot;, all of which seem to have potentially relevant values, and we don't know &lt;strong&gt;precisely&lt;/strong&gt; what the semantics of them are.
Here's the containing function that wraps the &lt;tt class="docutils literal"&gt;row_match&lt;/tt&gt; function:&lt;/p&gt;
&lt;pre class="literal-block"&gt;
&amp;#64;staticmethod
def execute(options: argparse.Namespace) -&amp;gt; None:
    &amp;quot;&amp;quot;&amp;quot;
    Search all str columns of all tables for the pattern.
    &amp;quot;&amp;quot;&amp;quot;

    # def row_match... (shown earlier)

    database = load_db(options.source)

    Grep.logger.info(&amp;quot;grep pattern %r&amp;quot;, options.pattern)
    pattern = re.compile(options.pattern)
    matcher = partial(row_match, pattern)
    for cls in database:
        # Could reuse :meth:`where` method for this
        for match in filter(matcher, database[cls]):
            print(type(match), shorten(repr(match), 128))
&lt;/pre&gt;
&lt;p&gt;This is a method of a &lt;tt class="docutils literal"&gt;Grep&lt;/tt&gt; command:&lt;/p&gt;
&lt;pre class="literal-block"&gt;
class Grep(Command):
    &amp;quot;&amp;quot;&amp;quot;Make a grep-like search through the content.&amp;quot;&amp;quot;&amp;quot;
    logger = logging.getLogger(&amp;quot;Grep&amp;quot;)

    &amp;#64;staticmethod
    def config_argparse(
        subparsers: argparse._SubParsersAction, defaults: dict[str, Any]
    ) -&amp;gt; None:
        parsers_grep = subparsers.add_parser(&amp;quot;grep&amp;quot;, help=&amp;quot;grep all tables for a Regex&amp;quot;)
        parsers_grep.add_argument(&amp;quot;--pattern&amp;quot;, &amp;quot;-p&amp;quot;, action=&amp;quot;store&amp;quot;, type=str)
        Command.common_args(parsers_grep, defaults)
        parsers_grep.set_defaults(command=Grep.execute)
&lt;/pre&gt;
&lt;p&gt;The abstract base class is defined like this:&lt;/p&gt;
&lt;pre class="literal-block"&gt;
class Command(abc.ABC):
    &amp;quot;&amp;quot;&amp;quot;CLI Command abstract base class.&amp;quot;&amp;quot;&amp;quot;

    logger: logging.Logger

    &amp;#64;staticmethod
    &amp;#64;abc.abstractmethod
    def config_argparse(
        subparsers: argparse._SubParsersAction, defaults: dict[str, Any]
    ) -&amp;gt; None: ...

    &amp;#64;staticmethod
    def common_args(parser: argparse.ArgumentParser, defaults: dict[str, Any]) -&amp;gt; None:
        parser.add_argument(
            &amp;quot;source&amp;quot;, action=&amp;quot;store&amp;quot;, type=Path, default=defaults.get(&amp;quot;source&amp;quot;)
        )

    &amp;#64;staticmethod
    &amp;#64;abc.abstractmethod
    def execute(options: argparse.Namespace) -&amp;gt; None: ...
&lt;/pre&gt;
&lt;p&gt;This provides a tidy package to wrap the &lt;tt class="docutils literal"&gt;grep&lt;/tt&gt; command so we can create a CLI to poke around in the database looking for the Home Page content, the various pages with narrowly-focused articles, and the specific articles with the links.&lt;/p&gt;
&lt;p&gt;This isn't quite enough to locate all of the various forums and galleries.
But it gets us started examining the content.
There's more -- of course -- but it's all outside the realm of SQL processing.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="the-path"&gt;
&lt;h2&gt;The Path&lt;/h2&gt;
&lt;p&gt;There are several steps on this path:&lt;/p&gt;
&lt;ol class="arabic simple"&gt;
&lt;li&gt;SQL legacy data.&lt;/li&gt;
&lt;li&gt;Python extract of SQL data.&lt;/li&gt;
&lt;li&gt;Python structures without SQL complications of foreign keys. And -- more important -- with proper hierarchies.&lt;/li&gt;
&lt;/ol&gt;
&lt;div class="figure"&gt;
&lt;img alt="The database migration path from SQL to an &amp;quot;intermediate&amp;quot; data structure." src="https://slott56.github.io/media/database_migration.png" /&gt;
&lt;p class="caption"&gt;The migration path so far&lt;/p&gt;
&lt;/div&gt;
&lt;p&gt;These first few transformations aren't the goal, of course.&lt;/p&gt;
&lt;p&gt;The goal is a directory tree of markdown and images that Hugo can transform into a static web site.
The rest of the exploration and migration isn't SQL-related at all.
It's a fairly complicated matter of finding the content and restating it in a form Hugo can work with.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="conclusion"&gt;
&lt;h2&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;We started with a SQL database, and carefully set it aside.
We wrote two small applications to get the data out of the SQL database.&lt;/p&gt;
&lt;ol class="arabic simple"&gt;
&lt;li&gt;&lt;tt class="docutils literal"&gt;scan_db.py&lt;/tt&gt; -- extracts the table definitions and PlantUML descriptions from the database.&lt;/li&gt;
&lt;li&gt;&lt;tt class="docutils literal"&gt;extract_db.py&lt;/tt&gt; -- extracts the data, writing a TAR file of NDJSON documents with all the database rows.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Once we had the data in a neutral form -- specifically NDJSON documents -- we could create
alternative models for the data and preparation steps to populate those models.&lt;/p&gt;
&lt;p&gt;This model is an integration part of exploring the data.
This means the exploration application evolves until it becomes the migration application.&lt;/p&gt;
&lt;p&gt;We start with a skeleton of &lt;tt class="docutils literal"&gt;view_content.py&lt;/tt&gt;.
This is based on a number of &lt;tt class="docutils literal"&gt;Builder&lt;/tt&gt; classes and a &lt;tt class="docutils literal"&gt;prepare_content()&lt;/tt&gt; function to get raw data organized into what appears to be a useful model.&lt;/p&gt;
&lt;p&gt;The steps in this &lt;tt class="docutils literal"&gt;view_content.py&lt;/tt&gt; application (and the association &lt;tt class="docutils literal"&gt;model.py&lt;/tt&gt;) are free of SQL complications.&lt;/p&gt;
&lt;p&gt;The conversion process has at least three parts:&lt;/p&gt;
&lt;ol class="arabic simple"&gt;
&lt;li&gt;Locate the relevant objects. Often, an instance of the &lt;tt class="docutils literal"&gt;Assets&lt;/tt&gt; class does this.
A relevant &lt;tt class="docutils literal"&gt;Assets&lt;/tt&gt; instance doesn't seem to be universal, though.&lt;/li&gt;
&lt;li&gt;Convert the objects for use by a static site generator like Hugo. This turns out to be pretty complicated.
There are a number of distinct cases for the different kinds of content: articles, images, downloads, and forum topics.
However, since we're done with SQL, these complications don't involve database queries.&lt;/li&gt;
&lt;li&gt;Write needed &lt;tt class="docutils literal"&gt;_index.md&lt;/tt&gt; files so Hugo &lt;em&gt;Sections&lt;/em&gt; and &lt;em&gt;Page Bundles&lt;/em&gt; will mimic the legacy site's Joomla! presentation.&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;
</content><category term="Python"></category><category term="#python"></category><category term="database"></category><category term="sql"></category></entry><entry><title>Database Migration, Part III</title><link href="https://slott56.github.io/2025-01-14_database_migration_part_iii.html" rel="alternate"></link><published>2025-01-14T07:21:00-05:00</published><updated>2025-01-14T07:21:00-05:00</updated><author><name>S.Lott</name></author><id>tag:slott56.github.io,2025-01-14:/2025-01-14_database_migration_part_iii.html</id><summary type="html">&lt;p&gt;We're talking about extracting data from complex relational databases.
This is -- in a way -- another case study for my &lt;em&gt;Unlearning SQL&lt;/em&gt; book.&lt;/p&gt;
&lt;div class="sidebar"&gt;
&lt;p class="first sidebar-title"&gt;&lt;em&gt;Unlearning SQL&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;KDP &lt;a class="reference external" href="https://www.amazon.com/dp/B0DDMFMXNW"&gt;https://www.amazon.com/dp/B0DDMFMXNW&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Lulu &lt;a class="reference external" href="https://www.lulu.com/shop/steven-lott/unlearning-sql/paperback/product-yvnm8zn.html?page=1&amp;amp;pageSize=4"&gt;https://www.lulu.com/shop/steven-lott/unlearning-sql/paperback/product-yvnm8zn.html?page=1&amp;amp;pageSize=4&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Google Play &lt;a class="reference external" href="https://play.google.com/store/books/details?id=23WAEAAAQBAJ"&gt;https://play …&lt;/a&gt;&lt;/p&gt;&lt;/div&gt;</summary><content type="html">&lt;p&gt;We're talking about extracting data from complex relational databases.
This is -- in a way -- another case study for my &lt;em&gt;Unlearning SQL&lt;/em&gt; book.&lt;/p&gt;
&lt;div class="sidebar"&gt;
&lt;p class="first sidebar-title"&gt;&lt;em&gt;Unlearning SQL&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;KDP &lt;a class="reference external" href="https://www.amazon.com/dp/B0DDMFMXNW"&gt;https://www.amazon.com/dp/B0DDMFMXNW&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Lulu &lt;a class="reference external" href="https://www.lulu.com/shop/steven-lott/unlearning-sql/paperback/product-yvnm8zn.html?page=1&amp;amp;pageSize=4"&gt;https://www.lulu.com/shop/steven-lott/unlearning-sql/paperback/product-yvnm8zn.html?page=1&amp;amp;pageSize=4&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Google Play &lt;a class="reference external" href="https://play.google.com/store/books/details?id=23WAEAAAQBAJ"&gt;https://play.google.com/store/books/details?id=23WAEAAAQBAJ&lt;/a&gt;&lt;/p&gt;
&lt;p class="last"&gt;Apple Books &lt;a class="reference external" href="https://books.apple.com/us/book/unlearning-sql/id6443164060"&gt;https://books.apple.com/us/book/unlearning-sql/id6443164060&lt;/a&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;p&gt;In &lt;a class="reference external" href="https://slott56.github.io/2024-12-31_database_migration_part_i.html"&gt;Part I&lt;/a&gt;, we loaded a database and queried the metadata.
In &lt;a class="reference external" href="https://slott56.github.io/2025-01-07_database_migration_part_ii.html"&gt;Part II&lt;/a&gt;, we extracted the raw tables and loaded up a TAR Archive with NDJSON documents.&lt;/p&gt;
&lt;p&gt;We can now move beyond the raw relational data into something more useful.&lt;/p&gt;
&lt;div class="section" id="things-sql-does-badly"&gt;
&lt;h2&gt;Things SQL Does Badly&lt;/h2&gt;
&lt;p&gt;One thing SQL does badly is model hierarchies. (This is one aspect of not handling graphs in general.)&lt;/p&gt;
&lt;p&gt;In a hierarchy -- a Directed Acyclic Graph -- there are nodes. A node may have a parent.
A mode may have one or more children.
A node with no parent is the &amp;quot;root&amp;quot; of the tree.
A node with no children is a &amp;quot;leaf&amp;quot; of the tree.&lt;/p&gt;
&lt;div class="figure"&gt;
&lt;img alt="Diagram of a tree." src="https://slott56.github.io/media/tree_model.png" /&gt;
&lt;p class="caption"&gt;A tree&lt;/p&gt;
&lt;/div&gt;
&lt;p&gt;The point is that the relationships are transitive -- the root has children that have children dot dot dot that have leaves.
No arbitrary &lt;tt class="docutils literal"&gt;&amp;lt;h1&amp;gt;&lt;/tt&gt; to &lt;tt class="docutils literal"&gt;&amp;lt;h6&amp;gt;&lt;/tt&gt; limit.
(Pragmatically, you don't &lt;em&gt;need&lt;/em&gt; very many levels.
Common SQL hacks impose limits to so a simple &lt;tt class="docutils literal"&gt;SELECT&lt;/tt&gt; statement and a programming languages like &lt;tt class="docutils literal"&gt;COBOL&lt;/tt&gt; will work.
The &lt;tt class="docutils literal"&gt;WITH&lt;/tt&gt; clause permits indefinite hierarchies, at the cost of consuming time querying the rows from the database.)&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="not-using-sql"&gt;
&lt;h2&gt;Not Using SQL&lt;/h2&gt;
&lt;p&gt;In ordinary, in-memory data structures, it makes sense to define tree structures like this:&lt;/p&gt;
&lt;pre class="literal-block"&gt;
from pydantic import BaseModel, Field, Json, WrapValidator

class AppModel(BaseModel, from_attributes=True, arbitrary_types_allowed=True):
    &amp;#64;property
    def pk(self) -&amp;gt; int:
        raise NotImplementedError()

    def __str__(self) -&amp;gt; str:
        base = super().__str__()
        return f&amp;quot;{self.__class__.__name__} {base}&amp;quot;

class Assets(AppModel):
    &amp;quot;&amp;quot;&amp;quot;
    1239 rows
    &amp;quot;&amp;quot;&amp;quot;

    &amp;#64;property
    def pk(self) -&amp;gt; int:
        return self.id

    id: int  # &amp;lt;&amp;lt;PK&amp;gt;&amp;gt; range 1..1334, in [(1, 1), (2, 1), (3, 1), (4, 1), (5, 1), ... 1234 more]
    parent_id: int  # range 1..941, in [(895, 361), (699, 51), (478, 49), (1, 42), (35, 38), ... 117 more]
    lft: int  # range 1..2475, in [(1, 2), (3, 1), (9, 1), (11, 1), (13, 1), ... 1233 more]
    rgt: int  # &amp;lt;&amp;lt;unique&amp;gt;&amp;gt; range 2..2477, in [(2477, 1), (2, 1), (8, 1), (10, 1), (12, 1), ... 1234 more]
    level: int  # range 1..5, in [(4, 766), (3, 254), (2, 90), (5, 85), (1, 43), ... 1 more]
    name: str  # &amp;lt;&amp;lt;unique&amp;gt;&amp;gt; range 'com_actionlogs'..'root.1', in [('root.1', 1), ('com_admin', 1), ('com_banners', 1), ('com_cache', 1), ('com_checkin', 1), ... 1234 more]
    title: str  # range 'Ar n-Inin (Hull #331)'..'vtest1', in [('Uncategorised', 7), ('Whitby42 #172 [...]', 5), ('General', 3), ('Introduction', 3), ('2008 Rendezvous', 3), ... 1170 more]
    rules: str  # range '{&amp;quot;core.admin&amp;quot;:[],&amp;quot;core.mana...'..'{}', in [('{}', 544), ('None', 348), ('{&amp;quot;core.delete&amp;quot;:{&amp;quot;...', 81), ('{&amp;quot;core.delete&amp;quot;:[]...', 74), ('{&amp;quot;core.delete&amp;quot;:[]...', 65), ... 25 more]

    children: list[&amp;quot;Assets&amp;quot;] = Field(default_factory=list, repr=False)
    parent: ref[&amp;quot;Assets&amp;quot;] | None = Field(default=None, repr=False)
    gallery_catgs: list[&amp;quot;Joomgallery_Catg&amp;quot;] = Field(default_factory=list, repr=False)
    galleries: list[&amp;quot;Joomgallery&amp;quot;] = Field(default_factory=list, repr=False)
    categories: list[&amp;quot;Categories&amp;quot;] = Field(default_factory=list, repr=False)
    content: list[&amp;quot;Content&amp;quot;] = Field(default_factory=list, repr=False)
    modules: list[&amp;quot;Modules&amp;quot;] = Field(default_factory=list, repr=False)
&lt;/pre&gt;
&lt;p&gt;An &lt;tt class="docutils literal"&gt;Assets&lt;/tt&gt; instance has &lt;tt class="docutils literal"&gt;children: &lt;span class="pre"&gt;list[&amp;quot;Assets&amp;quot;]&lt;/span&gt;&lt;/tt&gt;.
Similarly, an &lt;tt class="docutils literal"&gt;Assets&lt;/tt&gt; instance may have a weak reference to a parent, &lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;ref[&amp;quot;Assets&amp;quot;]&lt;/span&gt; | None&lt;/tt&gt;.&lt;/p&gt;
&lt;p&gt;It's a weak reference because two mutual references -- parent -&amp;gt; child and child -&amp;gt; parent -- will create a circularity that defeats reference counting.
Using weak references adds a bit of fussiness, but otherwise leads to objects that play well with others.&lt;/p&gt;
&lt;p&gt;Some of these fields are nonsense. The rest describe the asset tree used by Joomla!&lt;/p&gt;
&lt;p&gt;Having an explicit &lt;tt class="docutils literal"&gt;children&lt;/tt&gt; list attached to each &lt;tt class="docutils literal"&gt;Assets&lt;/tt&gt; saves going back to the database to do additional queries to find the children of a given asset.
Further, it makes it very easy to &amp;quot;walk&amp;quot; the transitive closure of all children under an asset.
And, it makes it very easy to locate the transitive closure of all parents of an asset.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="how-do-we-get-there"&gt;
&lt;h2&gt;How Do We Get There?&lt;/h2&gt;
&lt;p&gt;Building the the &lt;tt class="docutils literal"&gt;model&lt;/tt&gt; objects is a two-step process.&lt;/p&gt;
&lt;div class="section" id="step-the-first"&gt;
&lt;h3&gt;Step, the first&lt;/h3&gt;
&lt;p&gt;Most of the attributes are seeded from &lt;tt class="docutils literal"&gt;db_model&lt;/tt&gt; objects using a line like the following:&lt;/p&gt;
&lt;pre class="literal-block"&gt;
obj = Assets.model_validate(row)
&lt;/pre&gt;
&lt;p&gt;The &lt;tt class="docutils literal"&gt;model_validate()&lt;/tt&gt; moves data into a new instance of the  &lt;tt class="docutils literal"&gt;Assets&lt;/tt&gt; model.
The &lt;tt class="docutils literal"&gt;from_attributes=True&lt;/tt&gt; means attribute name matching is used; this means our &lt;tt class="docutils literal"&gt;AppModel&lt;/tt&gt; classes must have attribute names that match the &lt;tt class="docutils literal"&gt;DBModel&lt;/tt&gt; classes.
These have have attribute names that match the original SQL.
We have a reasonably transparent mapping because of this constraint.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="step-the-second"&gt;
&lt;h3&gt;Step, the second&lt;/h3&gt;
&lt;p&gt;The relationships don't resolve themselves.&lt;/p&gt;
&lt;p&gt;We need to attach children to parents and parents to children.
For this, we've defined a &lt;tt class="docutils literal"&gt;Builder&lt;/tt&gt; class.&lt;/p&gt;
&lt;pre class="literal-block"&gt;
class AssetsBuilder(AppModelBuilder):
    &amp;quot;&amp;quot;&amp;quot;
    &amp;#64;startuml
    hide circle
    skinparam linetype ortho

    entity Assets
    Assets }o-- &amp;quot;parent&amp;quot; Assets
    &amp;#64;enduml
    &amp;quot;&amp;quot;&amp;quot;

    class AppTable(model.AppTable[model.Assets]):
        pass

    def __call__(self, table: DBTable) -&amp;gt; model.AppTable[model.Assets]:
        items = self.AppTable.build(model.Assets, table)
        for item in items.values():
            if item.parent_id in items:
                items[item.parent_id].add_child(item)
        return items
&lt;/pre&gt;
&lt;p&gt;In SQL world, every &lt;tt class="docutils literal"&gt;Assets&lt;/tt&gt; row has a &lt;tt class="docutils literal"&gt;parent_id&lt;/tt&gt; column with a foreign key reference to another &lt;tt class="docutils literal"&gt;Assets&lt;/tt&gt;.
Or a null of some kind, maybe a database &lt;tt class="docutils literal"&gt;NULL&lt;/tt&gt;, maybe a zero.&lt;/p&gt;
&lt;p&gt;(There is &lt;strong&gt;not&lt;/strong&gt; one standard answer to null representation.
Don't &lt;tt class="docutils literal"&gt;&amp;#64;&lt;/tt&gt; me with it &lt;strong&gt;should&lt;/strong&gt; be &lt;tt class="docutils literal"&gt;NULL&lt;/tt&gt;.
In this case, it isn't &lt;tt class="docutils literal"&gt;NULL&lt;/tt&gt;, and it doesn't have to be a &lt;tt class="docutils literal"&gt;NULL&lt;/tt&gt;.
It's usually zero. Except in one case that seems to be the result of a bug of some kind.)&lt;/p&gt;
&lt;p&gt;(We'll look at the &lt;tt class="docutils literal"&gt;AppTable.build&lt;/tt&gt; later, for now I want to focus on the hierarchies.)&lt;/p&gt;
&lt;p&gt;For each &lt;tt class="docutils literal"&gt;Assets&lt;/tt&gt; object in &lt;tt class="docutils literal"&gt;items.values()&lt;/tt&gt;, we need to see if it has a parent.
If it does have a parent, we need to as the parent to add this child.
This will do two things: add the child to the parent's &lt;tt class="docutils literal"&gt;children&lt;/tt&gt; list, and &lt;strong&gt;also&lt;/strong&gt; set the parent relationship for each of the children.&lt;/p&gt;
&lt;pre class="literal-block"&gt;
def add_child(self, item: &amp;quot;Assets&amp;quot;) -&amp;gt; None:
    self.children.append(item)
    item.parent = ref(self)
&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="section" id="the-apptable-class"&gt;
&lt;h2&gt;The &lt;tt class="docutils literal"&gt;AppTable&lt;/tt&gt; class&lt;/h2&gt;
&lt;p&gt;The final step in the &lt;tt class="docutils literal"&gt;Builder&lt;/tt&gt; is a the &lt;tt class="docutils literal"&gt;AppTable&lt;/tt&gt;; a handy structure to manage each collection of objects.&lt;/p&gt;
&lt;p&gt;In the long run, this is not required.&lt;/p&gt;
&lt;p&gt;In the short run -- where we can't navigate the database -- it's really handy for exploring.&lt;/p&gt;
&lt;pre class="literal-block"&gt;
T_AppModel = TypeVar(&amp;quot;T_AppModel&amp;quot;)


class AppTable[T_AppModel: AppModel](dict[Any, T_AppModel]):
    &amp;quot;&amp;quot;&amp;quot;
    A mapping from PK id to AppModel instance.
    &amp;quot;&amp;quot;&amp;quot;

    logger: ClassVar[logging.Logger]

    &amp;#64;classmethod
    def build(
        cls, row_cls: type[T_AppModel], db_table: Iterable[BaseModel]
    ) -&amp;gt; &amp;quot;AppTable[T_AppModel]&amp;quot;:
        cls.logger = logging.getLogger(cls.__name__)
        app_table = AppTable[T_AppModel]()
        for row in db_table:
            obj = row_cls.model_validate(row, from_attributes=True)
            if obj.pk in app_table:
                cls.logger.error(
                    &amp;quot;Duplicate key %r, replacing %r&amp;quot;, row, app_table[obj.pk]
                )
            app_table[obj.pk] = obj
        return app_table

    def where(
        self, filter_function: Callable[[T_AppModel], bool]
    ) -&amp;gt; Iterator[T_AppModel]:
        &amp;quot;&amp;quot;&amp;quot;
        A vaguely SQL-like search.
        &amp;quot;&amp;quot;&amp;quot;
        yield from filter(filter_function, self.values())
&lt;/pre&gt;
&lt;p&gt;This is where we build a &lt;tt class="docutils literal"&gt;model.Assets&lt;/tt&gt; object from the database &lt;tt class="docutils literal"&gt;db_model.Assets&lt;/tt&gt; object.
Further, we index them by the stated PK so we don't &lt;strong&gt;need&lt;/strong&gt; to search.&lt;/p&gt;
&lt;p&gt;The &lt;tt class="docutils literal"&gt;where()&lt;/tt&gt; method lets us provide a &lt;tt class="docutils literal"&gt;lambda&lt;/tt&gt; that searches the rows for matching instances.&lt;/p&gt;
&lt;pre class="literal-block"&gt;
featured = list(self.content.Content.where(lambda c: c.featured == 1))
&lt;/pre&gt;
&lt;p&gt;This is equivalent to &lt;tt class="docutils literal"&gt;SELECT * FROM content WHERE featured = 1&lt;/tt&gt; in SQL.
Except it's a lot faster.
And a lot more flexible.&lt;/p&gt;
&lt;p&gt;This is not &lt;strong&gt;heavily&lt;/strong&gt; used.
Most of what we need, we can find with ordinary foreign-key-to-primary-key relationships that use the native Python mappings.
A few things, like specific assets that define Joomla! modules and content categories, must be found by name, and will use the &lt;tt class="docutils literal"&gt;where()&lt;/tt&gt; method.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="all-the-things"&gt;
&lt;h2&gt;All the Things&lt;/h2&gt;
&lt;p&gt;Now that we can unravel the parent-child hierarchies, we can prepare the database for real work.&lt;/p&gt;
&lt;p&gt;We'll transform the original SQL-like structures to a module-like namespace
that has all the things we want, with their proper relationships.
There are 18 tables that seem to have all the content we care about.
For now, we're avoiding some of the installed Joomla! extensions.&lt;/p&gt;
&lt;pre class="literal-block"&gt;
def prepare_content(database: Database) -&amp;gt; SimpleNamespace:
    content = SimpleNamespace()

    content.Phocadownload_Categories = PhocaCategoryBuilder(content)(
        database[&amp;quot;Phocadownload_Categories&amp;quot;]
    )
    content.Phocadownload = PhocaDownloadBuilder(content)(database[&amp;quot;Phocadownload&amp;quot;])

    content.Kunena_Categories = KCategoryBuilder(content)(database[&amp;quot;Kunena_Categories&amp;quot;])
    content.Kunena_Topics = KTopicBuilder(content)(database[&amp;quot;Kunena_Topics&amp;quot;])
    content.Kunena_Messages = KMessageBuilder(content)(database[&amp;quot;Kunena_Messages&amp;quot;])
    content.Kunena_Messages_Text = KMessageTextBuilder(content)(
        database[&amp;quot;Kunena_Messages_Text&amp;quot;]
    )
    content.Kunena_Attachments = KAttachmentBuilder(content)(
        database[&amp;quot;Kunena_Attachments&amp;quot;]
    )

    content.Assets = AssetsBuilder(content)(database[&amp;quot;Assets&amp;quot;])

    content.Rsgallery2_Galleries = RSGalleryBuilder(content)(
        database[&amp;quot;Rsgallery2_Galleries&amp;quot;]
    )
    content.Rsgallery2_Files = RSFileBuilder(content)(database[&amp;quot;Rsgallery2_Files&amp;quot;])

    content.Joomgallery_Catg = JGCatgBuilder(content)(database[&amp;quot;Joomgallery_Catg&amp;quot;])
    content.Joomgallery = JGalleryBuilder(content)(database[&amp;quot;Joomgallery&amp;quot;])

    content.Categories = CategoriesBuilder(content)(database[&amp;quot;Categories&amp;quot;])
    content.Content = ContentBuilder(content)(database[&amp;quot;Content&amp;quot;])

    content.Modules = ModulesBuilder(content)(database[&amp;quot;Modules&amp;quot;])
    content.Menu = MenuBuilder(content)(database[&amp;quot;Menu&amp;quot;])
    content.Weblinks = WeblinksBuilder(content)(database[&amp;quot;Weblinks&amp;quot;])

    # The following are of dubious value...
    content.Modules_Menu = ModulesMenuAssoc(content)(database[&amp;quot;Modules_Menu&amp;quot;])
    content.Content_Frontpage = ContentFPBuilder(content)(database[&amp;quot;Content_Frontpage&amp;quot;])

    return content
&lt;/pre&gt;
&lt;p&gt;Each &lt;tt class="docutils literal"&gt;Builder&lt;/tt&gt; applies several transformative steps:&lt;/p&gt;
&lt;ol class="arabic simple"&gt;
&lt;li&gt;Build &lt;tt class="docutils literal"&gt;model&lt;/tt&gt; objects from &lt;tt class="docutils literal"&gt;db_model&lt;/tt&gt; objects for the relevant few &lt;tt class="docutils literal"&gt;DBTable&lt;/tt&gt; objects.&lt;/li&gt;
&lt;li&gt;Make &lt;tt class="docutils literal"&gt;AppTable&lt;/tt&gt; dictionaries from &lt;tt class="docutils literal"&gt;object.pk&lt;/tt&gt; to &lt;tt class="docutils literal"&gt;object&lt;/tt&gt;.&lt;/li&gt;
&lt;li&gt;Make trees for objects with parent-child relationships.&lt;/li&gt;
&lt;li&gt;Resolve other inter-object references.&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;
&lt;div class="section" id="next"&gt;
&lt;h2&gt;Next&lt;/h2&gt;
&lt;p&gt;Once we've got a proper namespace full of objects, we can start to explore it to find the relevant pieces.&lt;/p&gt;
&lt;p&gt;Are are the lines we've drawn to distinguish the various parts of our processing.&lt;/p&gt;
&lt;ol class="arabic simple"&gt;
&lt;li&gt;scan_db.py -- extracts the table definitions and PlantUML descriptions from the database.&lt;/li&gt;
&lt;li&gt;extract_db.py -- extracts the data, writing a TAR file of NDJSON documents with all the database rows.&lt;/li&gt;
&lt;li&gt;view_content.py -- &lt;tt class="docutils literal"&gt;Builder&lt;/tt&gt; classes and &lt;tt class="docutils literal"&gt;prepare_content()&lt;/tt&gt; function to get raw data organized.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;The steps in &lt;tt class="docutils literal"&gt;view_content&lt;/tt&gt; are free of SQL complications.&lt;/p&gt;
&lt;p&gt;In the next section we'll look at the conversion process.&lt;/p&gt;
&lt;p&gt;There will be three parts:&lt;/p&gt;
&lt;ol class="arabic simple"&gt;
&lt;li&gt;Locate the relevant objects&lt;/li&gt;
&lt;li&gt;Convert the objects for use by a static site generator like Hugo. This turns out to be pretty complicated. However, since we're done with SQL, the complications don't involve database queries.&lt;/li&gt;
&lt;li&gt;Write needed &lt;tt class="docutils literal"&gt;_index.md&lt;/tt&gt; files the mimic the legacy site's Joomla! presentation.&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;
</content><category term="Python"></category><category term="#python"></category><category term="database"></category><category term="sql"></category></entry><entry><title>Database Migration, Part II</title><link href="https://slott56.github.io/2025-01-07_database_migration_part_ii.html" rel="alternate"></link><published>2025-01-07T13:21:00-05:00</published><updated>2025-01-07T13:21:00-05:00</updated><author><name>S.Lott</name></author><id>tag:slott56.github.io,2025-01-07:/2025-01-07_database_migration_part_ii.html</id><summary type="html">&lt;p&gt;We're talking about extracting data from complex relational databases.
This is -- in a way -- another case study for my &lt;em&gt;Unlearning SQL&lt;/em&gt; book.&lt;/p&gt;
&lt;div class="sidebar"&gt;
&lt;p class="first sidebar-title"&gt;&lt;em&gt;Unlearning SQL&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;KDP &lt;a class="reference external" href="https://www.amazon.com/dp/B0DDMFMXNW"&gt;https://www.amazon.com/dp/B0DDMFMXNW&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Lulu &lt;a class="reference external" href="https://www.lulu.com/shop/steven-lott/unlearning-sql/paperback/product-yvnm8zn.html?page=1&amp;amp;pageSize=4"&gt;https://www.lulu.com/shop/steven-lott/unlearning-sql/paperback/product-yvnm8zn.html?page=1&amp;amp;pageSize=4&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Google Play &lt;a class="reference external" href="https://play.google.com/store/books/details?id=23WAEAAAQBAJ"&gt;https://play …&lt;/a&gt;&lt;/p&gt;&lt;/div&gt;</summary><content type="html">&lt;p&gt;We're talking about extracting data from complex relational databases.
This is -- in a way -- another case study for my &lt;em&gt;Unlearning SQL&lt;/em&gt; book.&lt;/p&gt;
&lt;div class="sidebar"&gt;
&lt;p class="first sidebar-title"&gt;&lt;em&gt;Unlearning SQL&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;KDP &lt;a class="reference external" href="https://www.amazon.com/dp/B0DDMFMXNW"&gt;https://www.amazon.com/dp/B0DDMFMXNW&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Lulu &lt;a class="reference external" href="https://www.lulu.com/shop/steven-lott/unlearning-sql/paperback/product-yvnm8zn.html?page=1&amp;amp;pageSize=4"&gt;https://www.lulu.com/shop/steven-lott/unlearning-sql/paperback/product-yvnm8zn.html?page=1&amp;amp;pageSize=4&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Google Play &lt;a class="reference external" href="https://play.google.com/store/books/details?id=23WAEAAAQBAJ"&gt;https://play.google.com/store/books/details?id=23WAEAAAQBAJ&lt;/a&gt;&lt;/p&gt;
&lt;p class="last"&gt;Apple Books &lt;a class="reference external" href="https://books.apple.com/us/book/unlearning-sql/id6443164060"&gt;https://books.apple.com/us/book/unlearning-sql/id6443164060&lt;/a&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;p&gt;In &lt;a class="reference external" href="https://slott56.github.io/2024-12-31_database_migration_part_i.html"&gt;Part I&lt;/a&gt;, we loaded a database and queried the metadata.
From this we created Python &lt;tt class="docutils literal"&gt;Table&lt;/tt&gt; and &lt;tt class="docutils literal"&gt;Column&lt;/tt&gt; objects that we used to record what we know about the data.
These class could also emit metadata in other formats.
The other formats include&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;a class="reference external" href="https://plantuml.com"&gt;PlantUML&lt;/a&gt; ERD diagrams.&lt;/li&gt;
&lt;li&gt;&lt;a class="reference external" href="https://docs.pydantic.dev/latest/"&gt;Pydantic&lt;/a&gt; class definitions.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;So far, these have given us a sense of what the data is.&lt;/p&gt;
&lt;p&gt;We've fiddled with the PUML file(s) to create ERD's that seem to capture our initial understandings.&lt;/p&gt;
&lt;p&gt;We've got a &lt;tt class="docutils literal"&gt;db_model.py&lt;/tt&gt; file full of class definitions we can use for further work.&lt;/p&gt;
&lt;div class="section" id="stumble-2-extract"&gt;
&lt;h2&gt;Stumble 2, Extract&lt;/h2&gt;
&lt;p&gt;We can write a database extract (and database reloader) to work with the NDJSON extracts.
Then we can kiss MariaDB goodbye, and stop the service from running on our laptop.&lt;/p&gt;
&lt;p&gt;The database metadata includes a lot of tables. We don't want all of them.
It's hard to be sure &lt;strong&gt;exactly&lt;/strong&gt; which ones we need, so it pays to be flexible.&lt;/p&gt;
&lt;p&gt;What makes sense to me is creating a list of relevant tables in the &lt;tt class="docutils literal"&gt;scan_db.py&lt;/tt&gt; application.
Then we can run it as often as we uncover another table that seems relevant.&lt;/p&gt;
&lt;p&gt;The extract can use a function like this to find the tables in the &lt;tt class="docutils literal"&gt;db_model&lt;/tt&gt; module.&lt;/p&gt;
&lt;pre class="literal-block"&gt;
def class_iter(module: ModuleType) -&amp;gt; Iterator[type]:
    other_imported_names = {
        &amp;quot;Decimal&amp;quot;,
        &amp;quot;ClassVar&amp;quot;,
        &amp;quot;Any&amp;quot;,
        &amp;quot;BaseModel&amp;quot;,
        &amp;quot;Field&amp;quot;,
        &amp;quot;DBModel&amp;quot;,
        &amp;quot;__class__&amp;quot;,  # SimpleNamespace
    }
    for name in dir(module):
        object = getattr(module, name)
        match object:
            case typing._AnyMeta():  # type: ignore
                pass
            case type() if name not in other_imported_names:
                print(&amp;quot;DEBUG&amp;quot;, name)
                yield object
&lt;/pre&gt;
&lt;p&gt;This yields the class definitions.
Here's the entire list of classes in the module.&lt;/p&gt;
&lt;pre class="literal-block"&gt;
classes = list(class_iter(db_model))
&lt;/pre&gt;
&lt;p&gt;Since each of our classes has a query and a &lt;tt class="docutils literal"&gt;from_query()&lt;/tt&gt; method,
getting the data for a given table looks like this:&lt;/p&gt;
&lt;pre class="literal-block"&gt;
def get_data(
    connection: mariadb.Connection, cls: type[db_model.DBModel]
) -&amp;gt; list[db_model.DBModel]:
    try:
        with connection.cursor() as crsr:
            crsr.execute(cls.query)
            data = [cls.from_query(row) for row in crsr.fetchall()]
            # pprint.pprint(data)
            return data
        print(cls.__name__, len(data))
    except AttributeError:
        print(f&amp;quot;***UNEXPECTED {cls.__name__}&amp;quot;)
        # print(cls.query)
        raise
&lt;/pre&gt;
&lt;p&gt;Execute the table's query. Convert the table's rows to the &lt;strong&gt;pydantic&lt;/strong&gt; model instances.
Return the list of instances.&lt;/p&gt;
&lt;p&gt;Producing an line in an NDJSON file is delightfully simple with Pydantic.&lt;/p&gt;
&lt;pre class="literal-block"&gt;
row.model_dump_json(indent=None)
&lt;/pre&gt;
&lt;p&gt;While we can easily make a bunch of NDJSON files, it offends me to have a whole directory full of files that we're only going to read.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="stumble-3-the-working-files"&gt;
&lt;h2&gt;Stumble 3, The Working Files&lt;/h2&gt;
&lt;p&gt;My first preference was to pickle the data.
It's easy to create a dictionary that maps table name to  a list of row instances.&lt;/p&gt;
&lt;p&gt;We have a common base class&lt;/p&gt;
&lt;pre class="literal-block"&gt;
class DBModel(BaseModel):
    query: ClassVar[str]

    &amp;#64;classmethod
    def from_query(cls, row: tuple[Any, ...]) -&amp;gt; &amp;quot;DBModel&amp;quot;:
        raise NotImplementedError()
&lt;/pre&gt;
&lt;p&gt;This means the database is&lt;/p&gt;
&lt;pre class="literal-block"&gt;
type Database = map[str, list[DBModel]]
&lt;/pre&gt;
&lt;p&gt;We can pickle this mapping and recover the entire thing.&lt;/p&gt;
&lt;p&gt;It's really quite elegant. And pretty fast, too.&lt;/p&gt;
&lt;div class="section" id="big-problem"&gt;
&lt;h3&gt;Big Problem&lt;/h3&gt;
&lt;p&gt;There's a big problem.&lt;/p&gt;
&lt;p&gt;The data is essentially wired to specific class definitions.
Change the class too much, and the data no longer loads from the pickle.&lt;/p&gt;
&lt;p&gt;Since this is exploratory, we won't know anything up front.
We need more flexibility.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="section" id="course-correction"&gt;
&lt;h2&gt;Course Correction&lt;/h2&gt;
&lt;p&gt;Pickle didn't work. What's next?&lt;/p&gt;
&lt;p&gt;Make a TAR Archive (compressed) with all the NDJSON members.
The extra CPU of compression is more than offset by the reduced time to do physical I/O on a smaller file.&lt;/p&gt;
&lt;p&gt;Here's the TAR write:&lt;/p&gt;
&lt;pre class="literal-block"&gt;
def save_data(
    content_path: Path,
    archive: tarfile.TarFile,
    cls: type[db_model.DBModel],
    data: list[db_model.DBModel],
) -&amp;gt; None:
    detail = (content_path / cls.__name__).with_suffix(&amp;quot;.ndjson&amp;quot;)
    with open(detail, &amp;quot;w&amp;quot;) as detail_file:
        for row in data:
            print(row.model_dump_json(indent=None), file=detail_file)
    info = archive.gettarinfo(detail, arcname=cls.__name__)
    print(info.name, info.size)
    with open(detail, &amp;quot;rb&amp;quot;) as detail_file:
        archive.addfile(info, detail_file)
    detail.unlink()
&lt;/pre&gt;
&lt;p&gt;The idea is to write a table of data to a file at the &lt;tt class="docutils literal"&gt;detail&lt;/tt&gt; path, add this to the open TAR archive, and then delete the &lt;tt class="docutils literal"&gt;detail&lt;/tt&gt; entry.
This leaves us with a TAR file filled with the extracted database rows.
Further, it's in JSON notation, so we can fiddle with the schema.&lt;/p&gt;
&lt;p&gt;The original SQL backup was 167,885,194 bytes.&lt;/p&gt;
&lt;p&gt;The useful subset of data, compressed, is 28,815,360 bytes. 17% of the original. About 1/5 the size.&lt;/p&gt;
&lt;p&gt;Simply rebuilding the original db_model collections goes quickly.
And I can make small changes without breaking things.&lt;/p&gt;
&lt;p&gt;It turns out, I don't want to make &lt;strong&gt;small&lt;/strong&gt; changes.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="the-raw-database-model"&gt;
&lt;h2&gt;The Raw Database Model&lt;/h2&gt;
&lt;p&gt;The real model is derived from the class definitions in the  &lt;tt class="docutils literal"&gt;db_model&lt;/tt&gt;  module.
I don't need the SQL query.  Or the &lt;tt class="docutils literal"&gt;from_query()&lt;/tt&gt; method.
The &lt;tt class="docutils literal"&gt;db_model&lt;/tt&gt; module is full of classes that have these features, but doesn't need them.&lt;/p&gt;
&lt;p&gt;To move on in the data pipeline, I need to reload data using &lt;tt class="docutils literal"&gt;db_model&lt;/tt&gt; class definitions.
Later, we'll start transforming this data as we undo the mischief of normalization.
Loading the data for exploration is done by this function:&lt;/p&gt;
&lt;pre class="literal-block"&gt;
def load_db(source_path: Path) -&amp;gt; Database:
    logger = logging.getLogger(&amp;quot;load_db&amp;quot;)
    database: Database = {}

    with tarfile.open(source_path, &amp;quot;r&amp;quot;) as archive:
        for item in archive.getmembers():
            cls = getattr(db_model, item.name)
            raw_file = archive.extractfile(item)
            if raw_file:
                reader = io.TextIOWrapper(raw_file)
                rows = DBTable(cls.model_validate_json(line) for line in reader)
                database[item.name] = rows
            else:
                logger.error(&amp;quot;archive item %r as no content&amp;quot;, item)
    return database
&lt;/pre&gt;
&lt;p&gt;I can read and validate the NDJSON documents with the following generator expression.&lt;/p&gt;
&lt;pre class="literal-block"&gt;
(cls.model_validate_json(line) for line in reader)
&lt;/pre&gt;
&lt;p&gt;We can use the &lt;strong&gt;Pydantic&lt;/strong&gt; &lt;tt class="docutils literal"&gt;model_validate_json()&lt;/tt&gt; method to create my target object.
I can now adjust attribute definitions in a limited way, and add new attributes.&lt;/p&gt;
&lt;p&gt;First, however, we need to take a look at the &lt;tt class="docutils literal"&gt;DBTable&lt;/tt&gt; class.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="the-dbtable-collection"&gt;
&lt;h2&gt;The DBTable Collection&lt;/h2&gt;
&lt;p&gt;For the purposes of reading the db tables back in from the TAR archive,
we have these two definitions:&lt;/p&gt;
&lt;pre class="literal-block"&gt;
class DBTable(list[db_model.DBModel]):
    pass


type Database = dict[str, DBTable]
&lt;/pre&gt;
&lt;p&gt;Yes, &lt;tt class="docutils literal"&gt;DBTable&lt;/tt&gt; is a &lt;tt class="docutils literal"&gt;list&lt;/tt&gt;. It could do more. It turns out, nothing more is needed.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="next"&gt;
&lt;h2&gt;Next&lt;/h2&gt;
&lt;p&gt;Once we've got a dictionary full of lists of data, we need to restructure it into a more useful form.
This means drawing some more lines to distinguish the various parts of our processing.&lt;/p&gt;
&lt;ol class="arabic simple"&gt;
&lt;li&gt;scan_db.py -- extracts the table definitions and PlantUML descriptions from the database.&lt;/li&gt;
&lt;li&gt;extract_db.py -- extracts the data, writing a TAR file of NDJSON documents with all the database rows.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Reading and &amp;quot;preparing&amp;quot; the data for deeper analysis is a separate application.&lt;/p&gt;
&lt;p&gt;It took a few mistakes to learn that the &lt;tt class="docutils literal"&gt;db_model&lt;/tt&gt; schema &lt;strong&gt;must&lt;/strong&gt; match the database.
We really can't tweak it.
We need to build a model derived from this model.&lt;/p&gt;
&lt;p&gt;In the next section we'll define the &lt;tt class="docutils literal"&gt;model.AppModel&lt;/tt&gt; class for objects derived from the &lt;tt class="docutils literal"&gt;db_table.DBModel&lt;/tt&gt; objects.
These &lt;tt class="docutils literal"&gt;AppModel&lt;/tt&gt; classes can have a number of additional fields and distinct annotated types and validation rules.
This makes it easy to build them using a line like the following:&lt;/p&gt;
&lt;pre class="literal-block"&gt;
obj = row_cls.model_validate(row, from_attributes=True)
&lt;/pre&gt;
&lt;p&gt;the &lt;tt class="docutils literal"&gt;model_validate()&lt;/tt&gt; moves data into the &lt;tt class="docutils literal"&gt;row_cls&lt;/tt&gt; model. The &lt;tt class="docutils literal"&gt;from_attributes=True&lt;/tt&gt; means attribute name matching is used.
This means our &lt;tt class="docutils literal"&gt;AppModel&lt;/tt&gt; classes must have attribute names that match the &lt;tt class="docutils literal"&gt;DBModel&lt;/tt&gt; classes.
These have have attribute names that match the original SQL.
We have a reasonably transparent mapping because of this constraint.&lt;/p&gt;
&lt;/div&gt;
</content><category term="Python"></category><category term="#python"></category><category term="database"></category><category term="sql"></category></entry><entry><title>Database Migration, Part I</title><link href="https://slott56.github.io/2024-12-31_database_migration_part_i.html" rel="alternate"></link><published>2024-12-31T13:21:00-05:00</published><updated>2024-12-31T13:21:00-05:00</updated><author><name>S.Lott</name></author><id>tag:slott56.github.io,2024-12-31:/2024-12-31_database_migration_part_i.html</id><summary type="html">&lt;p&gt;Let's talk about extracting data from complex relational databases.
This is -- in a way -- another case study for my Unlearning SQL book.&lt;/p&gt;
&lt;div class="sidebar"&gt;
&lt;p class="first sidebar-title"&gt;&lt;em&gt;Unlearning SQL&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;KDP &lt;a class="reference external" href="https://www.amazon.com/dp/B0DDMFMXNW"&gt;https://www.amazon.com/dp/B0DDMFMXNW&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Lulu &lt;a class="reference external" href="https://www.lulu.com/shop/steven-lott/unlearning-sql/paperback/product-yvnm8zn.html?page=1&amp;amp;pageSize=4"&gt;https://www.lulu.com/shop/steven-lott/unlearning-sql/paperback/product-yvnm8zn.html?page=1&amp;amp;pageSize=4&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Google Play &lt;a class="reference external" href="https://play.google.com/store/books/details?id=23WAEAAAQBAJ"&gt;https://play …&lt;/a&gt;&lt;/p&gt;&lt;/div&gt;</summary><content type="html">&lt;p&gt;Let's talk about extracting data from complex relational databases.
This is -- in a way -- another case study for my Unlearning SQL book.&lt;/p&gt;
&lt;div class="sidebar"&gt;
&lt;p class="first sidebar-title"&gt;&lt;em&gt;Unlearning SQL&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;KDP &lt;a class="reference external" href="https://www.amazon.com/dp/B0DDMFMXNW"&gt;https://www.amazon.com/dp/B0DDMFMXNW&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Lulu &lt;a class="reference external" href="https://www.lulu.com/shop/steven-lott/unlearning-sql/paperback/product-yvnm8zn.html?page=1&amp;amp;pageSize=4"&gt;https://www.lulu.com/shop/steven-lott/unlearning-sql/paperback/product-yvnm8zn.html?page=1&amp;amp;pageSize=4&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Google Play &lt;a class="reference external" href="https://play.google.com/store/books/details?id=23WAEAAAQBAJ"&gt;https://play.google.com/store/books/details?id=23WAEAAAQBAJ&lt;/a&gt;&lt;/p&gt;
&lt;p class="last"&gt;Apple Books &lt;a class="reference external" href="https://books.apple.com/us/book/unlearning-sql/id6443164060"&gt;https://books.apple.com/us/book/unlearning-sql/id6443164060&lt;/a&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;p&gt;This case study is about legacy database preservation: we want the data.
We don't want the code.&lt;/p&gt;
&lt;p&gt;Let's reach back 20 years, when packages like Joomla! were -- essentially -- the only way to have interactive, peer-maintained content.
Facebook barely existed before 2005.
Yahoo! Groups was what we had been using to share information as a kind of &amp;quot;social media&amp;quot; up until 2008, when the Joomla! site was started.&lt;/p&gt;
&lt;p&gt;We have web content reaching back almost two decades, most of it in a Jooma! database.
Some of the Joomla! articles are extracts stretching from the Yahoo! groups.
Currently, the user base interacts through Facebook, rarely touching this complicated web content.&lt;/p&gt;
&lt;p&gt;We want to preserve what's in Joomla! and migrate it into a simpler publishing system like Hugo (&lt;a class="reference external" href="https://gohugo.io"&gt;https://gohugo.io&lt;/a&gt;)
We intend to sacrifice some of the Kunena features. However. Since no one is interacting via the Kunena forums, this isn't a real sacrifice.&lt;/p&gt;
&lt;p&gt;Let's work through the conversion of data from a relational database to a directory of Markdown files.
One stumbling step at a time.&lt;/p&gt;
&lt;div class="section" id="stumble-1-what-do-we-have"&gt;
&lt;h2&gt;Stumble 1: What do we have?&lt;/h2&gt;
&lt;p&gt;We have a snapshot of the database. It's MariaDB/MySQL, and the snapshot is a big SQL script.
We can install MariaDB on our laptop and run the script.&lt;/p&gt;
&lt;p&gt;We have the data.&lt;/p&gt;
&lt;p&gt;What do we have?&lt;/p&gt;
&lt;p&gt;The Joomla! PHP world has a bunch of admin apps and tools to peek at the database.
We're not interested in these because they run on the server, which we'd like to disconnect from.&lt;/p&gt;
&lt;p&gt;First, we have tables&lt;/p&gt;
&lt;pre class="literal-block"&gt;
SELECT TABLE_NAME, TABLE_ROWS
FROM   information_schema.TABLES
WHERE  TABLE_SCHEMA = &amp;quot;testdb&amp;quot;
AND    TABLE_TYPE = &amp;quot;BASE TABLE&amp;quot;
&lt;/pre&gt;
&lt;p&gt;We can extract some details and helps us discover the Joomla! naming convention.
There's a prefix in front of each table name. &lt;tt class="docutils literal"&gt;j930_&lt;/tt&gt; and &lt;tt class="docutils literal"&gt;jos_&lt;/tt&gt; seem to be prefixes used
by previous admins to preserve some data for a test instance (or something.)
While we don't &lt;strong&gt;know&lt;/strong&gt; this, the overlapping names and smaller (or zero) row counts suggest these don't matter.
It's the &lt;tt class="docutils literal"&gt;j500_&lt;/tt&gt; tables that matter.&lt;/p&gt;
&lt;p&gt;It's best to fuss about with this initial peeking in a notebook, just uncover the metadata, and see what's going on.&lt;/p&gt;
&lt;p&gt;Plan to abandon the notebook.&lt;/p&gt;
&lt;p&gt;Here's how we prefer to deal with the schema&lt;/p&gt;
&lt;pre class="literal-block"&gt;
&amp;#64;dataclass
class Table:
    name: str
    num_rows: int | None = field(default=None)
    columns: dict[str, Column] = field(default_factory=dict)

    query: ClassVar[str] = &amp;quot;&amp;quot;&amp;quot;
        SELECT TABLE_NAME, TABLE_ROWS
        FROM   information_schema.TABLES
        WHERE  TABLE_SCHEMA = &amp;quot;testdb&amp;quot;
        AND    TABLE_TYPE = &amp;quot;BASE TABLE&amp;quot;
    &amp;quot;&amp;quot;&amp;quot;

    &amp;#64;classmethod
    def from_query(cls, row: tuple[Any, ...]) -&amp;gt; &amp;quot;Table&amp;quot;:
        return Table(name=row[0], num_rows=row[1])
&lt;/pre&gt;
&lt;p&gt;A reusable function can execute the query, and then use the &lt;tt class="docutils literal"&gt;from_query()&lt;/tt&gt; method
to build rows of the &lt;tt class="docutils literal"&gt;Table&lt;/tt&gt; class.&lt;/p&gt;
&lt;p&gt;The column metadata is a separate dataclass:&lt;/p&gt;
&lt;pre class="literal-block"&gt;
&amp;#64;dataclass
class Column:
    name: str
    type_name: str
    size: int
    python_type: str | None
    optionality_type: OptionalityType
    domain_type: DomainType | None = field(default=None)
    not_used: bool = field(default=False)
    val_min: Any = field(default=None)
    val_max: Any = field(default=None)
    val_common: list[tuple[Any, int]] = field(default_factory=list)
    val_cardinality: int = field(default=0)

    query: ClassVar[str] = &amp;quot;&amp;quot;&amp;quot;
        SELECT COLUMN_NAME, IS_NULLABLE, DATA_TYPE, CHARACTER_MAXIMUM_LENGTH
        FROM information_schema.COLUMNS
        WHERE TABLE_SCHEMA = &amp;quot;testdb&amp;quot;
        AND   TABLE_NAME = ?
    &amp;quot;&amp;quot;&amp;quot;

    &amp;#64;classmethod
    def from_query(cls, row: tuple[Any, ...]) -&amp;gt; &amp;quot;Column&amp;quot;:
        assert row[2].upper() in PYTHON_TYPE, f&amp;quot;unknown type {row[2]}&amp;quot;
        return Column(
            name=row[0],
            type_name=row[2],
            size=row[3],
            python_type=PYTHON_TYPE.get(row[2].upper()),
            optionality_type=(
                OptionalityType.REQUIRED if row[1] == &amp;quot;NO&amp;quot; else OptionalityType.OPTIONAL
            ),
        )
&lt;/pre&gt;
&lt;p&gt;The various &lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;val_...&lt;/span&gt;&lt;/tt&gt; attributes are populated later.
We need to query the data to get the minimum value, maximum value, the five most common values, and a general sense of the overall cardinality (is each value unique?)&lt;/p&gt;
&lt;p&gt;We can see what the columns mean when we see sample data.&lt;/p&gt;
&lt;p&gt;We'll do this with methods that are part of the &lt;tt class="docutils literal"&gt;Table&lt;/tt&gt; dataclass.&lt;/p&gt;
&lt;pre class="literal-block"&gt;
def rows(self, connection: mariadb.Connection) -&amp;gt; Iterator[dict[str, Any]]:
    query = f&amp;quot;&amp;quot;&amp;quot;SELECT * FROM {self.name}&amp;quot;&amp;quot;&amp;quot;
    with connection.cursor() as c:
        c.execute(query)
        column_names = [col[0] for col in c.description]
        for rt in c.fetchall():
            row_dict = dict(zip(column_names, rt))
            yield row_dict

def set_domain(self, connection: mariadb.Connection) -&amp;gt; None:
    raw_domains = collections.defaultdict(collections.Counter)
    for row in self.rows(connection):
        for name in self.columns.keys():
            raw_domains[name][row[name]] += 1
    for name, col in self.columns.items():
        col.set_domain(raw_domains[name])
&lt;/pre&gt;
&lt;p&gt;These were not shown above to keep the initial definition of &lt;tt class="docutils literal"&gt;Table&lt;/tt&gt; clear.&lt;/p&gt;
&lt;p&gt;The &lt;tt class="docutils literal"&gt;set_domain()&lt;/tt&gt; method for a &lt;tt class="docutils literal"&gt;Table&lt;/tt&gt; gets all of the data, and then -- column by column -- sets the data domain for the column.
For vast tables, these has to be approached in a different.
For this databsae, with under 10,000 rows in any given table, fetching all the rows works out quite nicely.&lt;/p&gt;
&lt;p&gt;This relies on a &lt;tt class="docutils literal"&gt;set_domain()&lt;/tt&gt; method for the &lt;tt class="docutils literal"&gt;Column&lt;/tt&gt; class. Like this:&lt;/p&gt;
&lt;pre class="literal-block"&gt;
def set_domain(self, frequencies: collections.Counter[Any]) -&amp;gt; None:
    values = list(filter(None, frequencies.keys()))
    if values:
        self.val_min = min(values)
        self.val_max = max(values)
        self.val_common = frequencies.most_common(5)
        self.val_cardinality = len(frequencies)
        if all(f == 1 for val, f in frequencies.items() if val is not None):
            self.domain_type = DomainType.UNIQUE
        else:
            self.domain_type = DomainType.NON_UNIQUE
    else:
        self.not_used = True
&lt;/pre&gt;
&lt;p&gt;There are two enum class definitions that are part of this, also.&lt;/p&gt;
&lt;pre class="literal-block"&gt;
class DomainType(StrEnum):
    UNIQUE = &amp;quot;unique&amp;quot;
    NON_UNIQUE = &amp;quot;non-unique&amp;quot;


class OptionalityType(StrEnum):
    OPTIONAL = &amp;quot;nullable&amp;quot;
    REQUIRED = &amp;quot;non-nullable&amp;quot;
&lt;/pre&gt;
&lt;p&gt;With this, we can build a schema -- a collection of &lt;tt class="docutils literal"&gt;Table&lt;/tt&gt; definitions -- from the database.
We can then view the beast as a whole.&lt;/p&gt;
&lt;p&gt;Which means what?&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="how-do-we-explore"&gt;
&lt;h2&gt;How do we Explore?&lt;/h2&gt;
&lt;p&gt;Step 1 is to build some ERD diagrams.&lt;/p&gt;
&lt;p&gt;We can add a method to &lt;tt class="docutils literal"&gt;Table&lt;/tt&gt; to expose it as a Plant UML entity:&lt;/p&gt;
&lt;pre class="literal-block"&gt;
def as_puml(self) -&amp;gt; str:
    buffer = io.StringIO()
    with contextlib.redirect_stdout(buffer):
        print(f&amp;quot;entity {self.name} {{ /' {self.num_rows} rows '/&amp;quot;)
        for col in (c for c in self.columns.values() if not c.not_used):
            flag = (
                &amp;quot;* &amp;quot;
                if col.domain_type == DomainType.UNIQUE
                and col.optionality_type == OptionalityType.REQUIRED
                else &amp;quot;&amp;quot;
            )
            print(
                f&amp;quot;  {flag}{col.name} {col.type_name}({col.size}) /' {col.optionality_type}, {col.domain_type}, range {col.value_range}, {col.value_common} '/&amp;quot;
            )
        print(&amp;quot;}&amp;quot;)
    return buffer.getvalue()
&lt;/pre&gt;
&lt;p&gt;The output is a block of text like this:&lt;/p&gt;
&lt;pre class="literal-block"&gt;
entity j500_assets { /' 1239 rows '/
  * id INT(4) /' non-nullable, unique, range 1..1334, [(1, 1), (2, 1), (3, 1), (4, 1), (5, 1), ... 1234 more] '/
  parent_id INT(4) &amp;lt;&amp;lt;FK&amp;gt;&amp;gt; /' non-nullable, non-unique, range 1..941, [(895, 361), (699, 51), (478, 49), (1, 42), (35, 38), ... 117 more] '/
  --
  lft INT(4) /' non-nullable, non-unique, range 1..2475, [(1, 2), (3, 1), (9, 1), (11, 1), (13, 1), ... 1233 more] '/
  rgt INT(4) /' non-nullable, unique, range 2..2477, [(2477, 1), (2, 1), (8, 1), (10, 1), (12, 1), ... 1234 more] '/
  level INT(4) /' non-nullable, non-unique, range 1..5, [(4, 766), (3, 254), (2, 90), (5, 85), (1, 43), ... 1 more] '/
  name VARMYSQL(200) /' non-nullable, unique, range 'com_actionlogs'..'root.1', [('root.1', 1), ('com_admin', 1), ('com_banners', 1), ('com_cache', 1), ('com_checkin', 1), ... 1234 more] '/
  title VARMYSQL(400) /' non-nullable, non-unique, range 'Ar n-Inin (Hull #331)'..'vtest1', [('Uncategorised', 7), ('Whitby42 #172 [...]', 5), ('General', 3), ('Introduction', 3), ('2008 Rendezvous', 3), ... 1170 more] '/
  rules VARMYSQL(20480) /' non-nullable, non-unique, range '{&amp;quot;core.admin&amp;quot;:[],&amp;quot;core.mana...'..'{}', [('{}', 544), ('None', 348), ('{&amp;quot;core.delete&amp;quot;:{&amp;quot;...', 81), ('{&amp;quot;core.delete&amp;quot;:[]...', 74), ('{&amp;quot;core.delete&amp;quot;:[]...', 65), ... 25 more] '/
}
note bottom: 1239 rows
&lt;/pre&gt;
&lt;p&gt;This isn't too pretty, but when the PlantUML tool finishes with it, it's a tidy little box in an ERD.
The long &lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;/'...'/&lt;/span&gt;&lt;/tt&gt; comments are not shown in the diagram.
They're helpful in the file because they show us the domain of values in a column.&lt;/p&gt;
&lt;p&gt;Once we have all of the entities in a &lt;tt class="docutils literal"&gt;.puml&lt;/tt&gt; file, we can insert relationships.
We can also partition the tables into packages to try and discern which ones have interesting content, and
which ones are operational overheads.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="what-we-didn-t-do"&gt;
&lt;h2&gt;What We Didn't Do&lt;/h2&gt;
&lt;p&gt;An important part of this is to &lt;strong&gt;not&lt;/strong&gt; -- emphatically &lt;strong&gt;not&lt;/strong&gt; -- build an ORM layer.
We don't really want to try and get ORM class definitions wrapped around a legacy database.
It's technically possible.
The tables are small, so there may not be profound performance problems.&lt;/p&gt;
&lt;p&gt;It's much, much easier to extract that data from the database, and build native Python objects.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="what-we-will-do"&gt;
&lt;h2&gt;What We Will Do&lt;/h2&gt;
&lt;p&gt;The goal is to have a &lt;tt class="docutils literal"&gt;db_model&lt;/tt&gt; module with &lt;strong&gt;Pydantic&lt;/strong&gt; &lt;tt class="docutils literal"&gt;BaseModel&lt;/tt&gt; definitions for the tables we want to preserve.
As we'll see in the next section, we can query the database and populate the &lt;strong&gt;Pydantic&lt;/strong&gt; class definitions.
We can then dump these Python objects into NDJSON files so we can explore without the overheads of SQL or MariaDB.&lt;/p&gt;
&lt;p&gt;The relational model -- and the requirement to normalize -- has decomposed relatively straight-forward
objects into a table of tables with primary keys, foreign keys, and equijoin operations.
We want to undo the normalization and recreate a more sensible structure in native Python.
We really want to have nested &lt;tt class="docutils literal"&gt;for&lt;/tt&gt; statements without have to create cursors and execute queries.&lt;/p&gt;
&lt;p&gt;We want to be able to create dictionaries without the overhead of defining an index.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="the-database-model"&gt;
&lt;h2&gt;The Database Model&lt;/h2&gt;
&lt;p&gt;The starting position is some &lt;strong&gt;Pydantic&lt;/strong&gt; class definitions for the database tables.
This is another method of the &lt;tt class="docutils literal"&gt;Table&lt;/tt&gt; class.&lt;/p&gt;
&lt;pre class="literal-block"&gt;
def as_dataclass(self) -&amp;gt; str:
    &amp;quot;&amp;quot;&amp;quot;Actually, as pydantic DBModel subclass...&amp;quot;&amp;quot;&amp;quot;
    buffer = io.StringIO()
    with contextlib.redirect_stdout(buffer):
        column_subset = [c for c in self.columns.values() if not c.not_used]
        keys = set(
            col.name
            for col in column_subset
            if col.domain_type == DomainType.UNIQUE
            and col.optionality_type == OptionalityType.REQUIRED
        )
        print(f&amp;quot;class {self.class_name}(DBModel):&amp;quot;)
        print('    &amp;quot;&amp;quot;&amp;quot;')
        print(f&amp;quot;    {self.num_rows} rows&amp;quot;)
        print('    &amp;quot;&amp;quot;&amp;quot;')
        for col in column_subset:
            annotation = (
                f&amp;quot;{col.python_type}&amp;quot;
                if col.optionality_type == OptionalityType.REQUIRED
                else f&amp;quot;{col.python_type} | None&amp;quot;
            )
            key = &amp;quot;&amp;lt;&amp;lt;PK&amp;gt;&amp;gt; &amp;quot; if col.name in keys else &amp;quot;&amp;quot;
            print(
                f&amp;quot;    {col.name}: {annotation}  # {key}range {col.value_range}, in {col.value_common}&amp;quot;
            )
        wrapped_names = [f&amp;quot;`{col.name}`&amp;quot; for col in column_subset]
        print()
        print('    query: ClassVar[str] = &amp;quot;&amp;quot;&amp;quot;')
        print(f&amp;quot;        SELECT {', '.join(wrapped_names)}&amp;quot;)
        print(f&amp;quot;          FROM {self.name}&amp;quot;)
        print('    &amp;quot;&amp;quot;&amp;quot;')
        print()
        print(&amp;quot;    &amp;#64;classmethod&amp;quot;)
        print(
            f&amp;quot;    def from_query(cls, row: tuple[Any, ...]) -&amp;gt; '{self.class_name}':&amp;quot;
        )
        print(f&amp;quot;        return {self.class_name}(&amp;quot;)
        for position, col in enumerate(column_subset):
            print(f&amp;quot;            {col.name}=row[{position}],&amp;quot;)
        print(&amp;quot;        )&amp;quot;)
    return buffer.getvalue()
&lt;/pre&gt;
&lt;p&gt;It writes the definition as Python code.
We can assemble a &lt;tt class="docutils literal"&gt;db_model&lt;/tt&gt; class from these.
Once we have that we're in a position to extract the data and build NDJSON files.&lt;/p&gt;
&lt;p&gt;This first part, then, is an application with a name like &lt;tt class="docutils literal"&gt;scan_db.py&lt;/tt&gt; to emit the UML,
and the db_model.&lt;/p&gt;
&lt;p&gt;We draw a line under this module because it deals with the available metadata.
It doesn't do the full extract.
Nor does it explore the data prior to migration.
The database metadata analysis is something we'd like to isolate, and run rarely.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="unit-testing"&gt;
&lt;h2&gt;Unit Testing&lt;/h2&gt;
&lt;p&gt;While -- in principle -- this is one-0ff software, test cases are essential.
We don't need 100% code or logical path coverage.
But, we do need enough coverage that we can refactor with confidence.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="documentation"&gt;
&lt;h2&gt;Documentation&lt;/h2&gt;
&lt;p&gt;The PUML files document the source database.&lt;/p&gt;
&lt;p&gt;We should create a &lt;tt class="docutils literal"&gt;docs&lt;/tt&gt; directory and put some notes in there about what this is and how to use it.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="next"&gt;
&lt;h2&gt;Next&lt;/h2&gt;
&lt;p&gt;So far, we have a sense of what the data is.&lt;/p&gt;
&lt;p&gt;We've fiddled with the PUML file(s) to create ERD's that seem to capture our initial understandings.&lt;/p&gt;
&lt;p&gt;We've got a &lt;tt class="docutils literal"&gt;db_model.py&lt;/tt&gt; file full of class definitions we can use for further work.&lt;/p&gt;
&lt;p&gt;We can write a database extract (and database reloader) to work with the NDJSON extracts.
Then we can kiss MariaDB goodbye, and stop the service from running on our laptop.&lt;/p&gt;
&lt;/div&gt;
</content><category term="Python"></category><category term="#python"></category><category term="database"></category><category term="sql"></category></entry><entry><title>Better than grep</title><link href="https://slott56.github.io/2024-09-26-better_than_grep.html" rel="alternate"></link><published>2024-09-26T09:50:00-04:00</published><updated>2024-09-26T09:50:00-04:00</updated><author><name>S.Lott</name></author><id>tag:slott56.github.io,2024-09-26:/2024-09-26-better_than_grep.html</id><summary type="html">&lt;p&gt;In the process of writing &lt;em&gt;Unlearning SQL&lt;/em&gt;, I had a need to extract SQL blocks from Python programs.
Of course, I tried &lt;tt class="docutils literal"&gt;grep&lt;/tt&gt;.
It wasn't ideal.&lt;/p&gt;
&lt;div class="admonition note"&gt;
&lt;p class="first admonition-title"&gt;Note&lt;/p&gt;
&lt;p&gt;Book is available here:&lt;/p&gt;
&lt;ul class="last simple"&gt;
&lt;li&gt;&lt;a class="reference external" href="https://www.amazon.com/dp/B0DDMFMXNW"&gt;https://www.amazon.com/dp/B0DDMFMXNW&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="reference external" href="https://www.lulu.com/shop/steven-lott/unlearning-sql/paperback/product-yvnm8zn.html?page=1&amp;amp;pageSize=4"&gt;https://www.lulu.com/shop/steven-lott/unlearning-sql/paperback/product-yvnm8zn.html?page=1&amp;amp;pageSize …&lt;/a&gt;&lt;/li&gt;&lt;/ul&gt;&lt;/div&gt;</summary><content type="html">&lt;p&gt;In the process of writing &lt;em&gt;Unlearning SQL&lt;/em&gt;, I had a need to extract SQL blocks from Python programs.
Of course, I tried &lt;tt class="docutils literal"&gt;grep&lt;/tt&gt;.
It wasn't ideal.&lt;/p&gt;
&lt;div class="admonition note"&gt;
&lt;p class="first admonition-title"&gt;Note&lt;/p&gt;
&lt;p&gt;Book is available here:&lt;/p&gt;
&lt;ul class="last simple"&gt;
&lt;li&gt;&lt;a class="reference external" href="https://www.amazon.com/dp/B0DDMFMXNW"&gt;https://www.amazon.com/dp/B0DDMFMXNW&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="reference external" href="https://www.lulu.com/shop/steven-lott/unlearning-sql/paperback/product-yvnm8zn.html?page=1&amp;amp;pageSize=4"&gt;https://www.lulu.com/shop/steven-lott/unlearning-sql/paperback/product-yvnm8zn.html?page=1&amp;amp;pageSize=4&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="reference external" href="https://play.google.com/store/books/details?id=23WAEAAAQBAJ"&gt;https://play.google.com/store/books/details?id=23WAEAAAQBAJ&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="reference external" href="https://books.apple.com/us/book/unlearning-sql/id6443164060"&gt;https://books.apple.com/us/book/unlearning-sql/id6443164060&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;p&gt;SQL blocks are -- ideally -- triple-quoted strings.
Of course, so are docstrings.
This creates additional problems.&lt;/p&gt;
&lt;p&gt;The first pass is to try to use &lt;tt class="docutils literal"&gt;grep&lt;/tt&gt; to track down the triple-quoted blocks.
This is a complex regular expression because it spans multiple lines.&lt;/p&gt;
&lt;p&gt;The output is a file of text with SQL stateements, docstrings, and quote marks all over the place.
It requires much manual cleanup.&lt;/p&gt;
&lt;p&gt;Doing the cleanup of the initial &lt;tt class="docutils literal"&gt;grep&lt;/tt&gt; output is right awful.
I need to somehow preserve a multi-line triple-quoted string that starts with a SQL reserved word,
and ignore multi-line triple-quoted strings that don't begin with something obvious SQL.&lt;/p&gt;
&lt;p&gt;I give up.&lt;/p&gt;
&lt;div class="section" id="python-re"&gt;
&lt;h2&gt;Python RE&lt;/h2&gt;
&lt;p&gt;Using Python to extract the initial strings and create a data structure is a good second step.
I can use the same regular expression with the Python &lt;tt class="docutils literal"&gt;re&lt;/tt&gt; module.
I can use &lt;tt class="docutils literal"&gt;Path.glob()&lt;/tt&gt; instead of shell globbing.&lt;/p&gt;
&lt;p&gt;I can now apply a second regular expression to the list-of-strings object to look for SQL words.
(There aren't many: CREATE, DROP, INSERT, UPDATE, SELECT, DELETE.)&lt;/p&gt;
&lt;p&gt;This is much nicer. But. I can do better.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="python-ast"&gt;
&lt;h2&gt;Python AST&lt;/h2&gt;
&lt;p&gt;This process is reading valid, tested Python code.
The standard library's &lt;tt class="docutils literal"&gt;ast&lt;/tt&gt; module defines the abstract syntax tree for Python code.
This module can identify literal strings in code, and docstrings.&lt;/p&gt;
&lt;p&gt;How does this work?&lt;/p&gt;
&lt;p&gt;There are three parts.&lt;/p&gt;
&lt;ol class="arabic simple"&gt;
&lt;li&gt;Define an &lt;tt class="docutils literal"&gt;ast.NodeVisitor&lt;/tt&gt; subclass.&lt;/li&gt;
&lt;li&gt;Parse the module's source to create an AST.&lt;/li&gt;
&lt;li&gt;Use the visitor to collect the strings.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;We'll look at each in some detail.&lt;/p&gt;
&lt;p&gt;First, a note for those unfamiliar with the &lt;strong&gt;Visitor&lt;/strong&gt; design pattern.
A tree structure is recursive.
Examining each node of the tree can be done with a recursive function to visit a node, then visit all descendents of that node.
It's not a complicated function, but, there's  a complication.&lt;/p&gt;
&lt;p&gt;The node in an abstract syntax tree tend to be a union of a wide variety of types.
There will be an &lt;tt class="docutils literal"&gt;ast.Module&lt;/tt&gt;, an &lt;tt class="docutils literal"&gt;ast.ClassDef&lt;/tt&gt;, &lt;tt class="docutils literal"&gt;ast.FunctionDev&lt;/tt&gt;, etc., etc.
A &amp;quot;simple&amp;quot; recursive function needs to treat each class distinctly to properly visit all of the children.&lt;/p&gt;
&lt;p&gt;The &lt;strong&gt;Visitor&lt;/strong&gt; pattern delegates part of the work to a visitor object that is presented each node of the tree.
The visitor object can have methods for each unique type of node, avoiding a complex &lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;match-case&lt;/span&gt;&lt;/tt&gt; statement,
or a complex &lt;tt class="docutils literal"&gt;if isinstance(node, &lt;span class="pre"&gt;Whatever)-elif&lt;/span&gt;&lt;/tt&gt; chain.&lt;/p&gt;
&lt;div class="section" id="the-visitor"&gt;
&lt;h3&gt;The Visitor&lt;/h3&gt;
&lt;p&gt;We're interested in one feature of the abstract syntax tree of Python: the &lt;tt class="docutils literal"&gt;ast.Constant&lt;/tt&gt; objects where the constant's value is a string.
Ideally, this will focus on those strings that start with a SQL keyword.&lt;/p&gt;
&lt;p&gt;Here's the starting point:&lt;/p&gt;
&lt;pre class="literal-block"&gt;
class StringVisitor(ast.NodeVisitor):
    def __init__(self) -&amp;gt; None:
        self.sql_blocks = []
    def visit_Constant(self, node: ast.AST) -&amp;gt; None:
        match node.value:
            case str() if sql_like(node.value):
                self.sql_blocks.append(range(node.lineno+1, node.end_lineno))
            case _:  # Ignore all other types
                pass
&lt;/pre&gt;
&lt;p&gt;The &lt;tt class="docutils literal"&gt;visit_Constant()&lt;/tt&gt; method is invoked for each &lt;tt class="docutils literal"&gt;ast.Constant&lt;/tt&gt; object in the tree.
This will find nodes that are strings and pass the &lt;tt class="docutils literal"&gt;sql_like()&lt;/tt&gt; filter.&lt;/p&gt;
&lt;p&gt;This net is a little too fine.
It can will capture docstring comments that happen to look SQL-like.
It's essentially the same as the &lt;tt class="docutils literal"&gt;grep&lt;/tt&gt; output with two improvements:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;No baffling regular expression.&lt;/li&gt;
&lt;li&gt;The output can be a more useful data structure, not a file of lines of text.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;While nicer in some respects, it's incomplete.
The next step is to add the required feature to ignore docstring comments.
There two choices:&lt;/p&gt;
&lt;ol class="arabic simple"&gt;
&lt;li&gt;Write a better &lt;tt class="docutils literal"&gt;sql_like()&lt;/tt&gt; function.&lt;/li&gt;
&lt;li&gt;Exclude any string constant that is the first line of the body of a module, class, or function definition.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Option 1 can can involve finding a SQL parser to see if a string really is pure SQL.
Or, it can require writing a better regular expression to locate likely SQL statements.&lt;/p&gt;
&lt;p&gt;Here's the unit test case:&lt;/p&gt;
&lt;pre class="literal-block"&gt;
&amp;gt;&amp;gt;&amp;gt; src_text = &amp;quot;&amp;quot;&amp;quot;
... def f(c):
...     '''
...         Select the right answers.
...     '''
...     r = c.execute('''
...         SELECT * FROM DUMMY
...     ''')
... &amp;quot;&amp;quot;&amp;quot;
&lt;/pre&gt;
&lt;p&gt;The docstring comment starts with a SQL keyword. Ugh.
It seems kind of daunting to locate a suitable SQL parser.
The regular expression to distinguish casual use of SQL-like keywords seems hopeless complicated.
There's something better: exclude docstring constants.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="exclusion-rules"&gt;
&lt;h3&gt;Exclusion Rules&lt;/h3&gt;
&lt;p&gt;The &lt;strong&gt;ast.NodeVisitor&lt;/strong&gt; implementation has a handy feature.
This permits an application to choose to visit or skip the subsidiary nodes of an object.
When a class overrides a &lt;tt class="docutils literal"&gt;visit_XXX()&lt;/tt&gt; method, the override can call the &lt;tt class="docutils literal"&gt;self.generic_visit(node)&lt;/tt&gt; to visit all the children.
If the overriding method does not evaluate &lt;tt class="docutils literal"&gt;self.generic_visit(node)&lt;/tt&gt;, the children are &lt;strong&gt;not&lt;/strong&gt; examined.&lt;/p&gt;
&lt;p&gt;This is a bit too strict.&lt;/p&gt;
&lt;p&gt;Skipping &lt;strong&gt;all&lt;/strong&gt; children of a module, class definition or function definition isn't helpful.
The rest of the children could have SQL code.
It's important to skip only the very first line of code when this is a string constant.
The rest of the code needs to be visited.&lt;/p&gt;
&lt;p&gt;I decided to accumulate an &amp;quot;ignore these&amp;quot; set of nodes.
This set of nodes will be the first line of code that's also a string constant.
The &lt;tt class="docutils literal"&gt;visit_Constant()&lt;/tt&gt; can then politely decline these nodes.&lt;/p&gt;
&lt;p&gt;Here's the visitor class looks:&lt;/p&gt;
&lt;pre class="literal-block"&gt;
class StringVisitor(ast.NodeVisitor):
    def __init__(self) -&amp;gt; None:
        self.docstrings = set()  # Docstring nodes at the start of module, class, def
        self.sql_blocks = []
    def exclude_docstring(self, node: ast.AST) -&amp;gt; None:
        if isinstance(node.body[0], ast.Expr) and isinstance(node.body[0].value, ast.Constant):
            self.docstrings.add(node.body[0].value)
        self.generic_visit(node)
    visit_Module = exclude_docstring
    visit_ClassDef = exclude_docstring
    visit_FunctionDef = exclude_docstring
    def visit_Constant(self, node: ast.AST) -&amp;gt; None:
        match node.value:
        case str() if sql_like(node.value):
            if node not in self.docstrings:
                self.sql_blocks.append(range(node.lineno+1, node.end_lineno))
            case _:  # Ignore all other types
                pass
&lt;/pre&gt;
&lt;p&gt;First, I've added a a set of nodes to exclude.
(Nodes are immutable, and have a hash value, that's why a set works well for this.)&lt;/p&gt;
&lt;p&gt;Second, there's a generic visit function, &lt;tt class="docutils literal"&gt;exclude_docstring()&lt;/tt&gt;, that handles &lt;tt class="docutils literal"&gt;ast.Module&lt;/tt&gt;, &lt;tt class="docutils literal"&gt;ast.ClassDef&lt;/tt&gt; and &lt;tt class="docutils literal"&gt;ast.FunctionDef&lt;/tt&gt; classes.
I can define the needed &lt;tt class="docutils literal"&gt;visti_XXX()&lt;/tt&gt; methods for these classes to all use the generic exclusion method.&lt;/p&gt;
&lt;p&gt;Finally, I need to make sure any &lt;tt class="docutils literal"&gt;ast.Constant&lt;/tt&gt; node wasn't already excluded because it was the first string in a definition.&lt;/p&gt;
&lt;p&gt;With this, I can now parse the source, and apply the visitor.
The &lt;tt class="docutils literal"&gt;sql_blocks&lt;/tt&gt; attribute will have a list of &lt;tt class="docutils literal"&gt;range()&lt;/tt&gt; objects that point to the SQL statements.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="the-parsing-and-visiting"&gt;
&lt;h3&gt;The Parsing and Visiting&lt;/h3&gt;
&lt;p&gt;Here's the final bit.&lt;/p&gt;
&lt;pre class="literal-block"&gt;
tree = ast.parse(src_text, &amp;quot;&amp;lt;test&amp;gt;&amp;quot;)
sv = StringVisitor()
sv.visit(tree)
print(sv.sql_blocks)
&lt;/pre&gt;
&lt;p&gt;This will parse the code, then visit the ast.&lt;/p&gt;
&lt;p&gt;When this is done, it has a list of &lt;tt class="docutils literal"&gt;range()&lt;/tt&gt; objects.
These ranges can be used to highlight the proper lines of code from the source file.
Your application may be different, of course, you may want to simply write the literals to a TOML configuration file, for example.&lt;/p&gt;
&lt;p&gt;&amp;quot;Why,&amp;quot; you might ask, &amp;quot;do you have range() objects instead of the code?&amp;quot;&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="section" id="the-ultimate-goal"&gt;
&lt;h2&gt;The Ultimate Goal&lt;/h2&gt;
&lt;p&gt;In my specific (and unique) use case, I need to be sure the &lt;strong&gt;minted&lt;/strong&gt; code highlighter will properly format SQL code.&lt;/p&gt;
&lt;p&gt;The goal is a book, written using RST markup, with properly colorized SQL examples.
That part is easy, what's hard is making sure all examples are unit-tested and really, really work.
(I'm fussy like that.)&lt;/p&gt;
&lt;p&gt;This means including the source text from a &lt;tt class="docutils literal"&gt;.py&lt;/tt&gt; file into the book's content.&lt;/p&gt;
&lt;p&gt;But. (Big Sigh.)&lt;/p&gt;
&lt;p&gt;Minted can't cope with a mixture of Python and SQL, and can't properly color code a few isolated SQL lines plucked from a Python context.
(This shouldn't have been too surprising; after I ranted and raved about it, I realized minted must colorize the whole file before a few lines can be selected from it.
Context matters when highlighting syntax.)&lt;/p&gt;
&lt;p&gt;In my application, I'm creating a parallel file with all &lt;strong&gt;non-SQL&lt;/strong&gt; lines prefixed with the &amp;quot;--&amp;quot; SQL comment marker.
This leaves the SQL behind to be seen by minted and properly colored.
The Python lines are hidden from minted.&lt;/p&gt;
&lt;p&gt;The book can then use an &lt;tt class="docutils literal"&gt;.. literalinclude::&lt;/tt&gt; directive that has properly highlighted SQL.
The Python file is unit tested, which gives me confidence in the SQL file.&lt;/p&gt;
&lt;/div&gt;
</content><category term="Python"></category><category term="parsing"></category><category term="ast"></category><category term="SQL"></category><category term="grep"></category></entry><entry><title>Modern Python Cookbook and Type Hints</title><link href="https://slott56.github.io/2024-09-18-cookbook_and_mypy.html" rel="alternate"></link><published>2024-09-18T16:03:00-04:00</published><updated>2024-09-18T16:03:00-04:00</updated><author><name>S.Lott</name></author><id>tag:slott56.github.io,2024-09-18:/2024-09-18-cookbook_and_mypy.html</id><summary type="html">&lt;p&gt;Modern Python Cookbook — with lots and lots of recipes — is something you might need. Find the results of checking all these recipes here:
&lt;a class="reference external" href="https://www.amazon.com/Modern-Python-Cookbook-updated-techniques/dp/1835466389"&gt;https://www.amazon.com/Modern-Python-Cookbook-updated-techniques/dp/1835466389&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;I (reluctantly) switched from using &lt;strong&gt;mypy&lt;/strong&gt; to using &lt;strong&gt;pyright&lt;/strong&gt; to check all of these recipes carefully. The type alias (&lt;a class="reference external" href="https://peps.python.org/pep-0695"&gt;PEP …&lt;/a&gt;&lt;/p&gt;</summary><content type="html">&lt;p&gt;Modern Python Cookbook — with lots and lots of recipes — is something you might need. Find the results of checking all these recipes here:
&lt;a class="reference external" href="https://www.amazon.com/Modern-Python-Cookbook-updated-techniques/dp/1835466389"&gt;https://www.amazon.com/Modern-Python-Cookbook-updated-techniques/dp/1835466389&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;I (reluctantly) switched from using &lt;strong&gt;mypy&lt;/strong&gt; to using &lt;strong&gt;pyright&lt;/strong&gt; to check all of these recipes carefully. The type alias (&lt;a class="reference external" href="https://peps.python.org/pep-0695"&gt;PEP 695&lt;/a&gt;) syntax wasn’t fully supported.&lt;/p&gt;
&lt;p&gt;I didn’t find any new problems with &lt;strong&gt;pyright&lt;/strong&gt;, but I did tweak my workflow a little bit to use more of &lt;strong&gt;pyright&lt;/strong&gt;’s linting features.&lt;/p&gt;
&lt;p&gt;Follow this for status on mypy &lt;a class="reference external" href="https://github.com/python/mypy/issues/15238"&gt;https://github.com/python/mypy/issues/15238&lt;/a&gt;.&lt;/p&gt;
&lt;div class="section" id="what-gets-checked"&gt;
&lt;h2&gt;What gets checked?&lt;/h2&gt;
&lt;p&gt;Using a vague phrase like &amp;quot;everything&amp;quot; is -- obviously -- not quite as helpful as some details.&lt;/p&gt;
&lt;p&gt;There are three broad categories of examples:&lt;/p&gt;
&lt;ol class="arabic simple"&gt;
&lt;li&gt;REPL examples.&lt;/li&gt;
&lt;li&gt;Code examples.&lt;/li&gt;
&lt;li&gt;Jupyter Lab examples.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Clearly, the REPL examples can't be checked. However. Many REPL examples depend on code definitions that can include checkable hints. The idea is to provide code outside the REPL and then use REPL doctest test cases to demonstrate how the code works. Which means many REPL examples have type hints that aren't shown in detail in the recipe.&lt;/p&gt;
&lt;p&gt;All of the ordinary code examples are checked.&lt;/p&gt;
&lt;p&gt;The Jupyter Lab examples in Chapter 12 are a mixed bag. Some examples involve an importable module, which can be checked. The notebook itself, though, is difficult to check. One &lt;strong&gt;could&lt;/strong&gt; convert the notebook to a script and the type check the script. This only works when the notebook is careful about not reusing variables.&lt;/p&gt;
&lt;p&gt;So that means all the example code in almost all of the chapters is put through &lt;strong&gt;pyright&lt;/strong&gt; to be &lt;strong&gt;sure&lt;/strong&gt; there's nothing obviously sketchy.&lt;/p&gt;
&lt;p&gt;I'd like to switch back to &lt;strong&gt;mypy&lt;/strong&gt;. I don't have a great reason, it just seems like it's vaguely &amp;quot;better&amp;quot;, measured on some axis I can't articulate.&lt;/p&gt;
&lt;/div&gt;
</content><category term="Python"></category><category term="book"></category><category term="packt"></category><category term="cookbook"></category></entry><entry><title>Modern Python Cookbook, 3e</title><link href="https://slott56.github.io/2024-08-01-modern_python_cookbook_3e.html" rel="alternate"></link><published>2024-08-01T13:41:00-04:00</published><updated>2024-08-01T13:41:00-04:00</updated><author><name>S.Lott</name></author><id>tag:slott56.github.io,2024-08-01:/2024-08-01-modern_python_cookbook_3e.html</id><summary type="html">&lt;p&gt;Book Announcement:&lt;/p&gt;
&lt;p&gt;&lt;a class="reference external" href="https://www.packtpub.com/en-us/product/modern-python-cookbook-9781835466384"&gt;https://www.packtpub.com/en-us/product/modern-python-cookbook-9781835466384&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;There are about 130 recipes in here. Some new. Some revised.
All examined (and tested) for Python 3.12.&lt;/p&gt;
&lt;p&gt;For the Python folks who are just starting as well as those looking to pick up some more skills, this is for …&lt;/p&gt;</summary><content type="html">&lt;p&gt;Book Announcement:&lt;/p&gt;
&lt;p&gt;&lt;a class="reference external" href="https://www.packtpub.com/en-us/product/modern-python-cookbook-9781835466384"&gt;https://www.packtpub.com/en-us/product/modern-python-cookbook-9781835466384&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;There are about 130 recipes in here. Some new. Some revised.
All examined (and tested) for Python 3.12.&lt;/p&gt;
&lt;p&gt;For the Python folks who are just starting as well as those looking to pick up some more skills, this is for you&lt;/p&gt;
&lt;p&gt;The recipes cover a wide variety of topics. It covers both functional and object-oriented programming.&lt;/p&gt;
&lt;p&gt;Here's a run-down of the chapters (yes, it's a long list):&lt;/p&gt;
&lt;ol class="arabic simple"&gt;
&lt;li&gt;Numbers, Strings, and Tuples&lt;/li&gt;
&lt;li&gt;Statements and Syntax&lt;/li&gt;
&lt;li&gt;Function Definitions&lt;/li&gt;
&lt;li&gt;Built-In Data Structures Part 1: Lists and Sets&lt;/li&gt;
&lt;li&gt;Built-In Data Structures Part 2: Dictionaries&lt;/li&gt;
&lt;li&gt;User Inputs and Outputs&lt;/li&gt;
&lt;li&gt;Basics of Classes and Objects&lt;/li&gt;
&lt;li&gt;More Advanced Class Design&lt;/li&gt;
&lt;li&gt;Functional Programming Features&lt;/li&gt;
&lt;li&gt;Working with Type Matching and Annotations&lt;/li&gt;
&lt;li&gt;Input/Output, Physical Format, and Logical Layout&lt;/li&gt;
&lt;li&gt;Graphics and Visualization with Jupyter Lab&lt;/li&gt;
&lt;li&gt;Application Integration: Configuration&lt;/li&gt;
&lt;li&gt;Application Integration: Combination&lt;/li&gt;
&lt;li&gt;Testing&lt;/li&gt;
&lt;li&gt;Dependencies and Virtual Environments&lt;/li&gt;
&lt;li&gt;Documentation and Style&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Most chapters have recipes that include beginners as well as  advanced developers.
For example, the first chapter covers the differences between true division and floor division.
Some folks get this deeply, where for others, it can feel cryptic at first.&lt;/p&gt;
&lt;p&gt;Some chapters are based on others, making them less beginner-oriented.
The Functional Programming Features chapter is based on the Function Definitions chapter.
The More Advanced Class Design chapter builds on the Basics of Classes and Objects chapter.&lt;/p&gt;
&lt;p&gt;Since the whole book is tested (both doctests and unit tests), I'm particularly fond of the Testing chapter.
I have read posts from too many folks saying that testing isn't a thing in school, and at work, they're left on their own to work out what and how to test.
I think I can help a little with some recipes on using docstrings, testing functions with exeptions, using unittest, using pytest, and mocking external resources.&lt;/p&gt;
&lt;p&gt;I’m hoping you’ll find this useful for growing a deeper understanding of Python programming.&lt;/p&gt;
</content><category term="Python"></category><category term="book"></category><category term="packt"></category><category term="cookbook"></category></entry><entry><title>Synthetic Data Tool</title><link href="https://slott56.github.io/2024-07-25-synthetic_data_tool.html" rel="alternate"></link><published>2024-07-25T14:22:00-04:00</published><updated>2024-07-25T14:22:00-04:00</updated><author><name>S.Lott</name></author><id>tag:slott56.github.io,2024-07-25:/2024-07-25-synthetic_data_tool.html</id><summary type="html">&lt;p&gt;See &lt;a class="reference external" href="https://slott56.github.io/2024-06-29-synthetic_data.html"&gt;Synthetic Data&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;I've updated the repository with a &amp;quot;Noisy Data&amp;quot; feature.&lt;/p&gt;
&lt;p&gt;This will generate bulk data with invalid field values.&lt;/p&gt;
&lt;p&gt;It helps with testing ETL pipelines to be sure they will scale to the expected volumes.&lt;/p&gt;
&lt;p&gt;Clone &lt;a class="reference external" href="https://github.com/slott56/DataSynthTool"&gt;https://github.com/slott56/DataSynthTool&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Read &lt;a class="reference external" href="https://slott56.github.io/DataSynthTool/_build/html/index.html"&gt;https://slott56.github.io/DataSynthTool/_build …&lt;/a&gt;&lt;/p&gt;</summary><content type="html">&lt;p&gt;See &lt;a class="reference external" href="https://slott56.github.io/2024-06-29-synthetic_data.html"&gt;Synthetic Data&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;I've updated the repository with a &amp;quot;Noisy Data&amp;quot; feature.&lt;/p&gt;
&lt;p&gt;This will generate bulk data with invalid field values.&lt;/p&gt;
&lt;p&gt;It helps with testing ETL pipelines to be sure they will scale to the expected volumes.&lt;/p&gt;
&lt;p&gt;Clone &lt;a class="reference external" href="https://github.com/slott56/DataSynthTool"&gt;https://github.com/slott56/DataSynthTool&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Read &lt;a class="reference external" href="https://slott56.github.io/DataSynthTool/_build/html/index.html"&gt;https://slott56.github.io/DataSynthTool/_build/html/index.html&lt;/a&gt;&lt;/p&gt;
</content><category term="Python"></category><category term="synthetic data"></category><category term="project"></category></entry><entry><title>Functional SQL in Pure Python</title><link href="https://slott56.github.io/2024-07-16-functional_sql_in_pure_python.html" rel="alternate"></link><published>2024-07-16T08:14:00-04:00</published><updated>2024-07-16T08:14:00-04:00</updated><author><name>S.Lott</name></author><id>tag:slott56.github.io,2024-07-16:/2024-07-16-functional_sql_in_pure_python.html</id><summary type="html">&lt;p&gt;I've published a framework for doing SQL-like programming in Pure Python -- no database required.&lt;/p&gt;
&lt;p&gt;Here: &lt;a class="reference external" href="https://github.com/slott56/functional-SQL"&gt;https://github.com/slott56/functional-SQL&lt;/a&gt;.
See the &lt;a class="reference external" href="https://slott56.github.io/functional-SQL/_build/html/index.html"&gt;functional-SQL&lt;/a&gt; documentation.&lt;/p&gt;
&lt;p&gt;This allows us to transform SQL:&lt;/p&gt;
&lt;div class="highlight"&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;n&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="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;c2&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;names_table&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;values_table&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;v&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;n&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;code&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;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;c1&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;To pure Python:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="n"&gt;Select&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;name …&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;</summary><content type="html">&lt;p&gt;I've published a framework for doing SQL-like programming in Pure Python -- no database required.&lt;/p&gt;
&lt;p&gt;Here: &lt;a class="reference external" href="https://github.com/slott56/functional-SQL"&gt;https://github.com/slott56/functional-SQL&lt;/a&gt;.
See the &lt;a class="reference external" href="https://slott56.github.io/functional-SQL/_build/html/index.html"&gt;functional-SQL&lt;/a&gt; documentation.&lt;/p&gt;
&lt;p&gt;This allows us to transform SQL:&lt;/p&gt;
&lt;div class="highlight"&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;n&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="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;c2&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;names_table&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;values_table&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;v&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;n&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;code&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;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;c1&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;To pure Python:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="n"&gt;Select&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="k"&gt;lambda&lt;/span&gt; &lt;span class="n"&gt;cr&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;cr&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;n&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="n"&gt;value&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="k"&gt;lambda&lt;/span&gt; &lt;span class="n"&gt;cr&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;cr&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;c2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;from_&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;names_table&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;values_table&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;where&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;cr&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;cr&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;code&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;cr&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;c1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Yes, the Python is longer and cluttered with lambdas.&lt;/p&gt;
&lt;p&gt;This produces the same results using a similar algorithm.&lt;/p&gt;
&lt;p&gt;Most important, this works with table-like collections of &lt;strong&gt;Any&lt;/strong&gt; class of Python objects.&lt;/p&gt;
&lt;p&gt;This implements the essential SQL query algorithm:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;having &lt;tt class="docutils literal"&gt;filter()&lt;/tt&gt;&lt;/li&gt;
&lt;li&gt;group-by &lt;tt class="docutils literal"&gt;reduce()&lt;/tt&gt;&lt;/li&gt;
&lt;li&gt;where &lt;tt class="docutils literal"&gt;filter()&lt;/tt&gt;&lt;/li&gt;
&lt;li&gt;select &lt;tt class="docutils literal"&gt;map()&lt;/tt&gt;&lt;/li&gt;
&lt;li&gt;from &lt;tt class="docutils literal"&gt;itertools.product()&lt;/tt&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This &lt;cite&gt;From-Select-Where-GroupBy-Having(Tables)&lt;/cite&gt; design pattern is very handy.
A lot of people think of processing data following this template.
There's no reason, however, to inject the overhead of schema and database.&lt;/p&gt;
</content><category term="Python"></category><category term="SQL"></category><category term="project"></category><category term="functional programming"></category><category term="functional python programming"></category></entry><entry><title>DataSynthTool Repository</title><link href="https://slott56.github.io/2024-07-01-datasynthtool_repository.html" rel="alternate"></link><published>2024-07-01T19:22:00-04:00</published><updated>2024-07-01T19:22:00-04:00</updated><author><name>S.Lott</name></author><id>tag:slott56.github.io,2024-07-01:/2024-07-01-datasynthtool_repository.html</id><content type="html">&lt;p&gt;I've published
a framework for making tools to leverage formal schema definitions to synthesize bulk data for performance tuning.&lt;/p&gt;
&lt;p&gt;Here: &lt;a class="reference external" href="https://github.com/slott56/DataSynthTool"&gt;https://github.com/slott56/DataSynthTool&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;See the &lt;a class="reference external" href="https://slott56.github.io/DataSynthTool/synthetic_data.slides.html#/"&gt;original talk&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;See the &lt;a class="reference external" href="https://slott56.github.io/DataSynthTool/_build/html/index.html"&gt;documentation&lt;/a&gt;&lt;/p&gt;
</content><category term="Python"></category><category term="synthetic data"></category><category term="project"></category></entry><entry><title>Synthetic Data</title><link href="https://slott56.github.io/2024-06-29-synthetic_data.html" rel="alternate"></link><published>2024-06-29T09:22:00-04:00</published><updated>2024-06-29T09:22:00-04:00</updated><author><name>S.Lott</name></author><id>tag:slott56.github.io,2024-06-29:/2024-06-29-synthetic_data.html</id><summary type="html">&lt;p&gt;&lt;strong&gt;Book?&lt;/strong&gt; Second draft (with tech review comments addressed) off to editors.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Boat?&lt;/strong&gt; Still on the hard during Hurricane Season.&lt;/p&gt;
&lt;div class="section" id="synthetic-data-1"&gt;
&lt;h2&gt;Synthetic Data&lt;/h2&gt;
&lt;p&gt;I've had a passing interest in data synthesis for decades.&lt;/p&gt;
&lt;p&gt;Early on in my career, I figured out how the Z/OS IEDBG utility worked.
See &lt;a class="reference external" href="https://www.ibm.com/docs/en/zos/3.1.0?topic=utilities-iebdg-test-data-generator-program"&gt;https://www.ibm …&lt;/a&gt;&lt;/p&gt;&lt;/div&gt;</summary><content type="html">&lt;p&gt;&lt;strong&gt;Book?&lt;/strong&gt; Second draft (with tech review comments addressed) off to editors.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Boat?&lt;/strong&gt; Still on the hard during Hurricane Season.&lt;/p&gt;
&lt;div class="section" id="synthetic-data-1"&gt;
&lt;h2&gt;Synthetic Data&lt;/h2&gt;
&lt;p&gt;I've had a passing interest in data synthesis for decades.&lt;/p&gt;
&lt;p&gt;Early on in my career, I figured out how the Z/OS IEDBG utility worked.
See &lt;a class="reference external" href="https://www.ibm.com/docs/en/zos/3.1.0?topic=utilities-iebdg-test-data-generator-program"&gt;https://www.ibm.com/docs/en/zos/3.1.0?topic=utilities-iebdg-test-data-generator-program&lt;/a&gt;.
It synthesized test data according to a number of mainframe-centric rules.&lt;/p&gt;
&lt;p&gt;When I started doing DBA work (Ingres, Oracle, DB2, etc.) the need for bulk synthetic data became more profound.
Folks would debate optimization questions without the benefit of &lt;strong&gt;facts&lt;/strong&gt;.
They'd suppose the optimizer might use an index or a 2NF demormalization might save some time.&lt;/p&gt;
&lt;p&gt;But.&lt;/p&gt;
&lt;p&gt;Without data, it's all second-guessing the DBMS algorithms.&lt;/p&gt;
&lt;div class="admonition note"&gt;
&lt;p class="first admonition-title"&gt;Note&lt;/p&gt;
&lt;p&gt;This is &lt;strong&gt;not&lt;/strong&gt; synthetic data for generative machine learning models.&lt;/p&gt;
&lt;p class="last"&gt;That's a possible application, but the focus is on databases, ETL, and data analytics.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="new-development"&gt;
&lt;h3&gt;New Development&lt;/h3&gt;
&lt;p&gt;The real &amp;quot;shoal water without a chart&amp;quot; problem is new development.&lt;/p&gt;
&lt;p&gt;You don't have legacy data with which to benchmark anything.&lt;/p&gt;
&lt;p&gt;You only have hand-waving ideas of what the data might look like once the application sees some adoption.&lt;/p&gt;
&lt;p&gt;And those are often wishful thinking. (Worse, they can be outright lies by folks seeking investment money.)&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="what-to-do"&gt;
&lt;h3&gt;What To Do?&lt;/h3&gt;
&lt;p&gt;All design is driven by data. (Remember, when you switch application code, you preserve the data; it's the real value.)&lt;/p&gt;
&lt;p&gt;All scalability problems are data-related.&lt;/p&gt;
&lt;p&gt;You can use ordinary &amp;quot;Big-O&amp;quot; complexity analysis to design algorithms that are optimal, but you don't know
any actual performance metrics without hardware, software, and -- what's really hard -- data.&lt;/p&gt;
&lt;p&gt;The &lt;strong&gt;only&lt;/strong&gt; thing you can do is follow this plan:&lt;/p&gt;
&lt;ol class="arabic simple"&gt;
&lt;li&gt;Write your Proof-of-Concept, Spike solution.&lt;/li&gt;
&lt;li&gt;Synthesize realistic volumes of data with realistic relationships among the values and realistic distributions of values.&lt;/li&gt;
&lt;li&gt;Benchmark the performance of the POC/Spike with actual data on actual hardware.&lt;/li&gt;
&lt;li&gt;Continuously monitor performance, establishing new benchmarks with better algorithms or data structures.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Without actual performance benchmarks, you're creating two scalability problems:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;The immediate problem of &amp;quot;can't start using it because it's too slow.&amp;quot;&lt;/li&gt;
&lt;li&gt;The subsequent problem of &amp;quot;users are complaining that it's slow.&amp;quot;&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class="section" id="okay-how-do-i-do-it"&gt;
&lt;h3&gt;Okay, How Do I Do It?&lt;/h3&gt;
&lt;p&gt;Which brings us to tools that synthesize data.&lt;/p&gt;
&lt;p&gt;This is &lt;strong&gt;not&lt;/strong&gt; synthetic data for generative machine learning models.&lt;/p&gt;
&lt;p&gt;This is synthetic data for database, bulk ETL, and ordinary statistical analysis performance testing.&lt;/p&gt;
&lt;p&gt;I've got a bunch of stuff that I'll be posting to Git with an approach that I think might be useful to others.&lt;/p&gt;
&lt;p&gt;It based on stuff I've done before. It includes the results of some lessons learned.&lt;/p&gt;
&lt;p&gt;More to come.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
</content><category term="Python"></category><category term="synthetic data"></category><category term="project"></category></entry><entry><title>Git Nightmare</title><link href="https://slott56.github.io/2024-05-07-github_nightmare.html" rel="alternate"></link><published>2024-05-07T08:01:00-04:00</published><updated>2024-05-07T08:01:00-04:00</updated><author><name>S.Lott</name></author><id>tag:slott56.github.io,2024-05-07:/2024-05-07-github_nightmare.html</id><summary type="html">&lt;p&gt;My sailing blog, &lt;a class="reference external" href="https://itmaybeahack.com/TeamRedCruising2/index.html"&gt;Team Red Cruising&lt;/a&gt;
is very large: 859 postings over the last few years. 2,334 image files.&lt;/p&gt;
&lt;p&gt;This is a LOT of content.&lt;/p&gt;
&lt;p&gt;A few of the files (were) Movies, which tend to create immense files.&lt;/p&gt;
&lt;p&gt;The whole mess was so big&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;How Big Was It?&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;It …&lt;/p&gt;</summary><content type="html">&lt;p&gt;My sailing blog, &lt;a class="reference external" href="https://itmaybeahack.com/TeamRedCruising2/index.html"&gt;Team Red Cruising&lt;/a&gt;
is very large: 859 postings over the last few years. 2,334 image files.&lt;/p&gt;
&lt;p&gt;This is a LOT of content.&lt;/p&gt;
&lt;p&gt;A few of the files (were) Movies, which tend to create immense files.&lt;/p&gt;
&lt;p&gt;The whole mess was so big&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;How Big Was It?&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;It was so big, the &lt;tt class="docutils literal"&gt;git push&lt;/tt&gt; command crashed. Repeatedly.&lt;/p&gt;
&lt;p&gt;I researched a lot of Stack Overflow answers on dealing with big files.  Maybe they were helpful.
Maybe they were misleading. I tried a &lt;strong&gt;lot&lt;/strong&gt; of things.&lt;/p&gt;
&lt;p&gt;The problem was using the following approach.&lt;/p&gt;
&lt;ol class="arabic simple"&gt;
&lt;li&gt;First. Get it all organized.&lt;/li&gt;
&lt;li&gt;Then. Do a single massive commit of everything.&lt;/li&gt;
&lt;li&gt;Now. The &lt;tt class="docutils literal"&gt;git push&lt;/tt&gt;.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;This -- it turns out -- is not a good idea. There are too many files and too many huge files.&lt;/p&gt;
&lt;div class="section" id="beating-to-weather"&gt;
&lt;h2&gt;Beating to Weather&lt;/h2&gt;
&lt;p&gt;It seemed sensible to try and preseve the &amp;quot;one big commit with everything in it.&amp;quot;&lt;/p&gt;
&lt;p&gt;This was -- of course -- a mistake.&lt;/p&gt;
&lt;p&gt;It's just too big to preserve. And there's no good reason to preserve it.&lt;/p&gt;
&lt;p&gt;It had some HUGE movie and PDF files that are better kept separate from the blog content.
Some of the pictures were exported at &lt;strong&gt;full&lt;/strong&gt; size, leading to about 100 image files of 22Mb or larger.
The resulting pack files are huge. Far too big to process.&lt;/p&gt;
&lt;p&gt;Trying to get the &lt;tt class="docutils literal"&gt;git gc&lt;/tt&gt; and &lt;tt class="docutils literal"&gt;git repack&lt;/tt&gt; commands to create some useful approach to uploading one big commit was (in retrospect) a waste of time and brain calories.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="image-resizing"&gt;
&lt;h2&gt;Image Resizing&lt;/h2&gt;
&lt;p&gt;This was fun. A little program using &lt;tt class="docutils literal"&gt;pillow&lt;/tt&gt; to do &lt;tt class="docutils literal"&gt;Image.reduce()&lt;/tt&gt; on the 100 or so egregiously large files.&lt;/p&gt;
&lt;pre class="literal-block"&gt;
def png_reduce(counts: Counter, path: Path, new: Path) -&amp;gt; None:
    target_image_size =  4_851_306  # about 2202 x 2202.

    counts[path.suffix] += 1
    if not path.suffix.lower() in {&amp;quot;.png&amp;quot;}:
        return
    try:
        with Image.open(path) as image:
            if image.width * image.height &amp;lt; target_image_size:
                counts['small'] += 1
                return
            counts['reduce'] += 1
            factor = int((image.width * image.height) / target_image_size + 0.5)
            target_width = int(image.width / factor + 0.5)
            target_height = int(image.height / factor + 0.5)
            reduced = image.reduce(factor)
            # Save reduced Image in separate directory. These can then be moved to replace the originals.
            target = new / path.name
            reduced.save(target)
        print(
            f&amp;quot;Reduce {path.name} {path.lstat().st_size // 1024 // 1024}M:&amp;quot;
            f&amp;quot; factor={factor} from {image.width}×{image.height} to {target_width}×{target_height}&amp;quot;
        )
    except UnidentifiedImageError as ex:
        counts['exception'] += 1
        print(path.name, ex)
&lt;/pre&gt;
&lt;p&gt;The target image size was a kind of guess. I divided the range of sizes into 64 bins.
I counted the number of files in each bin to see where the various size lumps occurred.
The bin with 4,851,306 seemed to be on the line between a lot of small files and a few large files.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="tack-to-a-new-course"&gt;
&lt;h2&gt;Tack to a New Course&lt;/h2&gt;
&lt;p&gt;What's the alternative?&lt;/p&gt;
&lt;p&gt;Here's what worked.&lt;/p&gt;
&lt;ol class="arabic simple"&gt;
&lt;li&gt;&lt;tt class="docutils literal"&gt;git reset&lt;/tt&gt; back to empty. (There was only one commit, so this was easy.)&lt;/li&gt;
&lt;li&gt;Put in the overhead files: &lt;tt class="docutils literal"&gt;README.rst&lt;/tt&gt;, &lt;tt class="docutils literal"&gt;requirements.txt&lt;/tt&gt;, the Pelican configuration files, &lt;tt class="docutils literal"&gt;Makefile&lt;/tt&gt;, etc.&lt;/li&gt;
&lt;li&gt;Commit this. And push.&lt;/li&gt;
&lt;li&gt;Put in the text content files, all 859 of them. Plus the handful of non-blog pages and photo albums.&lt;/li&gt;
&lt;li&gt;Commit this. And push.&lt;/li&gt;
&lt;li&gt;The images can be broken into 5 batches of about 400 files. Commit and push each of these.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;At the end of the day, it's all there, but it took 7 commits and 7 separate &lt;tt class="docutils literal"&gt;git push&lt;/tt&gt; operations to get there.&lt;/p&gt;
&lt;p&gt;It went really fast. Error free. No drama.&lt;/p&gt;
&lt;p&gt;Doubly aggravating because this could have been completed two days ago.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="the-finish-line"&gt;
&lt;h2&gt;The Finish Line&lt;/h2&gt;
&lt;p&gt;Now, I can do ordinary &lt;tt class="docutils literal"&gt;git pull&lt;/tt&gt; and &lt;tt class="docutils literal"&gt;git push&lt;/tt&gt; from &lt;strong&gt;any&lt;/strong&gt; computer.&lt;/p&gt;
&lt;p&gt;I can login to the hosting service from any computer, do a &lt;tt class="docutils literal"&gt;git pull&lt;/tt&gt; and a &lt;tt class="docutils literal"&gt;make publish&lt;/tt&gt;, and the site is updated.&lt;/p&gt;
&lt;p&gt;The point is to be able to use an iPad to edit content and leverage my hosting service to do a little more than serve static HTML.&lt;/p&gt;
&lt;/div&gt;
</content><category term="Python"></category><category term="git"></category><category term="github"></category><category term="memory"></category></entry><entry><title>DBLock Context Manager</title><link href="https://slott56.github.io/2024-03-10-dblock-context-manager.html" rel="alternate"></link><published>2024-03-10T08:01:00-04:00</published><updated>2024-03-10T08:01:00-04:00</updated><author><name>S.Lott</name></author><id>tag:slott56.github.io,2024-03-10:/2024-03-10-dblock-context-manager.html</id><summary type="html">&lt;p&gt;Consider, for a moment, the &lt;tt class="docutils literal"&gt;shelve&lt;/tt&gt; and &lt;tt class="docutils literal"&gt;dbm&lt;/tt&gt; packages for storing things in a “database.”
Built-in. Lightweight. The database is essentially a mapping from identifiers to objects.
It can be quite nice.&lt;/p&gt;
&lt;p&gt;The &lt;tt class="docutils literal"&gt;shelve&lt;/tt&gt; module directly puts Python objects in a file.
It’s an ideal database structure for Python …&lt;/p&gt;</summary><content type="html">&lt;p&gt;Consider, for a moment, the &lt;tt class="docutils literal"&gt;shelve&lt;/tt&gt; and &lt;tt class="docutils literal"&gt;dbm&lt;/tt&gt; packages for storing things in a “database.”
Built-in. Lightweight. The database is essentially a mapping from identifiers to objects.
It can be quite nice.&lt;/p&gt;
&lt;p&gt;The &lt;tt class="docutils literal"&gt;shelve&lt;/tt&gt; module directly puts Python objects in a file.
It’s an ideal database structure for Python, with relatively little overhead.&lt;/p&gt;
&lt;p&gt;If you don’t like using &lt;tt class="docutils literal"&gt;pickle&lt;/tt&gt;, you can use the underlying &lt;tt class="docutils literal"&gt;dbm&lt;/tt&gt; with something like Pydantic for class definitions.
This means explicitly serializing a representation of object state as bytes before stuffing them into the &lt;tt class="docutils literal"&gt;dbm&lt;/tt&gt;-managed mapping.
Pydantic class definitions can deserialize the bytes and recover the object's state.&lt;/p&gt;
&lt;p&gt;With a little effort at designing keys, these provide a persistent mapping for arbitrarily complex objects.
(Simple UUID's are nice, but sometimes it helps to provide a key with two parts: collection name and identifier.)&lt;/p&gt;
&lt;p&gt;Why?&lt;/p&gt;
&lt;p&gt;It lets you read and write objects without the complexity of an ORM layer and a SQL database.&lt;/p&gt;
&lt;p&gt;This is often really helpful. But. What about concurrent writes in a multiprocessing context? The &lt;tt class="docutils literal"&gt;shelve&lt;/tt&gt; page is clear:&lt;/p&gt;
&lt;blockquote&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;The shelve module does not support concurrent read/write access to shelved objects. (Multiple simultaneous read accesses are safe.) When a program has a shelf open for writing, no other program should have it open for reading or writing. &lt;strong&gt;Unix file locking can be used to solve this&lt;/strong&gt;, but this differs across Unix versions and requires knowledge about the database implementation used.&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;
&lt;p&gt;(Emphasis mine.) Okay. We get it; this is BYOL™  -- Bring Your Own Locking.&lt;/p&gt;
&lt;p&gt;Which — it turns out — isn’t trivial.
See &lt;a class="reference external" href="https://en.wikipedia.org/wiki/Readers–writer_lock"&gt;https://en.wikipedia.org/wiki/Readers–writer_lock&lt;/a&gt; for information on multiple readers and single writers.&lt;/p&gt;
&lt;div class="section" id="the-single-writer-locking-problem"&gt;
&lt;h2&gt;The Single Writer Locking Problem&lt;/h2&gt;
&lt;p&gt;We can use the &lt;tt class="docutils literal"&gt;fcntl&lt;/tt&gt; module to lock files. This module (and the Linux OS) offers exclusive locks and shared locks.&lt;/p&gt;
&lt;p&gt;(For Windows folks, get a package like &lt;tt class="docutils literal"&gt;portalocker&lt;/tt&gt; or use the functions in &lt;tt class="docutils literal"&gt;pywin32&lt;/tt&gt;.)&lt;/p&gt;
&lt;p&gt;The objectives are these:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;Take out an exclusive lock for transactions that will update the database.&lt;/li&gt;
&lt;li&gt;Take out a shared lock for transactions that read the database.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This Shared vs. Exclusive locking is elegant, but also habors a small problem.&lt;/p&gt;
&lt;p&gt;Consider a web server. Each GET transaction acquires a shared lock, reads the data, prepares the response, and releases the shared lock. Because the shared lock prevents a writer from getting an exclusive lock, the data is untouched for the duration of the transaction.
There's no possibility of data file corruption mid-transaction because of a concurrent write.&lt;/p&gt;
&lt;p&gt;Each POST/PUT/PATCH/DELETE transaction acquires an exclusive lock, reads and writes the data, prepares a response, and releases the exclusive lock. This forces the writer to wait for readers to finish. It prevents any reader from seeing incomplete
or uncreadable files.&lt;/p&gt;
&lt;p&gt;The underlying &lt;tt class="docutils literal"&gt;dbm&lt;/tt&gt; file is updated in one smooth, atomic event. Everyone sees the data in a consistent state at all times.&lt;/p&gt;
&lt;p&gt;Yes, it's coarse-grained whole-database level locking. The point was to avoid the overheads of a huge SQL
server. A RESTful service can read and write local files. Emphasis on the read. Why have a super-elaborate database server
to provide rows and tables that have to be assembled into JSON documents? Maybe just read the JSON document from the database
and reply with it.&lt;/p&gt;
&lt;p&gt;But.&lt;/p&gt;
&lt;p&gt;There's a problem: a parade of readers can prevent the writer from getting in.&lt;/p&gt;
&lt;table border="1" class="docutils"&gt;
&lt;colgroup&gt;
&lt;col width="33%" /&gt;
&lt;col width="33%" /&gt;
&lt;col width="33%" /&gt;
&lt;/colgroup&gt;
&lt;thead valign="bottom"&gt;
&lt;tr&gt;&lt;th class="head"&gt;time&lt;/th&gt;
&lt;th class="head"&gt;action&lt;/th&gt;
&lt;th class="head"&gt;lock count&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody valign="top"&gt;
&lt;tr&gt;&lt;td&gt;T0&lt;/td&gt;
&lt;td&gt;reader 1 starts&lt;/td&gt;
&lt;td&gt;1 shared lock&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;T1&lt;/td&gt;
&lt;td&gt;reader 2 starts&lt;/td&gt;
&lt;td&gt;2 shared locks&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;T2&lt;/td&gt;
&lt;td&gt;writer 3 waiting for an exclusive lock...&lt;/td&gt;
&lt;td&gt;&amp;nbsp;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;T3&lt;/td&gt;
&lt;td&gt;reader 2 finishes&lt;/td&gt;
&lt;td&gt;1 shared lock&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;T4&lt;/td&gt;
&lt;td&gt;reader 4 starts&lt;/td&gt;
&lt;td&gt;2 shared locks&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;T5&lt;/td&gt;
&lt;td&gt;reader 1 finishes&lt;/td&gt;
&lt;td&gt;1 shared lock&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;And so it goes: a sequence of overlapping readers will starve the writer.
This is called &lt;strong&gt;Livelock&lt;/strong&gt;, and — while rare — it’s not impossible.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="preventing-livelock"&gt;
&lt;h2&gt;Preventing Livelock&lt;/h2&gt;
&lt;p&gt;One algorithm for preventing livelock is to have a “pending writer queue” that the readers have to acknowledge.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Think of a velvet rope to get into the most exclusive club in town.&lt;/p&gt;
&lt;p&gt;The writer talks to the bouncer, and the line of readers is stopped. No one gets in. Once the club is empty of readers, the writer is allowed in and has the place to themself. When they’re done writing, then they’re out of there, so the readers can crowd the place again.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;This is sometimes called the “preferred writer” solution. It’s unfair by design because many web requests are read requests; write requests are less common. Overall performance depends on getting any write out of the way as soon as possible.
There are other variants that are more equitable, but also a bit more complicated.&lt;/p&gt;
&lt;p&gt;To prevent livelock, we need some kind of shared queue to broadcast to all concurrent processes that there’s a writer waiting.&lt;/p&gt;
&lt;p&gt;We can do this using two lock files:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;A “queue” lock. This is always Exclusive. It's always acquired first. In effect, it's a one-element queue.&lt;/li&gt;
&lt;li&gt;The “working” lock. The working lock is either Shared or Exclusive.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The queue lock defines a mutual exclusion zone of code (called a &lt;em&gt;mutex&lt;/em&gt;) where at most one process is able to run.
The idea is readers enter the mutex, get their shared working lock, and leave the mutex.
Once they have their shared lock, they can loiter, doing whatever it is they need to do.&lt;/p&gt;
&lt;p&gt;When a writer enters the mutex, they have to wait for their exclusive working lock before they leave the mutex.
This stops the readers.&lt;/p&gt;
&lt;p&gt;Here are some play-by-play views for a number of scenarios.&lt;/p&gt;
&lt;div class="section" id="reader-following-readers"&gt;
&lt;h3&gt;Reader following Readers&lt;/h3&gt;
&lt;p&gt;Readers can get shared access freely.&lt;/p&gt;
&lt;table border="1" class="docutils"&gt;
&lt;colgroup&gt;
&lt;col width="25%" /&gt;
&lt;col width="25%" /&gt;
&lt;col width="25%" /&gt;
&lt;col width="25%" /&gt;
&lt;/colgroup&gt;
&lt;thead valign="bottom"&gt;
&lt;tr&gt;&lt;th class="head"&gt;time&lt;/th&gt;
&lt;th class="head"&gt;action&lt;/th&gt;
&lt;th class="head"&gt;queue lock&lt;/th&gt;
&lt;th class="head"&gt;working lock&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody valign="top"&gt;
&lt;tr&gt;&lt;td&gt;T0&lt;/td&gt;
&lt;td&gt;reader 1 acquire queue&lt;/td&gt;
&lt;td&gt;1 ex&lt;/td&gt;
&lt;td&gt;&amp;nbsp;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;T1&lt;/td&gt;
&lt;td&gt;reader 2 waiting&lt;/td&gt;
&lt;td&gt;1 ex&lt;/td&gt;
&lt;td&gt;&amp;nbsp;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;T2&lt;/td&gt;
&lt;td&gt;reader 1 acquire working&lt;/td&gt;
&lt;td&gt;1 ex&lt;/td&gt;
&lt;td&gt;1 sh&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;T3&lt;/td&gt;
&lt;td&gt;reader 1 release queue&lt;/td&gt;
&lt;td&gt;0 ex&lt;/td&gt;
&lt;td&gt;1 sh&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;T4&lt;/td&gt;
&lt;td&gt;reader 2 acquire queue&lt;/td&gt;
&lt;td&gt;1 ex&lt;/td&gt;
&lt;td&gt;1 sh&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;T5&lt;/td&gt;
&lt;td&gt;reader 2 acquire working&lt;/td&gt;
&lt;td&gt;1 ex&lt;/td&gt;
&lt;td&gt;2 sh&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;T6&lt;/td&gt;
&lt;td&gt;reader 2 release queue&lt;/td&gt;
&lt;td&gt;0 ex&lt;/td&gt;
&lt;td&gt;2 sh&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;T7&lt;/td&gt;
&lt;td&gt;reader 1 release working&lt;/td&gt;
&lt;td&gt;0 ex&lt;/td&gt;
&lt;td&gt;1 sh&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;T8&lt;/td&gt;
&lt;td&gt;reader 2 release working&lt;/td&gt;
&lt;td&gt;0 ex&lt;/td&gt;
&lt;td&gt;&amp;nbsp;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;And so it goes, readers acquiring and releasing working locks.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="reader-following-writer"&gt;
&lt;h3&gt;Reader following Writer&lt;/h3&gt;
&lt;p&gt;If there's a writer, the reader is forced to wait until
the writer releases their exclusive lock.&lt;/p&gt;
&lt;table border="1" class="docutils"&gt;
&lt;colgroup&gt;
&lt;col width="25%" /&gt;
&lt;col width="25%" /&gt;
&lt;col width="25%" /&gt;
&lt;col width="25%" /&gt;
&lt;/colgroup&gt;
&lt;thead valign="bottom"&gt;
&lt;tr&gt;&lt;th class="head"&gt;time&lt;/th&gt;
&lt;th class="head"&gt;action&lt;/th&gt;
&lt;th class="head"&gt;queue lock&lt;/th&gt;
&lt;th class="head"&gt;working lock&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody valign="top"&gt;
&lt;tr&gt;&lt;td&gt;T0&lt;/td&gt;
&lt;td&gt;writer 1 acquire queue&lt;/td&gt;
&lt;td&gt;1 ex&lt;/td&gt;
&lt;td&gt;&amp;nbsp;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;T1&lt;/td&gt;
&lt;td&gt;reader 2 waiting&lt;/td&gt;
&lt;td&gt;1 ex&lt;/td&gt;
&lt;td&gt;&amp;nbsp;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;T2&lt;/td&gt;
&lt;td&gt;writer 1 acquire working&lt;/td&gt;
&lt;td&gt;1 ex&lt;/td&gt;
&lt;td&gt;1 ex&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;T3&lt;/td&gt;
&lt;td&gt;writer 1 release queue&lt;/td&gt;
&lt;td&gt;0 ex&lt;/td&gt;
&lt;td&gt;1 ex&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;T4&lt;/td&gt;
&lt;td&gt;reader 2 acquire queue&lt;/td&gt;
&lt;td&gt;1 ex&lt;/td&gt;
&lt;td&gt;1 ex&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;T5&lt;/td&gt;
&lt;td&gt;reader 2 waiting&lt;/td&gt;
&lt;td&gt;1 ex&lt;/td&gt;
&lt;td&gt;1 ex&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;T7&lt;/td&gt;
&lt;td&gt;writer 1 release working&lt;/td&gt;
&lt;td&gt;1 ex&lt;/td&gt;
&lt;td&gt;&amp;nbsp;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;T8&lt;/td&gt;
&lt;td&gt;reader 2 acquire working&lt;/td&gt;
&lt;td&gt;1 ex&lt;/td&gt;
&lt;td&gt;1 sh&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;T9&lt;/td&gt;
&lt;td&gt;reader 2 release queue&lt;/td&gt;
&lt;td&gt;0 ex&lt;/td&gt;
&lt;td&gt;1 sh&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;T10&lt;/td&gt;
&lt;td&gt;reader 2 release working&lt;/td&gt;
&lt;td&gt;0 ex&lt;/td&gt;
&lt;td&gt;&amp;nbsp;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Yes. Traffic will back up waiting for a writer.
If this is a problem, then finer-grained locking is required.
This can lead to the possibility of deadlocks; proceed with caution and consider sharding the data to avoid
contention for locks,&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="writer-following-reader"&gt;
&lt;h3&gt;Writer following Reader&lt;/h3&gt;
&lt;p&gt;If there's a reader, the writer is forced to wait before they
can get their exclusive lock.&lt;/p&gt;
&lt;table border="1" class="docutils"&gt;
&lt;colgroup&gt;
&lt;col width="25%" /&gt;
&lt;col width="25%" /&gt;
&lt;col width="25%" /&gt;
&lt;col width="25%" /&gt;
&lt;/colgroup&gt;
&lt;thead valign="bottom"&gt;
&lt;tr&gt;&lt;th class="head"&gt;time&lt;/th&gt;
&lt;th class="head"&gt;action&lt;/th&gt;
&lt;th class="head"&gt;queue lock&lt;/th&gt;
&lt;th class="head"&gt;working lock&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody valign="top"&gt;
&lt;tr&gt;&lt;td&gt;T0&lt;/td&gt;
&lt;td&gt;reader 1 acquire queue&lt;/td&gt;
&lt;td&gt;1 ex&lt;/td&gt;
&lt;td&gt;&amp;nbsp;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;T1&lt;/td&gt;
&lt;td&gt;write 2 waiting&lt;/td&gt;
&lt;td&gt;1 ex&lt;/td&gt;
&lt;td&gt;&amp;nbsp;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;T2&lt;/td&gt;
&lt;td&gt;reader 1 acquire working&lt;/td&gt;
&lt;td&gt;1 ex&lt;/td&gt;
&lt;td&gt;1 sh&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;T3&lt;/td&gt;
&lt;td&gt;reader 1 release queue&lt;/td&gt;
&lt;td&gt;0 ex&lt;/td&gt;
&lt;td&gt;1 sh&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;T4&lt;/td&gt;
&lt;td&gt;writer 2 acquire queue&lt;/td&gt;
&lt;td&gt;1 ex&lt;/td&gt;
&lt;td&gt;1 sh&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;T5&lt;/td&gt;
&lt;td&gt;writer 2 waiting&lt;/td&gt;
&lt;td&gt;1 ex&lt;/td&gt;
&lt;td&gt;1 sh&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;T7&lt;/td&gt;
&lt;td&gt;reader 1 release working&lt;/td&gt;
&lt;td&gt;1 ex&lt;/td&gt;
&lt;td&gt;&amp;nbsp;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;T8&lt;/td&gt;
&lt;td&gt;writer 2 acquire working&lt;/td&gt;
&lt;td&gt;1 ex&lt;/td&gt;
&lt;td&gt;1 ex&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;T9&lt;/td&gt;
&lt;td&gt;writer 2 release queue&lt;/td&gt;
&lt;td&gt;0 ex&lt;/td&gt;
&lt;td&gt;1 ex&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;T10&lt;/td&gt;
&lt;td&gt;writer 2 release working&lt;/td&gt;
&lt;td&gt;0 ex&lt;/td&gt;
&lt;td&gt;&amp;nbsp;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Clearly, if there are a &lt;strong&gt;lot&lt;/strong&gt; of readers, the writer waits a long time
for them &lt;strong&gt;all&lt;/strong&gt; to finish.
Some more clever lock definitions permit an upper bound on the number of
locks that can be acquired.&lt;/p&gt;
&lt;p&gt;Our goal, however, is simplicity.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="section" id="python-implementation"&gt;
&lt;h2&gt;Python Implementation&lt;/h2&gt;
&lt;p&gt;This is intended to be used with &lt;strong&gt;Flask&lt;/strong&gt;.&lt;/p&gt;
&lt;pre class="literal-block"&gt;
from pathlib import Path
from flask import Flask

class DBLock:

    def __init__(self, app: Flask | None = None) -&amp;gt; None:
        if app:
            self.init_app(app)

    def init_app(self, app: Flask) -&amp;gt; None:
        self.lock_path = Path(cast(str, app.config.get(&amp;quot;DB_LOCK_FILENAME&amp;quot;, &amp;quot;dblock&amp;quot;)))
        self.queue_path = self.lock_path.with_suffix(&amp;quot;.dbqueue&amp;quot;)
        self.thread_local = threading.local()
&lt;/pre&gt;
&lt;p&gt;This could be refactored to work outside a Flask-specific context.&lt;/p&gt;
&lt;p&gt;The &lt;tt class="docutils literal"&gt;thread_local&lt;/tt&gt; storage is required to track each thread's unique open file handles.&lt;/p&gt;
&lt;p&gt;The essence is acquiring a lock and releaing a lock.
The &amp;quot;lock mode&amp;quot; is one the &lt;tt class="docutils literal"&gt;fcntl.LOCK_EX&lt;/tt&gt; or &lt;tt class="docutils literal"&gt;fcntl.LOCK_SH&lt;/tt&gt; values.&lt;/p&gt;
&lt;pre class="literal-block"&gt;
def acquire(self, lock_mode: int) -&amp;gt; Self:
    if &amp;quot;lock_file&amp;quot; not in self.thread_local.__dict__:
        # Enter Queue Mutex to acquire a database lock.
        queue_file = self.queue_path.open(&amp;quot;w+&amp;quot;)
        fcntl.flock(queue_file, fcntl.LOCK_EX)
        self.thread_local.lock_file = self.lock_path.open(&amp;quot;w+&amp;quot;)
        fcntl.flock(self.thread_local.lock_file, lock_mode)
        # Exit Queue Mutex. Permits another thread (or process) to acquire a lock.
        fcntl.flock(queue_file, fcntl.LOCK_UN)
    return self

def release(self) -&amp;gt; None:
    if &amp;quot;lock_file&amp;quot; in self.thread_local.__dict__:
        fcntl.flock(self.thread_local.lock_file, fcntl.LOCK_UN)
        self.thread_local.lock_file.close()
        delattr(self.thread_local, &amp;quot;lock_file&amp;quot;)
&lt;/pre&gt;
&lt;p&gt;The acquire and release are the algorithm described above.
An exclusive lock defines a system-wide Mutex for &lt;strong&gt;all&lt;/strong&gt; threads and processes.
The working lock is either shared or exclusive.&lt;/p&gt;
&lt;p&gt;The cleanup on release undoes the lock, closes the file to release any OS resources,
and also purges the &lt;tt class="docutils literal"&gt;thread_local&lt;/tt&gt; to make sure there's no confusion about the state.&lt;/p&gt;
&lt;p&gt;Some useful overheads:&lt;/p&gt;
&lt;pre class="literal-block"&gt;
close = release

def is_locked(self) -&amp;gt; bool:
    # print(f&amp;quot;is_locked: {self.thread_local.__dict__=}&amp;quot;)
    return &amp;quot;lock_file&amp;quot; in self.thread_local.__dict__
&lt;/pre&gt;
&lt;p&gt;If we provide a &lt;tt class="docutils literal"&gt;close()&lt;/tt&gt; method, then the &lt;tt class="docutils literal"&gt;contextlib.closing()&lt;/tt&gt; context manager
can be used.&lt;/p&gt;
&lt;p&gt;The &lt;tt class="docutils literal"&gt;is_locked()&lt;/tt&gt; method can be helpful to know the state of the lock.
It's far better to use the &lt;tt class="docutils literal"&gt;with&lt;/tt&gt; statement to define a context that eliminates any doubt.&lt;/p&gt;
&lt;p&gt;While this can be used with &lt;tt class="docutils literal"&gt;contextlib&lt;/tt&gt; functions, it seems helpful to provide explicit context management.&lt;/p&gt;
&lt;pre class="literal-block"&gt;
def __enter__(self) -&amp;gt; Self:
    return self

def __exit__(
    self,
    exc_type: type[Exception],
    exc_val: Exception,
    exc_tb: TracebackException,
) -&amp;gt; Literal[False]:
    self.release()
    return False
&lt;/pre&gt;
&lt;p&gt;And. Two convenience methods to avoid having to muck around with &lt;tt class="docutils literal"&gt;fcntl.LOCK_SH&lt;/tt&gt; and &lt;tt class="docutils literal"&gt;fcntl.LOCK_EX&lt;/tt&gt;.&lt;/p&gt;
&lt;pre class="literal-block"&gt;
def shared(self) -&amp;gt; Self:
    &amp;quot;&amp;quot;&amp;quot;
    Context manager, equivalent to::

        with dblock.acquire(fcntl.LOCK_SH):
            pass
    &amp;quot;&amp;quot;&amp;quot;
    self.acquire(fcntl.LOCK_SH)
    return self

def exclusive(self) -&amp;gt; Self:
    &amp;quot;&amp;quot;&amp;quot;
    Context manager, equivalent to::

        with dblock.acquire(fcntl.LOCK_EX):
            pass
    &amp;quot;&amp;quot;&amp;quot;
    self.acquire(fcntl.LOCK_EX)
    return self
&lt;/pre&gt;
&lt;p&gt;The goal is to have relatively lightweight code like the following.&lt;/p&gt;
&lt;p&gt;Some Flask app setup:&lt;/p&gt;
&lt;pre class="literal-block"&gt;
dblock = DBLock()
dblock.init_app(app)
&lt;/pre&gt;
&lt;p&gt;Within a GET view function:&lt;/p&gt;
&lt;pre class="literal-block"&gt;
with dblock.shared():
    with dbm.open(some_file) as db:
        item = SomeClass.model_validate_json(db[your_key_here])
&lt;/pre&gt;
&lt;p&gt;Within a POST/PUT/PATCH/DELETE view function:&lt;/p&gt;
&lt;pre class="literal-block"&gt;
with dblock.exclusive():
    with dbm.open(some_file, flag='c') as db:
        db[item.id] = item.model_dump_json().encode('utf-8')
&lt;/pre&gt;
&lt;p&gt;By acquiring an exclusive access lock, all changes will be saved reliably and predictably: an atomic state change.&lt;/p&gt;
&lt;p&gt;And yes, the explicit &lt;tt class="docutils literal"&gt;model_validate_json()&lt;/tt&gt; and &lt;tt class="docutils literal"&gt;model_dump_json()&lt;/tt&gt; is wordy.
I use a &lt;tt class="docutils literal"&gt;DB&lt;/tt&gt; class to conceals these details.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="conclusion"&gt;
&lt;h2&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;We can use &lt;tt class="docutils literal"&gt;dbm&lt;/tt&gt; as a dictionary-like repository of objects serialized as JSON.&lt;/p&gt;
&lt;p&gt;We have the benefits of a fancy relational database without the overheads.&lt;/p&gt;
&lt;/div&gt;
</content><category term="Python"></category><category term="database"></category><category term="dbm"></category><category term="shelve"></category><category term="multiprocessing"></category><category term="context manager"></category></entry><entry><title>Functional Python and Lambdas</title><link href="https://slott56.github.io/2024-02-09-functional_python_and_lambdas.html" rel="alternate"></link><published>2024-02-09T08:01:00-05:00</published><updated>2024-02-09T08:01:00-05:00</updated><author><name>S.Lott</name></author><id>tag:slott56.github.io,2024-02-09:/2024-02-09-functional_python_and_lambdas.html</id><summary type="html">&lt;p&gt;I saw a confusing post on &lt;a class="reference external" href="https://fosstodon.org"&gt;https://fosstodon.org&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;I won't link to it, but I will quote it because it repeats some common misconceptions.&lt;/p&gt;
&lt;pre class="literal-block"&gt;
I have some iterator chain (in reality longer and more complex than this example).
And now in one or more steps, I need to add …&lt;/pre&gt;</summary><content type="html">&lt;p&gt;I saw a confusing post on &lt;a class="reference external" href="https://fosstodon.org"&gt;https://fosstodon.org&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;I won't link to it, but I will quote it because it repeats some common misconceptions.&lt;/p&gt;
&lt;pre class="literal-block"&gt;
I have some iterator chain (in reality longer and more complex than this example).
And now in one or more steps, I need to add an extra operator.
Going from `foo.map(thing).filter(condition).reduce(collector)` to

foo.map({
  thing
  if condition {
      other_thing
  }
).reduce({
  setup
  collector
  logger
})

The design of Python's iterators make this very hard.

Because a lambda cannot easily contain multiple lines, conditions or statements.
&lt;/pre&gt;
&lt;p&gt;There are two unrelated misconceptions here. One's an minor error, the other is a nuanced design choice.
We'll look at the minor error first, since it's a common misconception about Python lambdas.&lt;/p&gt;
&lt;p&gt;And. I cover these in &lt;a class="reference external" href="https://www.amazon.com/Functional-Python-Programming-functional-expressive/dp/1803232579"&gt;Functional Python Programming&lt;/a&gt;&lt;/p&gt;
&lt;div class="section" id="lambda-and-lines-of-code"&gt;
&lt;h2&gt;Lambda and Lines of Code&lt;/h2&gt;
&lt;p&gt;A Python lambda has a single &lt;strong&gt;expression&lt;/strong&gt;. This precludes any statements.&lt;/p&gt;
&lt;p&gt;An expression must be complete on a single &lt;strong&gt;logical line&lt;/strong&gt; of code.
Because of the way &lt;tt class="docutils literal"&gt;(&lt;/tt&gt; and &lt;tt class="docutils literal"&gt;)&lt;/tt&gt; must balance, it can span multiple &lt;strong&gt;physical lines&lt;/strong&gt; of code.&lt;/p&gt;
&lt;pre class="literal-block"&gt;
a = list(
    map(
        lambda x: (
            (
                3 * x + 1
            )
            if
            (
                x % 2 == 1
            )
            else
            (
                x // 2
            )
        ),
        range(10)
    )
)
&lt;/pre&gt;
&lt;p&gt;A lambda can be quite long. Use &lt;tt class="docutils literal"&gt;(&lt;/tt&gt; and &lt;tt class="docutils literal"&gt;)&lt;/tt&gt; to enclose it.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="design-choices"&gt;
&lt;h2&gt;Design Choices&lt;/h2&gt;
&lt;p&gt;The Ruby &lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;foo.map(m).filter(f).reduce(r)&lt;/span&gt;&lt;/tt&gt; has two renderings in Python.&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;Nested functions. This is bad, but we'll look at it.&lt;/li&gt;
&lt;li&gt;Chains of generator expressions. I call them stacks. This is good.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Let's take a concrete example, one that we can unit test.
We'll look at it first as nested functions to see how bad it can be.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="nested-functions"&gt;
&lt;h2&gt;Nested Functions&lt;/h2&gt;
&lt;p&gt;Here's are two functions to generate some complicated data.&lt;/p&gt;
&lt;pre class="literal-block"&gt;
hotpo = (
    lambda x:
        (x * 3 + 1) if (x % 2 == 1)
        else (x // 2)
)
hotpo_run = (
    lambda x, r:
        r + [x] if x == 1
        else hotpo_run(hotpo(x), r + [x])
)
&lt;/pre&gt;
&lt;p&gt;These are two functions that we've defined as lambdas for no particularly good reason.&lt;/p&gt;
&lt;p&gt;Let's use a mapping to compute a range of values.
This is a bit like &lt;tt class="docutils literal"&gt;range(1, &lt;span class="pre"&gt;11).map(lambda...)&lt;/span&gt;&lt;/tt&gt; in Ruby.&lt;/p&gt;
&lt;pre class="literal-block"&gt;
list(
    map(
        lambda n: hotpo_run(n, []),
        range(1, 11)
    )
)
&lt;/pre&gt;
&lt;p&gt;We don't want the lists. We want the lengths of the lists.&lt;/p&gt;
&lt;pre class="literal-block"&gt;
list(
    map(len,
        map(
            lambda n: hotpo_run(n, []),
            range(1, 11)
        )
    )
)
&lt;/pre&gt;
&lt;p&gt;Not that it matters much, but let's add a filter.&lt;/p&gt;
&lt;pre class="literal-block"&gt;
list(
    filter(
        lambda x: x &amp;gt; 0,
        map(len,
            map(
                lambda n: hotpo_run(n, []),
                range(1, 11)
            )
        )
    )
)
&lt;/pre&gt;
&lt;div class="admonition note"&gt;
&lt;p class="first admonition-title"&gt;Note&lt;/p&gt;
&lt;p&gt;BTW, the answer is &lt;tt class="docutils literal"&gt;[1, 2, 8, 3, 6, 9, 17, 4, 20, 7]&lt;/tt&gt;.&lt;/p&gt;
&lt;p class="last"&gt;Without a cache of some kind. It takes quite a while to compute more than a few results.&lt;/p&gt;
&lt;/div&gt;
&lt;p&gt;Let's reduce this to find the largest value.&lt;/p&gt;
&lt;p&gt;Clearly &lt;tt class="docutils literal"&gt;max()&lt;/tt&gt; will work, but, for the sake of matching the Ruby,
let's build &lt;tt class="docutils literal"&gt;max()&lt;/tt&gt; as a &lt;tt class="docutils literal"&gt;reduce()&lt;/tt&gt;.&lt;/p&gt;
&lt;pre class="literal-block"&gt;
from functools import reduce
reduce(
    lambda a, b: a if a &amp;gt; b else b,
    filter(
        lambda x: x &amp;gt; 0,
        map(len,
            map(
                lambda n: hotpo_run(n, []),
                range(1, 11)
            )
        )
    )
)
&lt;/pre&gt;
&lt;p&gt;The answer is 20. What's important is the function-application version of the Ruby.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="n"&gt;foo&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="n"&gt;m&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="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&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;r&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&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;r&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;m&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;source&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Folks don't like reading the Python right-to-left.
When you spread it into multiple lines it has to be read from bottom-to-top.
Or maybe from inside to outside.
This nested function version is not widely used.&lt;/p&gt;
&lt;p&gt;We can do better with a stack of generator expressions.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="stacked-generators"&gt;
&lt;h2&gt;Stacked Generators&lt;/h2&gt;
&lt;p&gt;We'll start with the original two lambdas, &lt;tt class="docutils literal"&gt;hotpo()&lt;/tt&gt; and &lt;tt class="docutils literal"&gt;hotpo_run()&lt;/tt&gt;.&lt;/p&gt;
&lt;pre class="literal-block"&gt;
runs = map(
        lambda n: hotpo_run(n, []),
        range(1, 11)
    )
lengths = map(len, runs)
positive = filter(lambda x: x &amp;gt; 0, lengths)
maximum = reduce(
    lambda a, b: a if a &amp;gt; b else b,
    positive
)
&lt;/pre&gt;
&lt;p&gt;This reads from start to finish in an understandable fashion.&lt;/p&gt;
&lt;p&gt;We can easily add steps in the middle of this.&lt;/p&gt;
&lt;p&gt;The downside of adding steps is the intermediate results have names.&lt;/p&gt;
&lt;p&gt;When we want to insert a step, we have to &lt;strong&gt;also&lt;/strong&gt; modify the step after to
use the new intermediate results.&lt;/p&gt;
&lt;p&gt;The upside is the intermediate results have names. These describe
what's going on. I really like this approach.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="hey-wait"&gt;
&lt;h2&gt;Hey, Wait&lt;/h2&gt;
&lt;p&gt;Yes, this is related to the Collatz Conjecture.
See &lt;a class="reference external" href="https://projecteuler.net/problem=14"&gt;https://projecteuler.net/problem=14&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;The definition of the &lt;tt class="docutils literal"&gt;hotpo_run()&lt;/tt&gt; function isn't conducive to creating a cache.
We can rewrite it, easily, into a function that builds a list from a single argument value.
This works better with a cache.&lt;/p&gt;
&lt;pre class="literal-block"&gt;
from functools import cache
hotpo_run = cache(
    lambda x: [1] if x == 1 else [x] + hotpo_run(hotpo(x))
)
&lt;/pre&gt;
&lt;p&gt;This necessitates a change to the pipeline to deal with the slightly different parameter
signature for the &lt;tt class="docutils literal"&gt;hotpo_run()&lt;/tt&gt; function.&lt;/p&gt;
&lt;pre class="literal-block"&gt;
runs = map(
        hotpo_run,
        range(1, 11)
    )
&lt;/pre&gt;
&lt;p&gt;The rest is the same. Second verse same as the first verse.&lt;/p&gt;
&lt;pre class="literal-block"&gt;
lengths = map(len, runs)
positive = filter(lambda x: x &amp;gt; 0, lengths)
maximum = reduce(
    lambda a, b: a if a &amp;gt; b else b,
    positive
)
&lt;/pre&gt;
&lt;p&gt;This computes almost instantly.&lt;/p&gt;
&lt;p&gt;What's important is the sequence of &lt;tt class="docutils literal"&gt;map()&lt;/tt&gt;-&lt;tt class="docutils literal"&gt;filter()&lt;/tt&gt;-&lt;tt class="docutils literal"&gt;reduce()&lt;/tt&gt; functional operations
is better expressed as a sequence of generator expression statements.
I like to call it the &amp;quot;Stack of Generators&amp;quot; design pattern.
It has much of the expressive power of Ruby, with all of the flexibility we desire.&lt;/p&gt;
&lt;div class="admonition note"&gt;
&lt;p class="first admonition-title"&gt;Note&lt;/p&gt;
&lt;p&gt;And yes, that's still not a solution to Euler 14.&lt;/p&gt;
&lt;p&gt;Euler 14 wants this: &amp;quot;Which starting number, under one million, produces the longest chain?&amp;quot;.&lt;/p&gt;
&lt;p&gt;We need to change the result of the &lt;tt class="docutils literal"&gt;runs&lt;/tt&gt; generator to be a (run, starting value) two-tuple.&lt;/p&gt;
&lt;pre class="literal-block"&gt;
runs = ((hotpo_run(n), n) for n in range(1, 11))
&lt;/pre&gt;
&lt;p&gt;Or&lt;/p&gt;
&lt;pre class="literal-block"&gt;
runs = map(lambda n: (hotpo_run(n), n), range(1, 11))
&lt;/pre&gt;
&lt;p&gt;The &lt;tt class="docutils literal"&gt;lengths&lt;/tt&gt; generator needs to be modified to be &lt;tt class="docutils literal"&gt;lambda r_s: &lt;span class="pre"&gt;(len(r_s[0]),&lt;/span&gt; r_s[1])&lt;/tt&gt;.&lt;/p&gt;
&lt;p&gt;Drop the &lt;tt class="docutils literal"&gt;filter()&lt;/tt&gt;. It's only here to match the original conversation.&lt;/p&gt;
&lt;p class="last"&gt;Replace the &lt;tt class="docutils literal"&gt;reduce()&lt;/tt&gt; with a simpler &lt;tt class="docutils literal"&gt;max()&lt;/tt&gt;.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
</content><category term="Python"></category><category term="functional programming"></category><category term="functional python programming"></category></entry><entry><title>More Reasons to Stop Bash-ing</title><link href="https://slott56.github.io/2023_12_19-more_reasons_to_stop_bashing.html" rel="alternate"></link><published>2023-12-19T08:01:00-05:00</published><updated>2023-12-19T08:01:00-05:00</updated><author><name>S.Lott</name></author><id>tag:slott56.github.io,2023-12-19:/2023_12_19-more_reasons_to_stop_bashing.html</id><summary type="html">&lt;p&gt;There are many good reasons to use shell scripts.
Mostly, a script can be useful when it's an alias that launches an application.
Beyond that, I have doubts.&lt;/p&gt;
&lt;div class="section" id="bluf"&gt;
&lt;h2&gt;BLUF&lt;/h2&gt;
&lt;p&gt;Incumbency is a popular argument for bash.&lt;/p&gt;
&lt;p&gt;It's not a good argument, however.&lt;/p&gt;
&lt;p&gt;Use &lt;a class="reference external" href="https://pypi.org/project/invoke/"&gt;invoke&lt;/a&gt; and you'll be much happier.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="background"&gt;
&lt;h2&gt;Background …&lt;/h2&gt;&lt;/div&gt;</summary><content type="html">&lt;p&gt;There are many good reasons to use shell scripts.
Mostly, a script can be useful when it's an alias that launches an application.
Beyond that, I have doubts.&lt;/p&gt;
&lt;div class="section" id="bluf"&gt;
&lt;h2&gt;BLUF&lt;/h2&gt;
&lt;p&gt;Incumbency is a popular argument for bash.&lt;/p&gt;
&lt;p&gt;It's not a good argument, however.&lt;/p&gt;
&lt;p&gt;Use &lt;a class="reference external" href="https://pypi.org/project/invoke/"&gt;invoke&lt;/a&gt; and you'll be much happier.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="background"&gt;
&lt;h2&gt;Background&lt;/h2&gt;
&lt;p&gt;See &lt;a class="reference external" href="https://dnastacio.medium.com/bash-over-python-39e0eba502f9"&gt;When You Should Use Bash Over Python&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;I'll start with the three &amp;quot;expressiveness&amp;quot; points.&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;strong&gt;Syntax&lt;/strong&gt;: Python code is longer. While true, this isn't a reason to use bash. I have to reject this for two reasons.&lt;ul&gt;
&lt;li&gt;No one wins at code golf. Shorter code isn't better by any metric other than size. Bash syntax hides important details.&lt;/li&gt;
&lt;li&gt;The argument starts from the notion that there's a &amp;quot;better&amp;quot; way to express complicated structures, and the bash reflects better.
Python, by being more explicit, is less good.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Runtime&lt;/strong&gt;: Shell script interpreters are ubiquitous. True. Not a compelling argument, when we consider that bash scripts are untestable.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Semantics&lt;/strong&gt;: There is a cognitive cost in converting bash to Python. Correct. Easy to avoid by avoiding the confusing and opaque bash abstractions.&lt;ul&gt;
&lt;li&gt;The argument (again) starts from the notion that the bash abstraction is a standard against which other languages -- by virtue of being different -- aren't as good.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The argument &lt;strong&gt;for&lt;/strong&gt; bash is incumbency. Bash is installed, and because it's installed, it's better.&lt;/p&gt;
&lt;p&gt;&amp;quot;Bash’s longevity is rooted in core strengths that still resonate in the technology industry&amp;quot;.
I suggest the longevity is due entirely to it's incumbency.
It's not the &lt;strong&gt;best&lt;/strong&gt; choice for anything.
It's a handy default choice because it's already installed.&lt;/p&gt;
&lt;p&gt;And.  There's no easy way to unit test.&lt;/p&gt;
&lt;p&gt;Let's move on to the other six reasons.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="some-reasons-for-using-bash"&gt;
&lt;h2&gt;Some reasons for using bash&lt;/h2&gt;
&lt;p&gt;Here are the the detailed reasons for rejecting Python. Most of this isn't persuasive.
It's mostly about the incumbency of bash.&lt;/p&gt;
&lt;ol class="arabic"&gt;
&lt;li&gt;&lt;p class="first"&gt;Mastery Matters. Parts of this argument are true. Bash scripts seem to be uniformly bad because bad is a permitted style.
They could be better, making use of clever things like functions and their obscure semantics.&lt;/p&gt;
&lt;p&gt;This doesn't make bash better. It only says that a lot of people write bad scripts.
A lot of bash-bashing stems from seeing so many bad scripts.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p class="first"&gt;Bash is Everywhere. True. Incumbency may be helpful under certain situations.
It's like learning how to compute logarithms so you can then add them to avoid multiplication.
Yes. It does work. However. Calculators exist on this timeline; it's no longer 1617.&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&amp;quot;Sidestepping the discussions about the Python version to pick&amp;quot;? What discussion? Is this a &amp;quot;Python 2 v. Python 3&amp;quot; question? That's been answered.&lt;/li&gt;
&lt;li&gt;&amp;quot;the best way to install Python on a given environment&amp;quot;? Most linux distros have Python ready-to-go. That's best.&lt;/li&gt;
&lt;li&gt;&amp;quot;gymnastics to keep dependencies and environments in check&amp;quot;? This isn't hard, actually. Almost anything bash-related is in the standard library plus a few add-ons line &lt;a class="reference external" href="https://pypi.org/project/psutil/"&gt;psutil&lt;/a&gt;.
For a very complicated application with a tall stack of poorly-chosen dependencies, there's work involved.
That application with a complicated set of installs isn't doing bash-like things, though.&lt;/li&gt;
&lt;li&gt;&amp;quot;fragmentation of Python runtime versions&amp;quot;? What fragmentation? Python is popular, and evolves quickly. Is evolution to new versions some kind of problem?&lt;/li&gt;
&lt;li&gt;&amp;quot;mutually exclusive dependency matrix&amp;quot;? Callback to gymnastics. A tall stack of poorly-chosen dependencies is an edge case. It's not the sweet spot for admin tasks often written as bad, untestable bash scripts.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p class="first"&gt;Secured Production Environments. This is hard. None of these difficulties are Python-related, however. It applies to every single application in the environment.
Java requires installs for develolpers, too. So go Go and Rust. Air-gapped systems are hard to build.&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&amp;quot;whatever Python runtime environment you lock into a production environment&amp;quot;? Um. This is true for &lt;em&gt;all&lt;/em&gt; applications.
It has nothing to do with Python. This is configuration management. It's hard.&lt;/li&gt;
&lt;li&gt;&amp;quot;Running package managers safely inside a production environment is possible, but everything’s got a price&amp;quot;. And the price is actually quite low.
Further, this means building secured systems for software development. That's quite hard in all languages.
The only language that wouldn't require extra downloads of new and useful packages would be Pascal, I think.&lt;/li&gt;
&lt;li&gt;&amp;quot;You could do [here documents] with a Python script, too, as long as it does not import any package not already installed in the system.&amp;quot;
Right. Most bash-related Python features are part of the standard library. This isn't daunting or even particularly difficult or complicated.
And. With Python you can unit test.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p class="first"&gt;Container Runtimes. See #3. This is bash incumbency and configuration management from point 3, repeated. It's still challenging.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p class="first"&gt;The Universal Language of Platforms. The bash CLI is ubiquitous, it's ideal for bash. But it's not actually &lt;strong&gt;ideal&lt;/strong&gt; in general.
A Python library that offers access an application's API may be much easier to work with and involve far fewer weird
leaps to make the CLI amendable to the relatively weak set of abstractions bash has available.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p class="first"&gt;&amp;quot;It would take a couple of days in syntactical and semantical translations to get a result with more lines of code that were less readable than its Bash counterpart&amp;quot;.
Again, the argument presumes the bash language is the gold standard. Starting with bash and enduring translating into Python involves a cost.
It also had benefits, like the ability to test.
Why not start with Python?
&amp;quot;Less readable&amp;quot; is offered without further evidence. Again, this repeats the bash incumbency argument where smaller and older is inherently better.&lt;/p&gt;
&lt;p&gt;Further, the time spent writing Python is often time &lt;strong&gt;well&lt;/strong&gt; spent getting the abstractions right,
and understading use cases.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p class="first"&gt;&amp;quot;All original samples in the docs were written using the command-line interface.&amp;quot;  Incumbency. And maybe lazy documentation writers in the vendor organization.
&amp;quot;All Internet forums reference the command-line interface&amp;quot;. Sigh. The &amp;quot;All&amp;quot; is disputable, but the point remains that using Python takes some effort.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p class="first"&gt;The End of the Line Is Not Scripted. (Not sure what this means.)
There are two obstacles here, both of which seem specious at best.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p class="first"&gt;Mega CLI's. Just because a bash CLI is available does not make it &amp;quot;best.&amp;quot;&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&amp;quot;every bit of function be wrapped with command-line interfaces.&amp;quot;  While true, it ignores the fact
that some packages are actually written in Python, and the bash interface is -- at best -- a hack
for those folks who won't learn Python.&lt;/li&gt;
&lt;li&gt;Bash is everywhere. Incumbency does not make it better. It only makes it incumbent.&lt;/li&gt;
&lt;li&gt;Writing shell scripts is more accessible than writing a new application. A good straw-man.
It throws Python scripting away as if we can't write a short, pithy, testable, reusable Python script.&lt;/li&gt;
&lt;li&gt;&amp;quot;open-source juggernauts...&amp;quot; like &lt;tt class="docutils literal"&gt;awk&lt;/tt&gt;, &lt;tt class="docutils literal"&gt;curl&lt;/tt&gt;, &lt;tt class="docutils literal"&gt;openssl&lt;/tt&gt;, &lt;tt class="docutils literal"&gt;jq&lt;/tt&gt;, and &lt;tt class="docutils literal"&gt;yq&lt;/tt&gt; involves two issues.
First, some programs like &lt;tt class="docutils literal"&gt;openssl&lt;/tt&gt; are better left as stand-alone binaries used by a Python script.
Second, programs like  &lt;tt class="docutils literal"&gt;awk&lt;/tt&gt;, &lt;tt class="docutils literal"&gt;jq&lt;/tt&gt;, and &lt;tt class="docutils literal"&gt;yq&lt;/tt&gt; are the primary symptom of how unsuitable bash is for working with anything other
than a trivial string of characters. Reliance on these add-on programs is one of the reasons why bash is so confusingly horrible.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p class="first"&gt;Operations Frameworks like Ansible, Terraform, and (not mentioned) Puppet. These require some scripting
for integration. Having done it in Python, I can safely say Python works.&lt;/p&gt;
&lt;p&gt;And I could unit test it.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Unrelated to the two obstacles is this nugget: &amp;quot;tuned for five decades of minimum resource utilization&amp;quot;.
I don't think this is true at all.&lt;/p&gt;
&lt;p&gt;The original Bourne &lt;tt class="docutils literal"&gt;sh&lt;/tt&gt; wasn't very thrifty to begin with. It was constrained by the tiny size of early
machines. And. The Linux technique of sharing the read-only code pages meant the costs could stay low.
State management was environment variables and some OS settings (like the current working directory.)
The bash program is bloatware by comparison to the Bourne shell.
The use of the OS &lt;tt class="docutils literal"&gt;|&lt;/tt&gt; operator forks subprocess after subprocess leading to crazy OS overheads
for a &amp;quot;simple&amp;quot; &lt;tt class="docutils literal"&gt;app | awk | grep | sed &amp;gt; file&lt;/tt&gt; operation.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;
&lt;div class="section" id="conclusion"&gt;
&lt;h2&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;&amp;quot;In objective terms, regarding task automation for Cloud operations, it is hard to argue against Bash&amp;quot;.&lt;/p&gt;
&lt;p&gt;No. Actually. It's really easy.&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;The bash scripting language is opaque. Objectively, the syntax rules are quite obscure with complicated line-ending and quoting rules.
Objectively, it's really difficult to understand the semantics of the operators like &lt;tt class="docutils literal"&gt;;&lt;/tt&gt;, &lt;tt class="docutils literal"&gt;|&lt;/tt&gt;, and &lt;tt class="docutils literal"&gt;&amp;amp;&lt;/tt&gt;.
Why is &lt;tt class="docutils literal"&gt;;&lt;/tt&gt; optional? Why can a line end with &lt;tt class="docutils literal"&gt;&amp;amp;&lt;/tt&gt;  or &lt;tt class="docutils literal"&gt;;&lt;/tt&gt; but not end with &lt;tt class="docutils literal"&gt;|&lt;/tt&gt;?&lt;/li&gt;
&lt;li&gt;Error-handling in bash is an unholy mess. Objectively, what does &lt;tt class="docutils literal"&gt;set &lt;span class="pre"&gt;-e&lt;/span&gt;&lt;/tt&gt; do?
Objectively, why are there so many return codes?&lt;/li&gt;
&lt;li&gt;Unit testing is almost impossible. Objectively, no one should run a shell script without a test case.&lt;/li&gt;
&lt;li&gt;Bash has almost no useful data structures beyond the string.
Objectively, we can argue that there's a way to break strings on spaces to treat the string as an array.
This is essentially Python &lt;tt class="docutils literal"&gt;.split()&lt;/tt&gt; as the alternative data structure to the string.&lt;/li&gt;
&lt;li&gt;Programs like &lt;tt class="docutils literal"&gt;expr&lt;/tt&gt; and &lt;tt class="docutils literal"&gt;[&lt;/tt&gt; are used widely and very difficult to understand.
Objectively, the man pages for these programs are quite complicated.
What looks like an expression isn't really. It's input to a separate binary that produces a result used by the shell's &lt;tt class="docutils literal"&gt;if&lt;/tt&gt; construct.
Objectively, this is confusing and unpleasant.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Programs like &lt;tt class="docutils literal"&gt;awk&lt;/tt&gt;, &lt;tt class="docutils literal"&gt;jq&lt;/tt&gt;, &lt;tt class="docutils literal"&gt;yq&lt;/tt&gt; are used widely and can be difficult to understand.
They're -- technically -- separate binaries, part of the overall bash ecosystem of internal bash features and external binaries.
They do permit a kind of functional style on bash programming which is nice.
Objectively, this isn't all bad. Python, also, has functional programming features.&lt;/p&gt;
&lt;p&gt;The ubiquity of the bash programming is undeniable. It's also terrible. Bash should be used cautiously.&lt;/p&gt;
&lt;p&gt;When to use bash?&lt;/p&gt;
&lt;p&gt;Use bash you need to launch a Python script. A bash script should be little more than an alias for a program written in a language that offers unit testing.&lt;/p&gt;
&lt;/div&gt;
</content><category term="Python"></category><category term="shell"></category><category term="bash"></category><category term="unit-testing"></category></entry><entry><title>Understanding the Abstraction -- matplotlib Edition</title><link href="https://slott56.github.io/2023-12-12-understanding_the_abstraction.html" rel="alternate"></link><published>2023-12-12T08:01:00-05:00</published><updated>2023-12-12T08:01:00-05:00</updated><author><name>S.Lott</name></author><id>tag:slott56.github.io,2023-12-12:/2023-12-12-understanding_the_abstraction.html</id><summary type="html">&lt;p&gt;I wasted three days because I refused to get a grip on how &lt;a class="reference external" href="https://matplotlib.org"&gt;matplotlib&lt;/a&gt; &lt;strong&gt;really&lt;/strong&gt; works.&lt;/p&gt;
&lt;p&gt;Most of the time, folks like me are happy and successful using the &lt;a class="reference external" href="https://matplotlib.org/stable/api/pyplot_summary.html"&gt;pyplot&lt;/a&gt; module.
The &lt;a class="reference external" href="https://matplotlib.org/stable/users/explain/quick_start.html"&gt;Quickstart&lt;/a&gt; provides brilliant, working
examples.&lt;/p&gt;
&lt;p&gt;As my partner's grandfather used to say, &amp;quot;We're off to the races in …&lt;/p&gt;</summary><content type="html">&lt;p&gt;I wasted three days because I refused to get a grip on how &lt;a class="reference external" href="https://matplotlib.org"&gt;matplotlib&lt;/a&gt; &lt;strong&gt;really&lt;/strong&gt; works.&lt;/p&gt;
&lt;p&gt;Most of the time, folks like me are happy and successful using the &lt;a class="reference external" href="https://matplotlib.org/stable/api/pyplot_summary.html"&gt;pyplot&lt;/a&gt; module.
The &lt;a class="reference external" href="https://matplotlib.org/stable/users/explain/quick_start.html"&gt;Quickstart&lt;/a&gt; provides brilliant, working
examples.&lt;/p&gt;
&lt;p&gt;As my partner's grandfather used to say, &amp;quot;We're off to the races in a cloud of heifer dust.&amp;quot;&lt;/p&gt;
&lt;p&gt;The examples are easily rewritten for the data at hand. They work in Jupyter Lab. Boom. Done.&lt;/p&gt;
&lt;p&gt;There's a little bit of technical detail in &lt;a class="reference external" href="https://matplotlib.org/stable/users/explain/figure/interactive.html#jupyter-notebooks-jupyterlab"&gt;https://matplotlib.org/stable/users/explain/figure/interactive.html#jupyter-notebooks-jupyterlab&lt;/a&gt;.
When I realized things weren't working. I followed each piece of advice, scruplously. They were not the cause of my problems.
The root cause was failure to understand the abstraction.&lt;/p&gt;
&lt;div class="section" id="digging-a-little-deeper"&gt;
&lt;h2&gt;Digging a Little Deeper&lt;/h2&gt;
&lt;p&gt;What is not &lt;strong&gt;painfully&lt;/strong&gt; obvious is how the &lt;strong&gt;matplotlib&lt;/strong&gt; architecture works.
(It's not written in flaming letters 100 feet high.)&lt;/p&gt;
&lt;p&gt;The &lt;tt class="docutils literal"&gt;pyplot&lt;/tt&gt; module is pure genius. It works to shield us from a tech stack that's non-trivial.&lt;/p&gt;
&lt;p&gt;Which means, when someone like me wants to do something that's not copy-and-paste from one of the hundreds of examples,
I have to &lt;strong&gt;actually&lt;/strong&gt; read the documentation. Carefully.&lt;/p&gt;
&lt;p&gt;It took me three days to understand what the documentation said.
Here's my timeline.&lt;/p&gt;
&lt;p&gt;Day 1. Fuss around with my incorrect understanding of how graphics are created.&lt;/p&gt;
&lt;p&gt;Day 2. Write the entire thing as a stand-alone command-line app, where the extemely robust, clever &lt;strong&gt;matplotlib&lt;/strong&gt; architecture works.
It works in spite of me using it utterly incorrectly.&lt;/p&gt;
&lt;p&gt;Day 3. Blinding realization that for the last two days, I've been &lt;strong&gt;doing it wrong.&lt;/strong&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="the-big-reveal"&gt;
&lt;h2&gt;The Big Reveal&lt;/h2&gt;
&lt;p&gt;Just about everything that happens in &lt;strong&gt;matplotlib&lt;/strong&gt; depends on an abstraction called an &lt;tt class="docutils literal"&gt;Artist&lt;/tt&gt; being out of date from the canvas.&lt;/p&gt;
&lt;p&gt;This is not obvious, and no one needs to know it except in the rare cases of an animation.&lt;/p&gt;
&lt;p&gt;The rest of the time, we observe that changes to scale or axes or whatever lead to changes to the diagram
that are just what were expected. The &amp;quot;out-of-date&amp;quot; business doesn't enter into our experience
when we're making changes that update the static diagram we want.&lt;/p&gt;
&lt;p&gt;Anyone (even me) can get things to work by simply creating axes, creating a &amp;quot;fill&amp;quot; (a Polygon, actually), and being happy.
The &lt;tt class="docutils literal"&gt;plt.show()&lt;/tt&gt; works.&lt;/p&gt;
&lt;p&gt;But that's actually &lt;strong&gt;not&lt;/strong&gt; right for the kinds of things I was trying to do.&lt;/p&gt;
&lt;p&gt;Here's what I was working on.&lt;/p&gt;
&lt;a class="reference external image-reference" href="https://slott56.github.io/media/Empire_1337.png"&gt;
&lt;img alt="Hexagonal map showing 5 interlocking regions" src="https://slott56.github.io/media/Empire_1337.png" style="width: 400px; height: 400px;" /&gt;
&lt;/a&gt;
&lt;p&gt;This map is actually &amp;quot;grown&amp;quot; using some simple rules from a few seed points.
The animation of that growth process is what I want.&lt;/p&gt;
&lt;p&gt;This isn't as clever as &lt;a class="reference external" href="https://conwaylife.com"&gt;Conway's Game of Life&lt;/a&gt;, but it is similar in a few respects.
Mine, for example, involves random numbers. Is that desirable? Can the dependency be reduced
and still lead to complicated structures?&lt;/p&gt;
&lt;p&gt;I want to tinker with the rules.&lt;/p&gt;
&lt;p&gt;(I have a version running in the Pythonista environment on my iPad. I want a version
in JupyterLab that I can expand on more easily.)&lt;/p&gt;
&lt;p&gt;Let's compare and contrast the two approaches&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="seductive-and-wrong"&gt;
&lt;h2&gt;Seductive and Wrong&lt;/h2&gt;
&lt;p&gt;This is seductive and simple. It fits (to an extent) with previous examples.
It seems so right. And it sometimes works. But it's so wrong.&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;Create 324 polygon outlines to paint the background grid.&lt;/li&gt;
&lt;li&gt;Create 324 text labels to label the hexes.&lt;/li&gt;
&lt;li&gt;As the generative algorithm runs, create colored polygon fill patches, showing
how the 5 seed positions evolve into the 5 interlocking shapes.
This starts with 5 filled polygons and grows to 200+ polygons through 48 generations.
So that's &lt;span class="math"&gt;\(35 + ... + 235 = 6,650\)&lt;/span&gt; filled polygons.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Wave after wave of increasing number of polygons.
Sure, it's a lot of objects. I have a big laptop. We're good.&lt;/p&gt;
&lt;p&gt;This has two problems.&lt;/p&gt;
&lt;ol class="arabic simple"&gt;
&lt;li&gt;It's slow.&lt;/li&gt;
&lt;li&gt;If I save the animation as an HTML or JSHTML object, I get a cycling animation with the right number of images, but  no content in any image.&lt;/li&gt;
&lt;li&gt;In spite of the animation being empty, the final image looks good.&lt;/li&gt;
&lt;li&gt;A few off-by-one errors.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;What's wrong?&lt;/p&gt;
&lt;p&gt;I'm patient and thorough. I tried a &lt;strong&gt;lot&lt;/strong&gt; of things.  I added Qt. I added ipympl. I restructured the animation
as functions and callable objects. I used &lt;tt class="docutils literal"&gt;FuncAnimation&lt;/tt&gt; and &lt;tt class="docutils literal"&gt;ArtistAnimation&lt;/tt&gt;.&lt;/p&gt;
&lt;p&gt;Nothing Worked.  Nothing.&lt;/p&gt;
&lt;blockquote&gt;
&lt;strong&gt;Spoiler Alert&lt;/strong&gt;.  That's how you know you're doing something fundamentally wrong.
The thing works in general. But specific features are missing.&lt;/blockquote&gt;
&lt;/div&gt;
&lt;div class="section" id="how-can-that-be-wrong"&gt;
&lt;h2&gt;How Can That Be Wrong?&lt;/h2&gt;
&lt;p&gt;The foundational mis-understanding was trying to animate the appearance of various &lt;strong&gt;matplotlib&lt;/strong&gt; &lt;tt class="docutils literal"&gt;Artist&lt;/tt&gt; objects
on the map.&lt;/p&gt;
&lt;p&gt;I drew the grid. I drew the labels.&lt;/p&gt;
&lt;p&gt;Then the colored hexes are supposed to appear, one at at time. I figured (wrongly) I would just draw the filled polygons.&lt;/p&gt;
&lt;p&gt;See above. &amp;quot;Just about everything that happens in &lt;strong&gt;matplotlib&lt;/strong&gt; depends on an abstraction called an &lt;tt class="docutils literal"&gt;Artist&lt;/tt&gt; being out of date from the canvas.&amp;quot;&lt;/p&gt;
&lt;p&gt;Out-of-date?&lt;/p&gt;
&lt;p&gt;Out-of-date!&lt;/p&gt;
&lt;p&gt;State Change.&lt;/p&gt;
&lt;p&gt;The &lt;tt class="docutils literal"&gt;Artist&lt;/tt&gt; won't paint unless there's something &lt;strong&gt;new&lt;/strong&gt; to paint.&lt;/p&gt;
&lt;p&gt;On day three, I realized the truth.&lt;/p&gt;
&lt;p&gt;It works like this:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;Create 324 polygon outlines to paint the background grid.&lt;/li&gt;
&lt;li&gt;Create 324 text labels to label the hexes.&lt;/li&gt;
&lt;li&gt;Create 324 polygons filled with white.&lt;/li&gt;
&lt;li&gt;As the generative algorithm runs, change the color in the polygon.
&lt;strong&gt;Change&lt;/strong&gt; the color. Change.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Change. The &lt;tt class="docutils literal"&gt;Artist&lt;/tt&gt; is waiting for a change.&lt;/p&gt;
&lt;p&gt;Don't create a wave of new polygons. Change the color of the polygons.
It's simpler. It's faster. It works.&lt;/p&gt;
&lt;pre class="literal-block"&gt;
def paint(self, col: int, row: int, fill: str) -&amp;gt; None:
    &amp;quot;&amp;quot;&amp;quot;
    Update a hex's fill color.
    &amp;quot;&amp;quot;&amp;quot;
    for a in self.cells[col, row]:
        a.set(
            fill=True,
            color=fill
        )
&lt;/pre&gt;
&lt;p&gt;Don't create a new polygon.  Change the color of the polygon you have.&lt;/p&gt;
&lt;p&gt;I still have no idea how the scale factors work when creating the JSHTML.
I have eight mypy complaints because I'm not using &lt;strong&gt;matplotlib&lt;/strong&gt; correctly.
I have more work to do.&lt;/p&gt;
&lt;p&gt;But. I have pictures that work. For the right reason.&lt;/p&gt;
&lt;/div&gt;
&lt;script type='text/javascript'&gt;if (!document.getElementById('mathjaxscript_pelican_#%@#$@#')) {
    var align = "center",
        indent = "0em",
        linebreak = "false";

    if (false) {
        align = (screen.width &lt; 768) ? "left" : align;
        indent = (screen.width &lt; 768) ? "0em" : indent;
        linebreak = (screen.width &lt; 768) ? 'true' : linebreak;
    }

    var mathjaxscript = document.createElement('script');
    mathjaxscript.id = 'mathjaxscript_pelican_#%@#$@#';
    mathjaxscript.type = 'text/javascript';
    mathjaxscript.src = 'https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.3/latest.js?config=TeX-AMS-MML_HTMLorMML';

    var configscript = document.createElement('script');
    configscript.type = 'text/x-mathjax-config';
    configscript[(window.opera ? "innerHTML" : "text")] =
        "MathJax.Hub.Config({" +
        "    config: ['MMLorHTML.js']," +
        "    TeX: { extensions: ['AMSmath.js','AMSsymbols.js','noErrors.js','noUndefined.js'], equationNumbers: { autoNumber: 'none' } }," +
        "    jax: ['input/TeX','input/MathML','output/HTML-CSS']," +
        "    extensions: ['tex2jax.js','mml2jax.js','MathMenu.js','MathZoom.js']," +
        "    displayAlign: '"+ align +"'," +
        "    displayIndent: '"+ indent +"'," +
        "    showMathMenu: true," +
        "    messageStyle: 'normal'," +
        "    tex2jax: { " +
        "        inlineMath: [ ['\\\\(','\\\\)'] ], " +
        "        displayMath: [ ['$$','$$'] ]," +
        "        processEscapes: true," +
        "        preview: 'TeX'," +
        "    }, " +
        "    'HTML-CSS': { " +
        "        availableFonts: ['STIX', 'TeX']," +
        "        preferredFont: 'STIX'," +
        "        styles: { '.MathJax_Display, .MathJax .mo, .MathJax .mi, .MathJax .mn': {color: 'inherit ! important'} }," +
        "        linebreaks: { automatic: "+ linebreak +", width: '90% container' }," +
        "    }, " +
        "}); " +
        "if ('default' !== 'default') {" +
            "MathJax.Hub.Register.StartupHook('HTML-CSS Jax Ready',function () {" +
                "var VARIANT = MathJax.OutputJax['HTML-CSS'].FONTDATA.VARIANT;" +
                "VARIANT['normal'].fonts.unshift('MathJax_default');" +
                "VARIANT['bold'].fonts.unshift('MathJax_default-bold');" +
                "VARIANT['italic'].fonts.unshift('MathJax_default-italic');" +
                "VARIANT['-tex-mathit'].fonts.unshift('MathJax_default-italic');" +
            "});" +
            "MathJax.Hub.Register.StartupHook('SVG Jax Ready',function () {" +
                "var VARIANT = MathJax.OutputJax.SVG.FONTDATA.VARIANT;" +
                "VARIANT['normal'].fonts.unshift('MathJax_default');" +
                "VARIANT['bold'].fonts.unshift('MathJax_default-bold');" +
                "VARIANT['italic'].fonts.unshift('MathJax_default-italic');" +
                "VARIANT['-tex-mathit'].fonts.unshift('MathJax_default-italic');" +
            "});" +
        "}";

    (document.body || document.getElementsByTagName('head')[0]).appendChild(configscript);
    (document.body || document.getElementsByTagName('head')[0]).appendChild(mathjaxscript);
}
&lt;/script&gt;</content><category term="Python"></category><category term="language"></category><category term="semantics"></category></entry><entry><title>It's Not THE Ternary Operator -- there are many</title><link href="https://slott56.github.io/2023_12_05-not_the_ternary_operator.html" rel="alternate"></link><published>2023-12-05T08:01:00-05:00</published><updated>2023-12-05T08:01:00-05:00</updated><author><name>S.Lott</name></author><id>tag:slott56.github.io,2023-12-05:/2023_12_05-not_the_ternary_operator.html</id><summary type="html">&lt;p&gt;I'm sick of reading about &lt;strong&gt;THE&lt;/strong&gt; Ternary Operator.&lt;/p&gt;
&lt;p&gt;There is not merely &lt;strong&gt;a&lt;/strong&gt; single operator that is ternary.
There are many operators that are ternary.&lt;/p&gt;
&lt;p&gt;Here's one example:&lt;/p&gt;
&lt;pre class="literal-block"&gt;
&amp;gt;&amp;gt;&amp;gt; 6+1 &amp;gt;= 6 &amp;gt;= 6-1
&lt;/pre&gt;
&lt;p&gt;The &lt;tt class="docutils literal"&gt;&amp;gt;= &amp;gt;=&lt;/tt&gt; operator is ternary. It has 3 operands.  Count them.&lt;/p&gt;
&lt;p&gt;There are a 36 of these ternary operators …&lt;/p&gt;</summary><content type="html">&lt;p&gt;I'm sick of reading about &lt;strong&gt;THE&lt;/strong&gt; Ternary Operator.&lt;/p&gt;
&lt;p&gt;There is not merely &lt;strong&gt;a&lt;/strong&gt; single operator that is ternary.
There are many operators that are ternary.&lt;/p&gt;
&lt;p&gt;Here's one example:&lt;/p&gt;
&lt;pre class="literal-block"&gt;
&amp;gt;&amp;gt;&amp;gt; 6+1 &amp;gt;= 6 &amp;gt;= 6-1
&lt;/pre&gt;
&lt;p&gt;The &lt;tt class="docutils literal"&gt;&amp;gt;= &amp;gt;=&lt;/tt&gt; operator is ternary. It has 3 operands.  Count them.&lt;/p&gt;
&lt;p&gt;There are a 36 of these ternary operators.&lt;/p&gt;
&lt;p&gt;&amp;quot;Oh, pish-tosh,&amp;quot; you say. &amp;quot;It's an example of two binary operators.&amp;quot;&lt;/p&gt;
&lt;p&gt;Really?&lt;/p&gt;
&lt;p&gt;In a sense, you're almost right, it's equivalent to &lt;tt class="docutils literal"&gt;6+1 &amp;gt;= 6 and 6 &amp;gt;= &lt;span class="pre"&gt;6-1&lt;/span&gt;&lt;/tt&gt;. Which is three binary operators.&lt;/p&gt;
&lt;p&gt;But it's &lt;strong&gt;not&lt;/strong&gt; three binary operators. It's one ternary operator.&lt;/p&gt;
&lt;p&gt;&amp;quot;Whoa. What about &lt;tt class="docutils literal"&gt;5 &amp;lt; 6 &amp;lt; 7 &amp;lt; 8&lt;/tt&gt;?&amp;quot; you reply triumphantly. &amp;quot;That has four operands!&amp;quot;&lt;/p&gt;
&lt;p&gt;Right. It's quaternary. There a lot of ways to create quaternary (and higher) operators in Python.
There are 216 of quaternary operators.  1296 quinary.&lt;/p&gt;
&lt;p&gt;There is not &lt;strong&gt;A Ternary Operator&lt;/strong&gt;.  The phrase is meaningless.  You can stop using it.&lt;/p&gt;
&lt;div class="section" id="there-s-more"&gt;
&lt;h2&gt;There's more&lt;/h2&gt;
&lt;p&gt;We're not done.&lt;/p&gt;
&lt;pre class="literal-block"&gt;
&amp;gt;&amp;gt;&amp;gt; (2*a + 1
...    for a in range(5)
...    if a % 2 == 0
... )
&lt;/pre&gt;
&lt;p&gt;Is ternary.&lt;/p&gt;
&lt;p&gt;I'll clarify:&lt;/p&gt;
&lt;ol class="arabic simple"&gt;
&lt;li&gt;&lt;tt class="docutils literal"&gt;2*a + 1&lt;/tt&gt; -- expression #1&lt;/li&gt;
&lt;li&gt;&lt;tt class="docutils literal"&gt;range(5)&lt;/tt&gt; -- expression #2&lt;/li&gt;
&lt;li&gt;&lt;tt class="docutils literal"&gt;a % 2 == 0&lt;/tt&gt; -- expression #3&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Admittedly, the two pieces of interstitial syntax are complicated.&lt;/p&gt;
&lt;ol class="loweralpha simple"&gt;
&lt;li&gt;&lt;tt class="docutils literal"&gt;for a in&lt;/tt&gt;  -- kind of big; and it names a bind variable.&lt;/li&gt;
&lt;li&gt;&lt;tt class="docutils literal"&gt;if&lt;/tt&gt; -- more typical for ternary operators.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;But. There are three operands separated by punctuation.&lt;/p&gt;
&lt;p&gt;It is, therefore, ternary.&lt;/p&gt;
&lt;p&gt;And yes. When we have multiple &lt;tt class="docutils literal"&gt;for&lt;/tt&gt; clauses or multiple &lt;tt class="docutils literal"&gt;if&lt;/tt&gt; clauses, we clearly have quaternary and quinary operators.
That's part of my point: there are a number of &lt;em&gt;arities&lt;/em&gt; and a number of operators of each arity.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="i-could-go-on"&gt;
&lt;h2&gt;I could go on&lt;/h2&gt;
&lt;p&gt;A Big Pointless Beef (BPB™) is often this.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The ternary operator&lt;/strong&gt; (by which I presume they mean the conditional expression) &lt;strong&gt;evaluates the middle first.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Which is kind of a &amp;quot;so what?&amp;quot;&lt;/p&gt;
&lt;p&gt;Many things in Python are left-to-right.&lt;/p&gt;
&lt;p&gt;But not everything is trivially left-to-right.&lt;/p&gt;
&lt;pre class="literal-block"&gt;
&amp;gt;&amp;gt;&amp;gt; noisy = lambda x: print(x) or x
&amp;gt;&amp;gt;&amp;gt; list(noisy(2*a + 1)
...   for a in noisy(range(5))
...   if noisy(a % 2 == 0)
... )
range(0, 5)  # expression 2, the range(...)
True  # expression 3, the if.
1  # expression 1, the 2*a+1.
False
True
5
False
True
9
[1, 5, 9]
&lt;/pre&gt;
&lt;p&gt;The &lt;tt class="docutils literal"&gt;range(5)&lt;/tt&gt; -- in the middle of this particular ternay operator -- is evaluated first.
And only evaluated once, where the outer expressions are evaluated on right-to-left order over and over again.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="we-re-not-done"&gt;
&lt;h2&gt;We're not done&lt;/h2&gt;
&lt;p&gt;Consider, if you will,&lt;/p&gt;
&lt;pre class="literal-block"&gt;
&amp;gt;&amp;gt;&amp;gt; a = list(range(5))
&amp;gt;&amp;gt;&amp;gt; a[1:-1]
[1, 2, 3]
&lt;/pre&gt;
&lt;p&gt;The &lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;a[1:-1]&lt;/span&gt;&lt;/tt&gt; is ternary. It has three expressions. Count them yourself.&lt;/p&gt;
&lt;p&gt;Also, &lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;a[:-1:2]&lt;/span&gt;&lt;/tt&gt; and &lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;a[-1::-1]&lt;/span&gt;&lt;/tt&gt;.  All ternary subsets of a more general quaternary operator.&lt;/p&gt;
&lt;p&gt;&amp;quot;That'a wrong! You can't call a slice part of an operator,&amp;quot; you claim.&lt;/p&gt;
&lt;p&gt;Perhaps I am pushing it. But it sure looks like &lt;tt class="docutils literal"&gt;a&lt;/tt&gt; is one expressions, &lt;tt class="docutils literal"&gt;1&lt;/tt&gt; is another and &lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;-1&lt;/span&gt;&lt;/tt&gt; is the third.
And it sure looks like &lt;tt class="docutils literal"&gt;[&lt;/tt&gt; is one separator, &lt;tt class="docutils literal"&gt;:&lt;/tt&gt; is another, and there's an extra closing
punctuation mark of &lt;tt class="docutils literal"&gt;]&lt;/tt&gt;.&lt;/p&gt;
&lt;p&gt;&amp;quot;You've jumbled up indexing and slicing!&amp;quot; you claim. &amp;quot;They're clearly separate syntactic categories!&amp;quot;&lt;/p&gt;
&lt;p&gt;Clearly? If I can only using slicing in the context of indexing, I'm not completely sold that these two concepts
are separate and foreign.  They seem pretty tightly coupled.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="summary"&gt;
&lt;h2&gt;Summary&lt;/h2&gt;
&lt;p&gt;Stop writing (and saying) &amp;quot;The Ternary Operator&amp;quot;. Please.&lt;/p&gt;
&lt;p&gt;There are a lot of ternary operators.&lt;/p&gt;
&lt;p&gt;If you don't like the &lt;strong&gt;Conditional Expression&lt;/strong&gt; because it's too much like a list comprehension with that &amp;quot;evaluate something not on the left first&amp;quot; semantics,
please say that you don't like &lt;strong&gt;The Section 6.13 Conditional Expression&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;Please.  Please try to be precise.&lt;/p&gt;
&lt;p&gt;Otherwise, the rest of your rant on evaluation order looks like you haven't really taken the time to think things through.
Maybe you have, but the use of &amp;quot;The Ternary Operator&amp;quot; dilutes your message.&lt;/p&gt;
&lt;p&gt;Other languages use phrases like &amp;quot;the ternary operator.&amp;quot; That doesn't really mean much.
We're talking about Python, where there's more than one.&lt;/p&gt;
&lt;/div&gt;
</content><category term="Python"></category><category term="language"></category><category term="semantics"></category></entry><entry><title>This is Awful</title><link href="https://slott56.github.io/2023-11-14-this_is_awful.html" rel="alternate"></link><published>2023-11-14T08:01:00-05:00</published><updated>2023-11-14T08:01:00-05:00</updated><author><name>S.Lott</name></author><id>tag:slott56.github.io,2023-11-14:/2023-11-14-this_is_awful.html</id><summary type="html">&lt;p&gt;This is a disheartening thing to read&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;As someone who doesn't do a ton of JSON parsing on the command line, I tend to forget &lt;a class="reference external" href="https://jqlang.github.io/jq/manual/"&gt;jq&lt;/a&gt; syntax.&lt;/p&gt;
&lt;p&gt;Two tools I really like are &lt;a class="reference external" href="github.com/tomnomnom/gron"&gt;gron&lt;/a&gt; (make JSON greppable) from &amp;#64;tomnomnom and &lt;a class="reference external" href="github.com/noahgorstein/jqp"&gt;jqp&lt;/a&gt; ..., which provides a &amp;quot;tui playground for exploring jq.&amp;quot;&lt;/p&gt;
&lt;p&gt;98 …&lt;/p&gt;&lt;/blockquote&gt;</summary><content type="html">&lt;p&gt;This is a disheartening thing to read&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;As someone who doesn't do a ton of JSON parsing on the command line, I tend to forget &lt;a class="reference external" href="https://jqlang.github.io/jq/manual/"&gt;jq&lt;/a&gt; syntax.&lt;/p&gt;
&lt;p&gt;Two tools I really like are &lt;a class="reference external" href="github.com/tomnomnom/gron"&gt;gron&lt;/a&gt; (make JSON greppable) from &amp;#64;tomnomnom and &lt;a class="reference external" href="github.com/noahgorstein/jqp"&gt;jqp&lt;/a&gt; ..., which provides a &amp;quot;tui playground for exploring jq.&amp;quot;&lt;/p&gt;
&lt;p&gt;98% of the time I end up being able to get what I need with gron + grep, then jqp is awesome for when I actually need jq :)&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;What's so bad?&lt;/p&gt;
&lt;p&gt;With Python, all of this &amp;quot;syntax&amp;quot; and &amp;quot;greppable&amp;quot; and &amp;quot;interactive&amp;quot; goes away.&lt;/p&gt;
&lt;p&gt;Stop using bash. Start using Python. Life is so much simpler. (And faster. And unit-testable.)&lt;/p&gt;
</content><category term="Python"></category><category term="bash"></category><category term="json"></category></entry><entry><title>The Debugger</title><link href="https://slott56.github.io/2023_10_10-the_debugger.html" rel="alternate"></link><published>2023-10-10T18:21:00-04:00</published><updated>2023-10-10T18:21:00-04:00</updated><author><name>S.Lott</name></author><id>tag:slott56.github.io,2023-10-10:/2023_10_10-the_debugger.html</id><summary type="html">&lt;p&gt;See &lt;a class="reference external" href="https://www.bitecode.dev/p/python-312-what-didnt-make-the-headlines"&gt;Python 3.12: what didn't make the headlines&lt;/a&gt;. This is &lt;strong&gt;very&lt;/strong&gt; helpful.&lt;/p&gt;
&lt;p&gt;It is a great list of 7 key features of Python 3.12.&lt;/p&gt;
&lt;p&gt;With one tiny point I need to object to.&lt;/p&gt;
&lt;div class="section" id="i-don-t-like-debuggers"&gt;
&lt;h2&gt;I don't like debuggers&lt;/h2&gt;
&lt;p&gt;This is a strongly-held position.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Debuggers are harmful.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;I say this …&lt;/p&gt;&lt;/div&gt;</summary><content type="html">&lt;p&gt;See &lt;a class="reference external" href="https://www.bitecode.dev/p/python-312-what-didnt-make-the-headlines"&gt;Python 3.12: what didn't make the headlines&lt;/a&gt;. This is &lt;strong&gt;very&lt;/strong&gt; helpful.&lt;/p&gt;
&lt;p&gt;It is a great list of 7 key features of Python 3.12.&lt;/p&gt;
&lt;p&gt;With one tiny point I need to object to.&lt;/p&gt;
&lt;div class="section" id="i-don-t-like-debuggers"&gt;
&lt;h2&gt;I don't like debuggers&lt;/h2&gt;
&lt;p&gt;This is a strongly-held position.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Debuggers are harmful.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;I say this because I have had the misfortune to help more than one programmer
who could not actually describe the semantics of the code.&lt;/p&gt;
&lt;p&gt;They couldn't draw a picture. Write a sentence. Nothing.&lt;/p&gt;
&lt;p&gt;They could only point at the interactive debugger session with hapless flailing, and &amp;quot;see, it should work&amp;quot;
kind of noises.&lt;/p&gt;
&lt;p&gt;This is emphatically &lt;strong&gt;bad&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;Every time I would ask them to step away from the debugger and describe -- maybe on a whiteboard --
what the heck they thought was going on.&lt;/p&gt;
&lt;p&gt;I could go on with horror stories of bad debugging.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="i-use-debuggers"&gt;
&lt;h2&gt;I use debuggers&lt;/h2&gt;
&lt;p&gt;Back when C++ was my &amp;quot;stock-in-trade&amp;quot;, I used the debugger.&lt;/p&gt;
&lt;p&gt;Rarely.&lt;/p&gt;
&lt;p&gt;And then, mostly, on core dump files to figure out where the program failed.&lt;/p&gt;
&lt;p&gt;And to look at a few key variables to confirm the state of the computation.&lt;/p&gt;
&lt;p&gt;Then.&lt;/p&gt;
&lt;p&gt;I went back to the source, and looked for a logic path that lead to the wrong state.
It wasn't often hard to find.
And it didn't involve using the debugger for much more than finding the
call frame, stack contents, and local variables.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="what-set-me-off"&gt;
&lt;h2&gt;What set me off&lt;/h2&gt;
&lt;p&gt;This:&lt;/p&gt;
&lt;blockquote&gt;
...it's also removing a big &amp;quot;WTF&amp;quot; that all beginners will experience using the Python debugger with nobody in sight to explain to them what's going on.&lt;/blockquote&gt;
&lt;p&gt;I think there are no circumstances under which beginners should be using the debugger.&lt;/p&gt;
&lt;p&gt;I think there are no circumstances under which anyone should use a debugger before they already know
what's supposed to be going on.&lt;/p&gt;
&lt;p&gt;The idea of &amp;quot;beginners&amp;quot; being surprised at the structure of stackframes is an oxymoron.&lt;/p&gt;
&lt;blockquote&gt;
Beginners don't know about stack frames.&lt;/blockquote&gt;
&lt;p&gt;More-or-less, this is one definition of &amp;quot;beginner&amp;quot;.&lt;/p&gt;
&lt;p&gt;People who know about stack frames aren't beginners and can be trusted to understand the debugger.&lt;/p&gt;
&lt;p&gt;The points in the blog posts are sound: better debugging, additional support for evaluating expressions.&lt;/p&gt;
&lt;p&gt;The &amp;quot;audience of beginners&amp;quot; is my only quibble.&lt;/p&gt;
&lt;/div&gt;
</content><category term="Python"></category><category term="games"></category><category term="tutorial"></category></entry><entry><title>The Echo-Pipe Trap [Updated]</title><link href="https://slott56.github.io/2023-08-30-the_echo_pipe_trap.html" rel="alternate"></link><published>2023-08-30T09:00:00-04:00</published><updated>2023-08-30T09:00:00-04:00</updated><author><name>S.Lott</name></author><id>tag:slott56.github.io,2023-08-30:/2023-08-30-the_echo_pipe_trap.html</id><summary type="html">&lt;p&gt;This is a &lt;strong&gt;great&lt;/strong&gt; question.&lt;/p&gt;
&lt;p&gt;&lt;a class="reference external" href="https://fosstodon.org/&amp;#64;JustineSmithies/110979871574705636"&gt;https://fosstodon.org/&amp;#64;JustineSmithies/110979871574705636&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;This works, they said.&lt;/p&gt;
&lt;blockquote&gt;
echo -en &amp;quot;Firefox\0icon\x1fweechat&amp;quot; | fuzzel -d -w 100 -l 10&lt;/blockquote&gt;
&lt;p&gt;But. The superficial switch to &lt;tt class="docutils literal"&gt;subprocess.Popen()&lt;/tt&gt; doesn't work.&lt;/p&gt;
&lt;p&gt;Why?&lt;/p&gt;
&lt;p&gt;The way &lt;tt class="docutils literal"&gt;echo&lt;/tt&gt; works varies from shell to shell. When MacOSX changes to zsh, things …&lt;/p&gt;</summary><content type="html">&lt;p&gt;This is a &lt;strong&gt;great&lt;/strong&gt; question.&lt;/p&gt;
&lt;p&gt;&lt;a class="reference external" href="https://fosstodon.org/&amp;#64;JustineSmithies/110979871574705636"&gt;https://fosstodon.org/&amp;#64;JustineSmithies/110979871574705636&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;This works, they said.&lt;/p&gt;
&lt;blockquote&gt;
echo -en &amp;quot;Firefox\0icon\x1fweechat&amp;quot; | fuzzel -d -w 100 -l 10&lt;/blockquote&gt;
&lt;p&gt;But. The superficial switch to &lt;tt class="docutils literal"&gt;subprocess.Popen()&lt;/tt&gt; doesn't work.&lt;/p&gt;
&lt;p&gt;Why?&lt;/p&gt;
&lt;p&gt;The way &lt;tt class="docutils literal"&gt;echo&lt;/tt&gt; works varies from shell to shell. When MacOSX changes to zsh, things can break.
Or when you share it with someone else, who uses YetAnotherShell, things break.&lt;/p&gt;
&lt;p&gt;Two choices:&lt;/p&gt;
&lt;ol class="arabic simple"&gt;
&lt;li&gt;Specify which shell.&lt;/li&gt;
&lt;li&gt;Stop using the &lt;tt class="docutils literal"&gt;echo ... |&lt;/tt&gt; (echo-pipe) construct.&lt;/li&gt;
&lt;/ol&gt;
&lt;div class="section" id="what-s-better"&gt;
&lt;h2&gt;What's Better?&lt;/h2&gt;
&lt;p&gt;The &lt;tt class="docutils literal"&gt;stdin&lt;/tt&gt; parameter to &lt;tt class="docutils literal"&gt;Popen()&lt;/tt&gt; can be used to provide the required stream of bytes.&lt;/p&gt;
&lt;pre class="literal-block"&gt;
from pathlib import Path
import subprocess

temp = Path.cwd() / &amp;quot;temp.tmp&amp;quot;
temp.write_text(&amp;quot;Firefox\0icon\0x1fweechat&amp;quot;)  # I think.

with temp.open() as echo_file:
    subprocess.Popen(['fuzzel', '-d', '-w', '100', '-l', '10'], stdin=echo_file)
&lt;/pre&gt;
&lt;p&gt;Something like the above will avoid the echo-pipe construct.&lt;/p&gt;
&lt;p&gt;But. It leaves a temporary file lying around. What to do?&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="slightly-better"&gt;
&lt;h2&gt;Slightly Better&lt;/h2&gt;
&lt;p&gt;This will cleanup the file. And. You don't have to pick a name.&lt;/p&gt;
&lt;pre class="literal-block"&gt;
import tempfile
import subprocess

temp = tempfile.TemporaryFile(mode='w+')
with temp:
    temp.write(&amp;quot;Firefox\0icon\0x1fweechat&amp;quot;)  # I think.
    temp.seek(0)
    subprocess.Popen(['fuzzel', '-d', '-w', '100', '-l', '10'], stdin=temp)
&lt;/pre&gt;
&lt;p&gt;Seems kind of long. And it involves an additional problem. A file.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="but-there-s-a-file"&gt;
&lt;h2&gt;But. There's a FILE!&lt;/h2&gt;
&lt;p&gt;Yes. We &lt;strong&gt;can&lt;/strong&gt; create a pipe.  I think it's kind of hideous, though.&lt;/p&gt;
&lt;pre class="literal-block"&gt;
import os
import subprocess

r, w = os.pipe()
readable = os.fdopen(r, 'r')
writeable = os.fdopen(w, 'w')
writeable.write(&amp;quot;Firefox\0icon\0x1fweechat&amp;quot;)  # I think.
writeable.close()
subprocess.Popen(['fuzzel', '-d', '-w', '100', '-l', '10'], stdin=readable)
readable.close()
&lt;/pre&gt;
&lt;p&gt;However. It's not too long.&lt;/p&gt;
&lt;p&gt;We can create a pleasant wrapper in the form
of a context manager.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="how-s-this"&gt;
&lt;h2&gt;How's This?&lt;/h2&gt;
&lt;p&gt;This seems pleasant, if you do a lot of this sort of thing.&lt;/p&gt;
&lt;pre class="literal-block"&gt;
import os
import subprocess

class EchoPipe:
    def __init__(self, content):
        self.content = content

    def __enter__(self):
        r, w = os.pipe()
        self.readable = os.fdopen(r, 'r')
        writeable = os.fdopen(w, 'w')
        writeable.write(self.content)
        writeable.close()
        return self.readable

    def __exit__(self, exc_type, exc_value, traceback):
        self.readable.close()
&lt;/pre&gt;
&lt;p&gt;Once you have the &lt;tt class="docutils literal"&gt;EchoPipe&lt;/tt&gt; context, you can now write this.&lt;/p&gt;
&lt;pre class="literal-block"&gt;
with EchoPipe(&amp;quot;Firefox\0icon\0x1fweechat&amp;quot;) as echo_pipe:
    subprocess.Popen(['fuzzel', '-d', '-w', '100', '-l', '10'], stdin=echo_pipe)
&lt;/pre&gt;
&lt;p&gt;Which is pretty close to the original terse shell stuff.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="and-this-update"&gt;
&lt;h2&gt;And this [Update]&lt;/h2&gt;
&lt;p&gt;Consider this...&lt;/p&gt;
&lt;pre class="literal-block"&gt;
subprocess.run(
    ['fuzzel', '-d', '-w', '100', '-l', '10'],
    input=&amp;quot;Firefox\0icon\0x1fweechat&amp;quot;,
    text=True
)
&lt;/pre&gt;
&lt;p&gt;It a very useful variant. This is -- perhaps -- the best of them all.&lt;/p&gt;
&lt;p&gt;I found the documentation is a bit hard to follow around this topic, so I was
completely taken by surprise when I was shown this.&lt;/p&gt;
&lt;/div&gt;
</content><category term="Python"></category><category term="shell"></category><category term="bash"></category></entry><entry><title>More Python Quirks Debunking</title><link href="https://slott56.github.io/2023-08-15-more_python_quirk_debunking.html" rel="alternate"></link><published>2023-08-15T09:00:00-04:00</published><updated>2023-08-15T09:00:00-04:00</updated><author><name>S.Lott</name></author><id>tag:slott56.github.io,2023-08-15:/2023-08-15-more_python_quirk_debunking.html</id><summary type="html">&lt;p&gt;Stuff I found on the internet that I have to disagree with.&lt;/p&gt;
&lt;p&gt;(And no, I didn't ask for clarification.
If the author posts things without supporting details it suggests they might lack the supporting
details. I can be charitable and assume they don't really care about providing useful information,
but …&lt;/p&gt;</summary><content type="html">&lt;p&gt;Stuff I found on the internet that I have to disagree with.&lt;/p&gt;
&lt;p&gt;(And no, I didn't ask for clarification.
If the author posts things without supporting details it suggests they might lack the supporting
details. I can be charitable and assume they don't really care about providing useful information,
but are merely trolling for engagement. Yes. That's cruel.
I can't see how you take the time to have an opinion and not provide support for it.)&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;A few of #Python 3 #quirks and #kludges&lt;/p&gt;
&lt;ol class="arabic simple"&gt;
&lt;li&gt;Global Interpreter Lock&lt;/li&gt;
&lt;li&gt;Strong, dynamic typing&lt;/li&gt;
&lt;li&gt;Massive &lt;tt class="docutils literal"&gt;Any&lt;/tt&gt; hole in the type system&lt;/li&gt;
&lt;li&gt;Verbose class definition&lt;/li&gt;
&lt;li&gt;Declaration of instance attributes is their definitions in &lt;tt class="docutils literal"&gt;__init__()&lt;/tt&gt;&lt;/li&gt;
&lt;li&gt;Repetitious &lt;tt class="docutils literal"&gt;self&lt;/tt&gt; and kludgy &lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;super().__init__()&lt;/span&gt;&lt;/tt&gt;&lt;/li&gt;
&lt;li&gt;Kludgy string-quotes to reference the class from within its definition&lt;/li&gt;
&lt;li&gt;Kludgy &lt;tt class="docutils literal"&gt;TypeVar&lt;/tt&gt; definition&lt;/li&gt;
&lt;li&gt;Absence of structural typing&lt;/li&gt;
&lt;li&gt;Need explicitly to convert iterator to list using &lt;tt class="docutils literal"&gt;list()&lt;/tt&gt;&lt;/li&gt;
&lt;li&gt;Usurping the &lt;tt class="docutils literal"&gt;id()&lt;/tt&gt; name&lt;/li&gt;
&lt;li&gt;Inner method may mutate referenced objects in the closure but may not mutate primitive values therein&lt;/li&gt;
&lt;li&gt;Kludgy &lt;tt class="docutils literal"&gt;main()&lt;/tt&gt; invocation&lt;/li&gt;
&lt;li&gt;Disconcerting lack of type information in the holdover documentation from the P2 days&lt;/li&gt;
&lt;/ol&gt;
&lt;/blockquote&gt;
&lt;p&gt;Some of these might be good points. Some of these seem to be nonsense.
A bunch can't be interpreted, which I find madding.&lt;/p&gt;
&lt;div class="section" id="global-interpreter-lock"&gt;
&lt;h2&gt;1. Global Interpreter Lock&lt;/h2&gt;
&lt;p&gt;I'm not sure what this means. It's a solution to a specific problem.
It's -- I suppose -- a candidate &amp;quot;quirk&amp;quot; because it's an unusual solution to
the problem of assuring that data structure updates are atomic.&lt;/p&gt;
&lt;p&gt;It saves us from having to use explicit locks all over the place when updating
objects with complex state.&lt;/p&gt;
&lt;p&gt;The GIL-less Python proposals will require a bit more care in defining structures
useful in multithreaded environments. Is this a gain? It's disputable.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="strong-dynamic-typing"&gt;
&lt;h2&gt;2. Strong, dynamic typing&lt;/h2&gt;
&lt;p&gt;Yep. There it is. Quirk? Kluge? Dunno. It seems like a brilliant solution to a long-standing problem.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="massive-any-hole-in-the-type-system"&gt;
&lt;h2&gt;3. Massive &lt;tt class="docutils literal"&gt;Any&lt;/tt&gt; hole in the type system&lt;/h2&gt;
&lt;p&gt;Does this mean it's bad that &lt;strong&gt;mypy&lt;/strong&gt; assumes &lt;tt class="docutils literal"&gt;Any&lt;/tt&gt; for missing types?
If you don't like the &lt;strong&gt;mypy&lt;/strong&gt; assumptions, write type hints.&lt;/p&gt;
&lt;p&gt;Does this mean it's bad that you can use &lt;tt class="docutils literal"&gt;Any&lt;/tt&gt; to provide uninformative type hints?
If you don't like &lt;tt class="docutils literal"&gt;Any&lt;/tt&gt;, consider not using it as a type hint.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="verbose-class-definition"&gt;
&lt;h2&gt;4. Verbose class definition&lt;/h2&gt;
&lt;p&gt;Quirk?  I guess they've never seen Java.&lt;/p&gt;
&lt;p&gt;Kluge?  What would they prefer?&lt;/p&gt;
&lt;p&gt;What would they omit is the real question. From the item 6, below, I'm guessing
they don't like to have &lt;tt class="docutils literal"&gt;self&lt;/tt&gt; listed explicitly.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="declaration-of-instance-attributes-is-their-definitions-in-init"&gt;
&lt;h2&gt;5. Declaration of instance attributes is their definitions in &lt;tt class="docutils literal"&gt;__init__()&lt;/tt&gt;&lt;/h2&gt;
&lt;p&gt;First -- and most important -- there aren't any C- or Java- style declarations.
The instances are dynamic in every sense of the word.
The &lt;tt class="docutils literal"&gt;__new__()&lt;/tt&gt; method does almost nothing.&lt;/p&gt;
&lt;p&gt;I'll buy this as a legit quirk. It's a consequence of the way attributes
work, and logic is compelling and consistent.&lt;/p&gt;
&lt;p&gt;It's trivial to include type hints in the class definition, separate
from initialization in the &lt;tt class="docutils literal"&gt;__init__()&lt;/tt&gt; method. I find this helpful.&lt;/p&gt;
&lt;pre class="literal-block"&gt;
class X:
    a: int
    b: float

    def __init__(self, a: str, b: str) -&amp;gt; None:
        self.a = int(a)
        self.b = float(b)
&lt;/pre&gt;
&lt;p&gt;It's potentially misleading: the &lt;tt class="docutils literal"&gt;a&lt;/tt&gt; and &lt;tt class="docutils literal"&gt;b&lt;/tt&gt; appear to be class variables.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="repetitious-self-and-kludgy-super-init"&gt;
&lt;h2&gt;6. Repetitious &lt;tt class="docutils literal"&gt;self&lt;/tt&gt; and kludgy &lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;super().__init__()&lt;/span&gt;&lt;/tt&gt;&lt;/h2&gt;
&lt;p&gt;The use of &lt;tt class="docutils literal"&gt;self&lt;/tt&gt; is not repetitious. It's explicit.&lt;/p&gt;
&lt;p&gt;Not sure what's klugy about &lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;super().__init__()&lt;/span&gt;&lt;/tt&gt;. I guess they don't like writing &lt;tt class="docutils literal"&gt;__init__()&lt;/tt&gt; and
prefer having this assumed, also.&lt;/p&gt;
&lt;p&gt;This is -- to me -- flat our wrong.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="kludgy-string-quotes-to-reference-the-class-from-within-its-definition"&gt;
&lt;h2&gt;7. Kludgy string-quotes to reference the class from within its definition&lt;/h2&gt;
&lt;p&gt;I guess they'd prefer to have an explicitly complicated-looking forward reference
for a name. We could have a lot of &lt;tt class="docutils literal"&gt;class XYZ: defined_later()&lt;/tt&gt; constructs
to sort out circular references among classes.&lt;/p&gt;
&lt;p&gt;I guess they don't like this.&lt;/p&gt;
&lt;pre class="literal-block"&gt;
class X:
    &amp;#64;classmethod
    def makes_X(cls: type[&amp;quot;X&amp;quot;], *args, **kwargs) -&amp;gt; &amp;quot;X&amp;quot;:
        ...
&lt;/pre&gt;
&lt;p&gt;It seems like a tedious &lt;tt class="docutils literal"&gt;X = &lt;span class="pre"&gt;ForwardRef('X')&lt;/span&gt;&lt;/tt&gt; would be required
to define the name &lt;tt class="docutils literal"&gt;X&lt;/tt&gt; before the actual class is defined.
See #8, below, they don't like that syntax. Does this mean they want a new statement for
forward references?&lt;/p&gt;
&lt;p&gt;Or. It would require &lt;strong&gt;mypy&lt;/strong&gt; to gaze more deeply at the parse tree to resolve
circular references. I'm not sure what they think would be better.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="kludgy-typevar-definition"&gt;
&lt;h2&gt;8. Kludgy &lt;tt class="docutils literal"&gt;TypeVar&lt;/tt&gt; definition&lt;/h2&gt;
&lt;p&gt;I'm guessing they want a new statement in the language instead of a function
in the &lt;tt class="docutils literal"&gt;typing&lt;/tt&gt; module.&lt;/p&gt;
&lt;p&gt;Since types are explicitly optional, new statements to handle types seems wrong to me.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="absence-of-structural-typing"&gt;
&lt;h2&gt;9. Absence of structural typing&lt;/h2&gt;
&lt;p&gt;This is confusing. The &lt;tt class="docutils literal"&gt;NamedTuple&lt;/tt&gt; provides structural types.&lt;/p&gt;
&lt;p&gt;I'm guessing they were hoping for some other classes to &lt;strong&gt;also&lt;/strong&gt; behave like
types in a structural system. It seems simplest to use &lt;tt class="docutils literal"&gt;NamedTuple&lt;/tt&gt;
and a functional style of programming.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="need-explicitly-to-convert-iterator-to-list-using-list"&gt;
&lt;h2&gt;10. Need explicitly to convert iterator to list using &lt;tt class="docutils literal"&gt;list()&lt;/tt&gt;&lt;/h2&gt;
&lt;p&gt;This is nonsense. What if the iterator is a sequence of pairs that
should be converted to a mapping with &lt;tt class="docutils literal"&gt;dict()&lt;/tt&gt;?&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="usurping-the-id-name"&gt;
&lt;h2&gt;11. Usurping the &lt;tt class="docutils literal"&gt;id()&lt;/tt&gt; name&lt;/h2&gt;
&lt;p&gt;Don't get this. The &lt;tt class="docutils literal"&gt;print()&lt;/tt&gt; name is also usurped by built-ins.
There are a dozen built-in function names that usurp other names one might want to use.
And all those keywords!  The name &lt;tt class="docutils literal"&gt;class&lt;/tt&gt; and &lt;tt class="docutils literal"&gt;def&lt;/tt&gt; and &lt;tt class="docutils literal"&gt;return&lt;/tt&gt; are all usurped
by keywords.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="inner-method-may-mutate-referenced-objects-in-the-closure-but-may-not-mutate-primitive-values-therein"&gt;
&lt;h2&gt;12. Inner method may mutate referenced objects in the closure but may not mutate primitive values therein&lt;/h2&gt;
&lt;p&gt;Primitives can't be mutated.&lt;/p&gt;
&lt;p&gt;Referenced objects can &lt;strong&gt;always&lt;/strong&gt; be mutated.&lt;/p&gt;
&lt;p&gt;It doesn't require an &amp;quot;inner&amp;quot; method. It's true for every function and method at all levels.&lt;/p&gt;
&lt;p&gt;I'm guessing the idea of mutable vs. immutable objects could be a quirk.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="kludgy-main-invocation"&gt;
&lt;h2&gt;13. Kludgy &lt;tt class="docutils literal"&gt;main()&lt;/tt&gt; invocation&lt;/h2&gt;
&lt;p&gt;Kludge? Really?  I guess they've never seen Java.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="disconcerting-lack-of-type-information-in-the-holdover-documentation-from-the-p2-days"&gt;
&lt;h2&gt;14. Disconcerting lack of type information in the holdover documentation from the P2 days&lt;/h2&gt;
&lt;p&gt;It's often helpful to provide an example of a documentation gap where
type information is totally missing (or is only present in a stubs file where it's not
automatically included by Sphinx). While I haven't seen any examples of missing type information in
Python or standard library documentation, that doesn't mean much. I only write books
about Python, I don't actually help maintain it.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="summary"&gt;
&lt;h2&gt;Summary&lt;/h2&gt;
&lt;p&gt;There's a good point:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;Declaration of instance attributes is their definitions in &lt;tt class="docutils literal"&gt;__init__()&lt;/tt&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The reset is a mixture of&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;too vague to comment on,&lt;/li&gt;
&lt;li&gt;it's not clear what would be better, and&lt;/li&gt;
&lt;li&gt;wrong.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Mostly the former. Few of the latter. (#10 seems to be the stand-out for wrong.)&lt;/p&gt;
&lt;p&gt;It's important to think about these things when learning a language.
Some discussion of alterantives from other languages would make these points a lot
easier to interpret and understand.&lt;/p&gt;
&lt;p&gt;However, it's also important to understand why soem things are present in a language.
It's important to look a little more deeply at the language rules -- perhaps read the
relevant PEP's -- to see what alternatives have been proposed and discarded.&lt;/p&gt;
&lt;p&gt;In most cases, decisions aren't arbitrary, but reflect deeper considerations on the underlying
semantics of the language and the implementation details of the compiler and/or run-time.&lt;/p&gt;
&lt;/div&gt;
</content><category term="Python"></category><category term="community"></category><category term="quirks"></category></entry><entry><title>Python Quirks that aren't very quirky</title><link href="https://slott56.github.io/2023-08-01-python_quirks_that_arent_very_quirky.html" rel="alternate"></link><published>2023-08-01T09:00:00-04:00</published><updated>2023-08-01T09:00:00-04:00</updated><author><name>S.Lott</name></author><id>tag:slott56.github.io,2023-08-01:/2023-08-01-python_quirks_that_arent_very_quirky.html</id><summary type="html">&lt;p&gt;See &lt;a class="reference external" href="https://writing.peercy.net/p/python-quirks"&gt;https://writing.peercy.net/p/python-quirks&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Superficially, most of these are true.&lt;/p&gt;
&lt;p&gt;Looking a little more deeply, most of them are also presented in a somewhat misleading way.
A few set up a good punch-line. The &lt;strong&gt;Inheritance&lt;/strong&gt; one, for example, is funny.&lt;/p&gt;
&lt;p&gt;If the point is to force a …&lt;/p&gt;</summary><content type="html">&lt;p&gt;See &lt;a class="reference external" href="https://writing.peercy.net/p/python-quirks"&gt;https://writing.peercy.net/p/python-quirks&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Superficially, most of these are true.&lt;/p&gt;
&lt;p&gt;Looking a little more deeply, most of them are also presented in a somewhat misleading way.
A few set up a good punch-line. The &lt;strong&gt;Inheritance&lt;/strong&gt; one, for example, is funny.&lt;/p&gt;
&lt;p&gt;If the point is to force a deeper investigation, I think the piece might not be helpful.
I know too many people who would look at this list and say &amp;quot;See, Python is as bad as JavaScript.&amp;quot;
Or &amp;quot;That's why I only use perl.&amp;quot;
These are the sort of folks won't actually refer to the Python language reference manual to see what's going on.&lt;/p&gt;
&lt;p&gt;One of these &lt;strong&gt;is&lt;/strong&gt; a legitimate quirk.
The rest involve a little bit of &amp;quot;don't look at the man behind the curtain&amp;quot; mixed with &amp;quot;don't read the documentation.&amp;quot;&lt;/p&gt;
&lt;p&gt;To ease my own mental anguish, I'll include a slightly deeper dive into these language feaures.&lt;/p&gt;
&lt;ol class="arabic"&gt;
&lt;li&gt;&lt;p class="first"&gt;Generators.&lt;/p&gt;
&lt;p&gt;&lt;tt class="docutils literal"&gt;sorted()&lt;/tt&gt; creates a new list from the argument value. It's not a generator.
Comparing the resulting list to the argument is unsurprising.&lt;/p&gt;
&lt;p&gt;&lt;tt class="docutils literal"&gt;reversed()&lt;/tt&gt; doesn't create a list. It is a generator. Since it can only
be used once, one use of the generator &lt;tt class="docutils literal"&gt;y&lt;/tt&gt; has a sequence of values.
The other use of the generator &lt;tt class="docutils literal"&gt;y&lt;/tt&gt; has no values.&lt;/p&gt;
&lt;p&gt;I suppose the single-use-of-a-generator featrure could be called a quirk.
Except it's well-documented, so, I'd argue that this simply exposes a language feature.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p class="first"&gt;References.&lt;/p&gt;
&lt;p&gt;The example fails to show how &lt;tt class="docutils literal"&gt;a&lt;/tt&gt; was created. It's not obvious
how the reused reference to a sublist was propogated throughout
the list.&lt;/p&gt;
&lt;p&gt;Missing:&lt;/p&gt;
&lt;pre class="literal-block"&gt;
a = [[0]] * 5
&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p class="first"&gt;Assignment.&lt;/p&gt;
&lt;p&gt;Not sure what the point of this is.
It doesn't even seem quirky.&lt;/p&gt;
&lt;p&gt;I guess they're astonished they can use something other than a trivial
variable in a &lt;tt class="docutils literal"&gt;for&lt;/tt&gt; statement.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p class="first"&gt;Closures.&lt;/p&gt;
&lt;p&gt;My guess on this is they were hoping the &lt;tt class="docutils literal"&gt;i&lt;/tt&gt; variable would not be a single variable;
instead, a fresh, new variable would be created by the generator expression.
Perhaps other languages do this, and manufacture fresh, new variable bindings.&lt;/p&gt;
&lt;p&gt;Python has a (relatively) simple rule for variables: Local, Enclosing, Global, and Built-in.
There's no closure rule to create new variables. There are many good tutorials on the LEGB rule.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p class="first"&gt;Inheritance.  This one is kind of funny.&lt;/p&gt;
&lt;p&gt;It's also, on reflection, unsurprising.
The &lt;tt class="docutils literal"&gt;type&lt;/tt&gt; type -- like all types -- is an &lt;tt class="docutils literal"&gt;object&lt;/tt&gt;. Mostly because almost everything is an object.
The &lt;tt class="docutils literal"&gt;object&lt;/tt&gt; type -- like all types -- is a &lt;tt class="docutils literal"&gt;type&lt;/tt&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p class="first"&gt;Operator Chaining.&lt;/p&gt;
&lt;p&gt;This isn't a quirk at all.
This seems to be an exploration of precedence rules among operators.
It seems to be a matter of definition among &lt;tt class="docutils literal"&gt;==&lt;/tt&gt;, &lt;tt class="docutils literal"&gt;and&lt;/tt&gt;, and &lt;tt class="docutils literal"&gt;in&lt;/tt&gt; operators.&lt;/p&gt;
&lt;p&gt;Also, it's not clear what &amp;quot;chaining&amp;quot; means here.
If &lt;tt class="docutils literal"&gt;1 + 2 + 3&lt;/tt&gt; is what they mean by &amp;quot;operator chaining&amp;quot;, then I think that may be the root
cause of the confusion. These are all binary operators with intermediate results.
Perhaps it can help to think of implicit ()'s around each binary operation.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p class="first"&gt;Identity.&lt;/p&gt;
&lt;p&gt;This is an optimization in the CPython interpreter to pre-allocate some integers.
This is a proper quirk. I'm glad it's on this list.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p class="first"&gt;NFKC Normalization.&lt;/p&gt;
&lt;p&gt;See &lt;a class="reference external" href="https://unicode.org/reports/tr15/"&gt;https://unicode.org/reports/tr15/&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;This isn't Python. This is Unicode.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p class="first"&gt;Default Arguments.&lt;/p&gt;
&lt;p&gt;This is pretty well-known. Some newbies discover it, and then
re-read the documentation that describes why this happens, and say &amp;quot;makes sense.&amp;quot;
Here's the rule: &lt;strong&gt;The mutable object is only created once; it's shared.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Most linters warn that this feature may not be doing what folks think it's doing.&lt;/p&gt;
&lt;p&gt;This appears in many other places. For example, default values for fields of
dataclasses cannot be mutable objects, because they'd be shared.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p class="first"&gt;Whatever This Is.&lt;/p&gt;
&lt;p&gt;This isn't a quirk, it's a bug. It was fixed in Python 3.11, though. So it's much less interesting.
It remains a known bug until Python 3.10 end-of-life, 04 Oct 2026.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p class="first"&gt;Python 2.&lt;/p&gt;
&lt;p&gt;Python 2 has been at end-of-life since 01 Jan 2020.
These kinds of things ceased to be interesting on that date.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;In summary, and in conclusion, the identity of small integers is a legitimate quirk.
I like it. The inheritance is funny. I like that, too.&lt;/p&gt;
</content><category term="Python"></category><category term="community"></category><category term="quirks"></category></entry><entry><title>Two Problems with Python</title><link href="https://slott56.github.io/2023-07-25-two_problems_with_python.html" rel="alternate"></link><published>2023-07-25T09:00:00-04:00</published><updated>2023-07-25T09:00:00-04:00</updated><author><name>S.Lott</name></author><id>tag:slott56.github.io,2023-07-25:/2023-07-25-two_problems_with_python.html</id><summary type="html">&lt;p&gt;I want to call out two huge problems with Python.
I'm not the first to point these out, but they've been bothering me for a while.&lt;/p&gt;
&lt;ol class="arabic simple"&gt;
&lt;li&gt;&lt;a class="reference internal" href="#surprising-changes"&gt;Surprising Changes&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="reference internal" href="#dependency-hell"&gt;Dependency Hell&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;I've provided them here to save folks from repeating these.
They're now officially &amp;quot;known&amp;quot; and there's no point in repeating …&lt;/p&gt;</summary><content type="html">&lt;p&gt;I want to call out two huge problems with Python.
I'm not the first to point these out, but they've been bothering me for a while.&lt;/p&gt;
&lt;ol class="arabic simple"&gt;
&lt;li&gt;&lt;a class="reference internal" href="#surprising-changes"&gt;Surprising Changes&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="reference internal" href="#dependency-hell"&gt;Dependency Hell&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;I've provided them here to save folks from repeating these.
They're now officially &amp;quot;known&amp;quot; and there's no point in repeating this again.
Write your blog posts about something else, please.&lt;/p&gt;
&lt;div class="section" id="surprising-changes"&gt;
&lt;h2&gt;Surprising Changes&lt;/h2&gt;
&lt;p&gt;Every language and library has changes. That's part of normal innovation and
evolution of the language.&lt;/p&gt;
&lt;p&gt;Some changes, however, were not communicated to me, personnally, and are therefore
suprising, which makes them bad. Really bad.&lt;/p&gt;
&lt;p&gt;Let's focus on linter tools as an example. Here's the scenario.&lt;/p&gt;
&lt;ol class="arabic simple"&gt;
&lt;li&gt;I have a code base. It's good. 100% compliant.&lt;/li&gt;
&lt;li&gt;I ugprade the linter.&lt;/li&gt;
&lt;li&gt;A new error is flagged. This was not an error before but &lt;strong&gt;somehow&lt;/strong&gt; (big eyeroll) it's an error now.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;This is a surprising change. No one told me.&lt;/p&gt;
&lt;p&gt;The code &lt;em&gt;is&lt;/em&gt; sketchy. It could be seen as ambiguous. &lt;strong&gt;Even though it passes all the unit tests.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Someone else may have learned a lesson about sketchy code, and embodied that lesson in the linter.
But they didn't tell me.&lt;/p&gt;
&lt;p&gt;Python had a surprise change, and the mere presence of a surprise means one thing:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Python is useless&lt;/strong&gt;.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="dependency-hell"&gt;
&lt;h2&gt;Dependency Hell&lt;/h2&gt;
&lt;p&gt;Every application has dependencies. That's part of building a language
in a rich ecosystem with a lot of useful packages.&lt;/p&gt;
&lt;p&gt;Some changes to these packages, while well-intentioned, can break a dependency with another package.
Packages have inter-dependencies, which I find &lt;strong&gt;impossible&lt;/strong&gt; to manage.&lt;/p&gt;
&lt;p&gt;Here's the scenario.&lt;/p&gt;
&lt;ol class="arabic simple"&gt;
&lt;li&gt;I have a code base. It's good. 100% tests pass. Installs perfectly on all supported platforms.&lt;/li&gt;
&lt;li&gt;Two packages, &lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;p==3.14&lt;/span&gt;&lt;/tt&gt; and &lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;q==2.78&lt;/span&gt;&lt;/tt&gt; both depend on &lt;tt class="docutils literal"&gt;x&lt;/tt&gt; version 1.1&lt;/li&gt;
&lt;li&gt;The authors of &lt;tt class="docutils literal"&gt;p&lt;/tt&gt; updated to &lt;tt class="docutils literal"&gt;4.0&lt;/tt&gt; and switched their dependency to to &lt;tt class="docutils literal"&gt;x&lt;/tt&gt; version 2.0. The authors of &lt;tt class="docutils literal"&gt;q&lt;/tt&gt; did not switch.&lt;/li&gt;
&lt;li&gt;If I include &lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;p==4.0&lt;/span&gt;&lt;/tt&gt; and &lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;x==2.0&lt;/span&gt;&lt;/tt&gt; the &lt;tt class="docutils literal"&gt;q&lt;/tt&gt; package breaks. I can't upgrade &lt;tt class="docutils literal"&gt;p&lt;/tt&gt;.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Dependency Hell.  Unresolvable Conflicts.&lt;/p&gt;
&lt;p&gt;Any combination of packages will have numerous internal dependencies.
The mere presence of these dependencies means one thing:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Python is useless&lt;/strong&gt;.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="summary"&gt;
&lt;h2&gt;Summary&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Python is useless&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;I cannot tolerate innovation.&lt;/p&gt;
&lt;p&gt;If someone learns something and changes a linter, that's innovation: it breaks my code; I don't want it.&lt;/p&gt;
&lt;p&gt;If someone creates a new version of an open-source package, that's innovation: it breaks my code; I don't want it.&lt;/p&gt;
&lt;p&gt;This isn't to say that innovation is &lt;strong&gt;bad&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;Innovation is &lt;strong&gt;good&lt;/strong&gt;. When it occurs very slowly, and I'm able to personally vet each individual change for impact on my project(s).&lt;/p&gt;
&lt;p&gt;The idea that every single open source package is innovating and learning at their own unique tempo
is insanity. It makes Python &lt;strong&gt;useless&lt;/strong&gt;.&lt;/p&gt;
&lt;/div&gt;
</content><category term="Python"></category><category term="community"></category></entry><entry><title>An Implementation of Annotated Types</title><link href="https://slott56.github.io/2023-07-11-an_implementation_of_annotated_types.html" rel="alternate"></link><published>2023-07-11T09:00:00-04:00</published><updated>2023-07-11T09:00:00-04:00</updated><author><name>S.Lott</name></author><id>tag:slott56.github.io,2023-07-11:/2023-07-11-an_implementation_of_annotated_types.html</id><summary type="html">&lt;p&gt;The &lt;tt class="docutils literal"&gt;typing&lt;/tt&gt; module includes the mysterious-looking &lt;tt class="docutils literal"&gt;Annotated&lt;/tt&gt; type hint.
See &lt;a class="reference external" href="https://docs.python.org/3/library/typing.html#typing.Annotated"&gt;https://docs.python.org/3/library/typing.html#typing.Annotated&lt;/a&gt; for details.&lt;/p&gt;
&lt;p&gt;&lt;a class="reference internal" href="#what-does-this-do"&gt;What does this do?&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a class="reference internal" href="#why-do-i-need-it"&gt;Why do I need it?&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a class="reference internal" href="#where-can-i-see-examples"&gt;Where can I see examples?&lt;/a&gt;&lt;/p&gt;
&lt;div class="section" id="what-does-this-do"&gt;
&lt;h2&gt;What does this do?&lt;/h2&gt;
&lt;p&gt;The &lt;tt class="docutils literal"&gt;Annotated&lt;/tt&gt; type hint lets us append &amp;quot;details&amp;quot; to a …&lt;/p&gt;&lt;/div&gt;</summary><content type="html">&lt;p&gt;The &lt;tt class="docutils literal"&gt;typing&lt;/tt&gt; module includes the mysterious-looking &lt;tt class="docutils literal"&gt;Annotated&lt;/tt&gt; type hint.
See &lt;a class="reference external" href="https://docs.python.org/3/library/typing.html#typing.Annotated"&gt;https://docs.python.org/3/library/typing.html#typing.Annotated&lt;/a&gt; for details.&lt;/p&gt;
&lt;p&gt;&lt;a class="reference internal" href="#what-does-this-do"&gt;What does this do?&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a class="reference internal" href="#why-do-i-need-it"&gt;Why do I need it?&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a class="reference internal" href="#where-can-i-see-examples"&gt;Where can I see examples?&lt;/a&gt;&lt;/p&gt;
&lt;div class="section" id="what-does-this-do"&gt;
&lt;h2&gt;What does this do?&lt;/h2&gt;
&lt;p&gt;The &lt;tt class="docutils literal"&gt;Annotated&lt;/tt&gt; type hint lets us append &amp;quot;details&amp;quot; to a type.&lt;/p&gt;
&lt;p&gt;It might look like this&lt;/p&gt;
&lt;pre class="literal-block"&gt;
x: Annotated[int, MustBePrime()]
&lt;/pre&gt;
&lt;p&gt;The annotated type has one origin type (which must be first) and a sequence of objects. Presumably, they are &amp;quot;annotations&amp;quot; of some kind.
They can be anything. We can do a lot with them; we'll start with using them to narrow the domain of values.&lt;/p&gt;
&lt;p&gt;The core &lt;tt class="docutils literal"&gt;x: int&lt;/tt&gt; provides a large domain of possible values. Python's ints can be immense numbers, easily filling memory with digits.&lt;/p&gt;
&lt;p&gt;The &lt;tt class="docutils literal"&gt;MustBePrime&lt;/tt&gt; class is the kind of thing that might be used to narrow the domain of allowed
values to prime numbers.&lt;/p&gt;
&lt;div class="section" id="when-does-this-value-checking-happen"&gt;
&lt;h3&gt;When does this value checking happen?&lt;/h3&gt;
&lt;p&gt;I'm glad you asked.&lt;/p&gt;
&lt;p&gt;Use of annotated types is &lt;strong&gt;not&lt;/strong&gt; part of the Python run-time. Annotated type arguments are essentially ignored.
The origin type is used by tools like &lt;strong&gt;mypy&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;Any further use of annotations is a thing your application or tool-chain will need to do.&lt;/p&gt;
&lt;p&gt;An application can see the annotations for an object using the &lt;tt class="docutils literal"&gt;__annotations__&lt;/tt&gt; special attribute number,
or use the &lt;tt class="docutils literal"&gt;typing.get_type_hints()&lt;/tt&gt; function.&lt;/p&gt;
&lt;pre class="literal-block"&gt;
&amp;gt;&amp;gt;&amp;gt; from typing import Annotated, get_type_hints
&amp;gt;&amp;gt;&amp;gt; class MustBePrime:
...     pass
...

&amp;gt;&amp;gt;&amp;gt; class SomeApp:
...     x: Annotated[int, MustBePrime()]
...

&amp;gt;&amp;gt;&amp;gt; get_type_hints(SomeApp)
{'x': &amp;lt;class 'int'&amp;gt;}
&amp;gt;&amp;gt;&amp;gt; get_type_hints(SomeApp, include_extras=True)
{'x': typing.Annotated[int, &amp;lt;__main__.MustBePrime object at 0x7fde259a7be0&amp;gt;]}
&amp;gt;&amp;gt;&amp;gt; get_type_hints(SomeApp, include_extras=True)['x']
typing.Annotated[int, &amp;lt;__main__.MustBePrime object at 0x7fde259a7be0&amp;gt;]
&lt;/pre&gt;
&lt;p&gt;We can see the annotated type hint for &lt;tt class="docutils literal"&gt;x&lt;/tt&gt;.&lt;/p&gt;
&lt;p&gt;This means, our application is free to &amp;quot;apply&amp;quot; the annotation in some way.&lt;/p&gt;
&lt;p&gt;&amp;quot;&lt;strong&gt;Whoa!  That's vague&lt;/strong&gt;,&amp;quot; you say. &amp;quot;There are no specific rules for annotated types?&amp;quot;&lt;/p&gt;
&lt;p&gt;I agree.&lt;/p&gt;
&lt;p&gt;The details are up to your app.  Seriously.  Define them in a way that makes sense.&lt;/p&gt;
&lt;p&gt;Maybe you want your app looks like this:&lt;/p&gt;
&lt;pre class="literal-block"&gt;
&amp;gt;&amp;gt;&amp;gt; class SomeApp:
....    x: Annotated[int, MustBePrime()]
...     def __init__(self, arg_value: int) -&amp;gt; None:
...         self.x = arg_value
...
&lt;/pre&gt;
&lt;p&gt;And you've got a use case in mind...&lt;/p&gt;
&lt;pre class="literal-block"&gt;
&amp;gt;&amp;gt;&amp;gt; sa = SomeApp(42)
Traceback (most recent call last):
   ...
ValueError: value 42 is not prime
&lt;/pre&gt;
&lt;p&gt;The idea is that this specific app has an associated collection of annotations that are used
during &lt;tt class="docutils literal"&gt;__init__()&lt;/tt&gt; processing to further validate the supplied values.&lt;/p&gt;
&lt;p&gt;The code to this is &lt;em&gt;clearly&lt;/em&gt; part of &lt;tt class="docutils literal"&gt;SomeApp&lt;/tt&gt; -- maybe a metaclass, maybe a superclass -- but
clearly part of the app.&lt;/p&gt;
&lt;p&gt;And the app will use the annotation as a kind of &amp;quot;plug-in&amp;quot; or &amp;quot;extension&amp;quot; or &lt;strong&gt;Strategy&lt;/strong&gt; design pattern to do some additional processing at some point.&lt;/p&gt;
&lt;p&gt;Our use case was part of &lt;tt class="docutils literal"&gt;__init__()&lt;/tt&gt; processing.  What does this look like?&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="an-example-app"&gt;
&lt;h3&gt;An example app&lt;/h3&gt;
&lt;p&gt;We'll avoid metaclasses, and pretend that Annotated types are checked by an
explict call to a method of the class.
Let's say a superclass, named &lt;tt class="docutils literal"&gt;RuleCheck&lt;/tt&gt; has a method that must be
called at the end of &lt;tt class="docutils literal"&gt;__init__()&lt;/tt&gt; to check compliance with annotations.&lt;/p&gt;
&lt;pre class="literal-block"&gt;
class SomeApp(RuleCheck):
    x: Annotated[int, MustBePrime()]

    def __init__(self, arg_value: int) -&amp;gt; None:
        self.x = arg_value
        self.check()
&lt;/pre&gt;
&lt;p&gt;The idea here is that the class-level hints are carefully defined.&lt;/p&gt;
&lt;p&gt;The &lt;tt class="docutils literal"&gt;__init__()&lt;/tt&gt; merely slaps any old value in there.&lt;/p&gt;
&lt;p&gt;And the &lt;tt class="docutils literal"&gt;self.check()&lt;/tt&gt; then assures that all hints are actually true for the supplied
values.&lt;/p&gt;
&lt;p&gt;This means it will &amp;quot;apply&amp;quot; the annotation to the given value. In this case,
it will either allow the value silently or raise an exception if there's a problem.&lt;/p&gt;
&lt;p&gt;Here's the &lt;tt class="docutils literal"&gt;RuleCheck&lt;/tt&gt; class.&lt;/p&gt;
&lt;pre class="literal-block"&gt;
from typing import Annotated, get_type_hints, get_args

class RuleCheck:
    def check(self) -&amp;gt; None:
        vars = get_type_hints(self.__class__, include_extras=True)
        for name in vars:
            match vars[name]:
                case Annotated:
                   base, *rules = get_args(vars[name])
                   for rule in rules:
                       rule(getattr(self, name))
&lt;/pre&gt;
&lt;p&gt;Each annotated variable has the arguments to the annotation
retrieved with &lt;tt class="docutils literal"&gt;typing.get_args()&lt;/tt&gt;.
Each of these annotations must be a callable object of some kind
that can be applied to the attribute's value.&lt;/p&gt;
&lt;p&gt;We leave the implementation of &lt;tt class="docutils literal"&gt;MustBePrime&lt;/tt&gt; as an exercise for the reader.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="section" id="why-do-i-need-it"&gt;
&lt;h2&gt;Why do I need it?&lt;/h2&gt;
&lt;p&gt;You need it in a bunch of cases. Here are some ideas.&lt;/p&gt;
&lt;ol class="arabic simple"&gt;
&lt;li&gt;Type domain narrowing. We used &amp;quot;prime&amp;quot; as an example. You might want to use positive values, or
values in a range. Or other properties that you'd like to make part of a type.&lt;/li&gt;
&lt;li&gt;Documentation. You can imagine &lt;tt class="docutils literal"&gt;x: Annotated[str, &lt;span class="pre"&gt;title(&amp;quot;Some&lt;/span&gt; Descriptive &lt;span class="pre"&gt;Information&amp;quot;),&lt;/span&gt; &lt;span class="pre"&gt;Positive()]&lt;/span&gt;&lt;/tt&gt;.
Since the documentation is not a comment or other ephermeral source text, you can use this
to create a formal Schema for a class. Thing JSONSchema. (Or XSD if you're old.)
You could use the title to beef up the exception messages, for example.&lt;/li&gt;
&lt;li&gt;Other Processing. Let's not get crazy, but the following is possible.&lt;/li&gt;
&lt;/ol&gt;
&lt;pre class="literal-block"&gt;
x: Annotated[float, Title(&amp;quot;Independent&amp;quot;), Range(0, 10)]
y: Annotated[float, DerivedFrom(&amp;quot;x&amp;quot;), Function(lambda x: 2*x-1)]
&lt;/pre&gt;
&lt;p&gt;The idea is that we might build a class where any change to &lt;tt class="docutils literal"&gt;x&lt;/tt&gt; computes a value
for &lt;tt class="docutils literal"&gt;y&lt;/tt&gt; based on the annotation; and the value is cached as an attribute
value, not a &lt;tt class="docutils literal"&gt;&amp;#64;property&lt;/tt&gt; which is always recompued.&lt;/p&gt;
&lt;p&gt;(Yes, &lt;tt class="docutils literal"&gt;&amp;#64;cache&lt;/tt&gt; and &lt;tt class="docutils literal"&gt;&amp;#64;property&lt;/tt&gt; can do this. This isn't necessarily a &lt;strong&gt;great&lt;/strong&gt; idea.
But it's possible.)&lt;/p&gt;
&lt;div class="section" id="building-a-type-definition"&gt;
&lt;h3&gt;Building a type definition&lt;/h3&gt;
&lt;p&gt;Maybe we want this.&lt;/p&gt;
&lt;pre class="literal-block"&gt;
PosInt: TypeAlias = Annotated[int, MustBePositive()]
PrimePosInt: TypeAlias = Annotated[PosInt, MustBePrime()]
&lt;/pre&gt;
&lt;p&gt;We've built a complicated type on top of another complicated type.&lt;/p&gt;
&lt;p&gt;This permits us to -- for example -- improve the performance of &lt;tt class="docutils literal"&gt;MustBePositive&lt;/tt&gt; with an attendant speedup of other, related objects.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="file-parsing"&gt;
&lt;h3&gt;File Parsing&lt;/h3&gt;
&lt;p&gt;This is an edge case. But. It applies to the vast number of files processed by COBOL programs.&lt;/p&gt;
&lt;pre class="literal-block"&gt;
x: Annotated[str, Start(0), Length(5)]
y: Annotated[str, Start(5), Length(10)]
z: Annotated[Decimal, Start(15), Length(10), Scale(2)]
&lt;/pre&gt;
&lt;p&gt;We've provided the metadata for positions of the source data in a text document.
A file with a line like &lt;tt class="docutils literal"&gt;&amp;quot;ABCDEZYXWVUTSRQ0000001299&amp;quot;&lt;/tt&gt; could be parsed by a class
that leveraged the annotations to pluck values out of the source string.
It could apply conversion from mainframe encodings (&amp;quot;EBCDIC&amp;quot;) and do &lt;tt class="docutils literal"&gt;decimal&lt;/tt&gt; conversion.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="section" id="where-can-i-see-examples"&gt;
&lt;h2&gt;Where can I see examples?&lt;/h2&gt;
&lt;p&gt;I have two examples, right now.&lt;/p&gt;
&lt;p&gt;Pydantic v2 Annotated Validators: &lt;a class="reference external" href="https://docs.pydantic.dev/latest/usage/validators/#annotated-validators"&gt;https://docs.pydantic.dev/latest/usage/validators/#annotated-validators&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Wow is this cool.&lt;/p&gt;
&lt;p&gt;Also.&lt;/p&gt;
&lt;p&gt;TigerShark. &lt;a class="reference external" href="https://github.com/slott56/TigerShark"&gt;https://github.com/slott56/TigerShark&lt;/a&gt;  This is a pretty narrow problem domain.
But, the Annotated type hints were a &lt;em&gt;perfect&lt;/em&gt; solution to an ages-old problem.
The X12 messages have complex more-or-less hierarchical structure. Messages have Loops (that can repeat), Segments, and individual Data Elements.&lt;/p&gt;
&lt;p&gt;The definitions of the messages have complicated meta-data on size, encoding, data types,
optionality, etc., and etc.&lt;/p&gt;
&lt;p&gt;What we want is a top-level definition of a message that looks like this:&lt;/p&gt;
&lt;pre class="literal-block"&gt;
class MSG270(Message):
    &amp;quot;&amp;quot;&amp;quot;HIPAA Health Care Eligibility Inquiry X092A1-270&amp;quot;&amp;quot;&amp;quot;
    ItemIsa_Loop: TypeAlias = Annotated[ISA_LOOP, Title('Interchange Control Header'), Usage('R'), Position(1), Required(True)]
    isa_loop: Annotated[list[ItemIsa_Loop], MinItems(1)]
&lt;/pre&gt;
&lt;p&gt;The TypeAlias and Annotated type provide all the metadata for this message.&lt;/p&gt;
&lt;p&gt;Looking elsewhere in the message module, we find this...&lt;/p&gt;
&lt;pre class="literal-block"&gt;
class ISA_LOOP_ISA(Segment):
    &amp;quot;&amp;quot;&amp;quot;Interchange Control Header&amp;quot;&amp;quot;&amp;quot;
    _segment_name = 'ISA'

    isa01: Annotated[I01, Title('Authorization Information Qualifier'), Usage('R'), Position(1), Enumerated(*['00', '03'])]

    isa02: Annotated[I02, Title('Authorization Information'), Usage('R'), Position(2)]

    isa03: Annotated[I03, Title('Security Information Qualifier'), Usage('R'), Position(3), Enumerated(*['00', '01'])]
&lt;/pre&gt;
&lt;p&gt;Again, the elements are defined (entirely) by annotations.&lt;/p&gt;
&lt;p&gt;The base type? &lt;tt class="docutils literal"&gt;I01&lt;/tt&gt;?  A pool of common definitions.&lt;/p&gt;
&lt;pre class="literal-block"&gt;
I01: TypeAlias = Annotated[ID, MinLen(2), MaxLen(2)]
&lt;/pre&gt;
&lt;p&gt;But wait! That still depends on a more foundational definition, &lt;tt class="docutils literal"&gt;ID&lt;/tt&gt;.&lt;/p&gt;
&lt;pre class="literal-block"&gt;
ID: TypeAlias = str
&lt;/pre&gt;
&lt;p&gt;The idea of this is to map the type information to type aliases, so anyone
can follow the message definitions completely. The annotations are defined
formally by the X12/EDI standards; the mapping to Python is through these
foundational type aliases for Python types.&lt;/p&gt;
&lt;p&gt;Also see &lt;a class="reference external" href="https://pypi.org/project/TigerShark3/"&gt;https://pypi.org/project/TigerShark3/&lt;/a&gt; if you have the urge to install it.&lt;/p&gt;
&lt;/div&gt;
</content><category term="Python"></category><category term="patterns"></category><category term="type-hints"></category></entry><entry><title>Behave Tests and Fixtures</title><link href="https://slott56.github.io/2023_01_27-behave_tests_and_fixtures.html" rel="alternate"></link><published>2023-01-27T08:00:00-05:00</published><updated>2023-01-27T08:00:00-05:00</updated><author><name>S.Lott</name></author><id>tag:slott56.github.io,2023-01-27:/2023_01_27-behave_tests_and_fixtures.html</id><summary type="html">&lt;div class="section" id="bluf"&gt;
&lt;h2&gt;BLUF&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Behave&lt;/strong&gt; fixtures totally rock for testing
complex applications.&lt;/p&gt;
&lt;p&gt;I had been doing them wrong. Doing them right is simpler.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="history"&gt;
&lt;h2&gt;History&lt;/h2&gt;
&lt;p&gt;I'm a fan of the Gherkin language for specifying
the behavior of software.&lt;/p&gt;
&lt;pre class="literal-block"&gt;
Scenario: Works for Me

Given a configuration
When a request is made
Then the response can …&lt;/pre&gt;&lt;/div&gt;</summary><content type="html">&lt;div class="section" id="bluf"&gt;
&lt;h2&gt;BLUF&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Behave&lt;/strong&gt; fixtures totally rock for testing
complex applications.&lt;/p&gt;
&lt;p&gt;I had been doing them wrong. Doing them right is simpler.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="history"&gt;
&lt;h2&gt;History&lt;/h2&gt;
&lt;p&gt;I'm a fan of the Gherkin language for specifying
the behavior of software.&lt;/p&gt;
&lt;pre class="literal-block"&gt;
Scenario: Works for Me

Given a configuration
When a request is made
Then the response can be evaluated.
&lt;/pre&gt;
&lt;p&gt;I love this.&lt;/p&gt;
&lt;p&gt;What I particularly love is the way &lt;strong&gt;Behave's&lt;/strong&gt; &lt;tt class="docutils literal"&gt;steps&lt;/tt&gt; package
provide implementations for the individual steps of the scenario.&lt;/p&gt;
&lt;p&gt;The steps can be organized around technical needs,
where the features are organized around the user's experience
when operating the software.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="the-non-fixture-approach"&gt;
&lt;h2&gt;The Non-Fixture Approach&lt;/h2&gt;
&lt;p&gt;For a long time, I used the &lt;tt class="docutils literal"&gt;Given&lt;/tt&gt; step and an &lt;tt class="docutils literal"&gt;after_scenario()&lt;/tt&gt;
function in &lt;strong&gt;Behave's&lt;/strong&gt; environment module to create and destroy fixtures.&lt;/p&gt;
&lt;pre class="literal-block"&gt;
Scenario: Test with Mock API

Given a server running on http://127.0.0.1:8000
And the server has the resource requested
When a client makes some random request or other
Then the response is a tidy bit of data the user just loves
&lt;/pre&gt;
&lt;p&gt;The &lt;tt class="docutils literal"&gt;Given&lt;/tt&gt; step would seed the context with details used
to configure a tiny, specialized service built with
Python's &lt;tt class="docutils literal"&gt;http.server&lt;/tt&gt; module. This separate subprocess would provide
an appropriate response for this scenario.&lt;/p&gt;
&lt;p&gt;The mock server srequired creating a request handling class hierarchy
with reusable and extensible choices for the
various scenarios and features.
Often only one or two paths would be handled, since that's all
a scenario needed.&lt;/p&gt;
&lt;p&gt;The context parameters were turned into command-line options.
The mini server was started by the &lt;tt class="docutils literal"&gt;When&lt;/tt&gt; step and stopped
(eventually) after the scenario.&lt;/p&gt;
&lt;p&gt;This was workable. But. Ultimately. Dumb.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="fixtures"&gt;
&lt;h2&gt;Fixtures&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Behave&lt;/strong&gt; has a much, much better way to configure
and manage fixtures. This is great for tests that
databases or RESTful API servers or other, separate processes
to collaborate with.&lt;/p&gt;
&lt;p&gt;Fixtures.&lt;/p&gt;
&lt;pre class="literal-block"&gt;
&amp;#64;fixture.the_mock_server
Scenario: Test with Mock API

Given a server running on http://127.0.0.1:8000
And the server has the resource requested
When a client makes some random request or other
Then the response is a tidy bit of data the user just loves
&lt;/pre&gt;
&lt;p&gt;There's one change to the scenario -- a tag with &lt;tt class="docutils literal"&gt;&amp;#64;fixture.&lt;/tt&gt; to positively identify
a fixture required for this scenario to make sense.&lt;/p&gt;
&lt;p&gt;When reviewing the Gherkin with users, the &lt;tt class="docutils literal"&gt;&amp;#64;fixture&lt;/tt&gt; tag
is easy to explain. There are often other tags throughout
the scenarios. A &lt;tt class="docutils literal"&gt;&amp;#64;slow&lt;/tt&gt; tag, for example, might be used for those
scenarios that involve throttling or timeouts. A &lt;tt class="docutils literal"&gt;&amp;#64;future&lt;/tt&gt; tag
for those options that aren't required but can be tested
to observe development progress. For one project I had a &lt;tt class="docutils literal"&gt;&amp;#64;core&lt;/tt&gt; tag
that recapitulated the examples in the documentation --- these &lt;strong&gt;had&lt;/strong&gt; to work
exactly as shown.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="infrastructure"&gt;
&lt;h2&gt;Infrastructure&lt;/h2&gt;
&lt;p&gt;The fixture infrastructure has three parts.&lt;/p&gt;
&lt;ol class="arabic simple"&gt;
&lt;li&gt;Our specific fixture-managing generator function. This will create the fixture, yield something, and then destroy the fixture. This precisely parallels the way &lt;strong&gt;pytest&lt;/strong&gt; fixtures work.&lt;/li&gt;
&lt;li&gt;A &lt;tt class="docutils literal"&gt;before_tag()&lt;/tt&gt; function in the environment to look for the tags and do any setup or logging required.&lt;/li&gt;
&lt;li&gt;The fixture itself. This is our specialized test-case server based on &lt;tt class="docutils literal"&gt;http.server&lt;/tt&gt;. It still uses a configuration file or command-line options -- or both -- to define some behavior required for the scenario.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;What happens, then, is this.&lt;/p&gt;
&lt;ol class="arabic simple"&gt;
&lt;li&gt;The &lt;tt class="docutils literal"&gt;before_tag()&lt;/tt&gt; function is evaluated for every tag on every step.
If a tag starts with &lt;tt class="docutils literal"&gt;&amp;quot;fixture.&amp;quot;&lt;/tt&gt; then, something special needs to be done.&lt;/li&gt;
&lt;li&gt;The &lt;tt class="docutils literal"&gt;before_tag()&lt;/tt&gt; function will evaluate the &lt;tt class="docutils literal"&gt;behave.use_fixture()&lt;/tt&gt; function to inject
our specific fixture-managing generator function into the step processing.&lt;/li&gt;
&lt;li&gt;The fixture will be created (and destroyed) as part of the scenario's execution.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;(If you need details, see &lt;a class="reference external" href="https://behave.readthedocs.io/en/stable/fixtures.html#fixture-cleanup-points"&gt;https://behave.readthedocs.io/en/stable/fixtures.html#fixture-cleanup-points&lt;/a&gt;)&lt;/p&gt;
&lt;p&gt;The mapping from &lt;tt class="docutils literal"&gt;&amp;quot;fixture.this_special_api&amp;quot;&lt;/tt&gt; to
a generator function named &lt;tt class="docutils literal"&gt;this_special_api()&lt;/tt&gt; is kind
of trivial. So trivial that the examples in the &lt;strong&gt;Behave&lt;/strong&gt;
documentation suggest you look these up in a map in
the simplest possible way.&lt;/p&gt;
&lt;pre class="literal-block"&gt;
TAG_IMPLEMENTATIONS = {
    &amp;quot;the_mock_server&amp;quot;: server_fixture_generator,
    &amp;quot;the_other_server&amp;quot;: another_fixture_generator,
    &amp;quot;the_timeout_server&amp;quot;: the_timeout_server
}

def before_tag(context, tag):
    if tag.startswith(&amp;quot;fixture.&amp;quot;):
        _, name = tag.split('.')
        use_fixture(TAG_IMPLEMENTATIONS[name], context)
&lt;/pre&gt;
&lt;p&gt;There's a &lt;tt class="docutils literal"&gt;use_fixture_by_tag()&lt;/tt&gt; function that may be considered to be simpler
than my example.&lt;/p&gt;
&lt;p&gt;Now, we can add fixtures by writing a generator
function to create (and destroy) the fixture
and adding the new function to the &lt;tt class="docutils literal"&gt;TAG_IMPLEMENTATIONS&lt;/tt&gt; mapping.&lt;/p&gt;
&lt;p&gt;The fixture names are for users who might want to review
the scenarios. They're subject to the same kind of negotiation
the rest of the Gherkin terminology is. Sometimes, you'll
tweak the wording as the user's understanding (and needs)
evolve.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="cleanup"&gt;
&lt;h2&gt;Cleanup&lt;/h2&gt;
&lt;p&gt;When you have serious problems in your test implementation,
you'll have tiny cleanup issues.&lt;/p&gt;
&lt;p&gt;For example, if your step implementation code is broken,
the test can crash without having executed all
the steps you anticipated.&lt;/p&gt;
&lt;p&gt;This can mean a fixture isn't properly torn down.
It's a rare, but annoying thing to happen.&lt;/p&gt;
&lt;p&gt;See &lt;a class="reference external" href="https://behave.readthedocs.io/en/stable/fixtures.html#ensure-fixture-cleanups-with-fixture-setup-errors"&gt;https://behave.readthedocs.io/en/stable/fixtures.html#ensure-fixture-cleanups-with-fixture-setup-errors&lt;/a&gt; for
some solutions.&lt;/p&gt;
&lt;p&gt;I'm a fan of leaving information about the fixture in the context,
and using &lt;tt class="docutils literal"&gt;after_scenario()&lt;/tt&gt; or &lt;tt class="docutils literal"&gt;after_feature()&lt;/tt&gt; functions
to kill long-running process in the rare case that a step failed.&lt;/p&gt;
&lt;p&gt;The alternative, using &lt;tt class="docutils literal"&gt;add_cleanup()&lt;/tt&gt;, is -- perhaps -- nicer,
because it relies on a closure that doesn't clutter the
context with these little, technical overheads. (I find closures
a little awkward to debug, but, debugging is rarely needed
for this.)&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="books"&gt;
&lt;h2&gt;Books&lt;/h2&gt;
&lt;p&gt;Yes, this is for a book.
Stay tuned. Later this year.&lt;/p&gt;
&lt;/div&gt;
</content><category term="Python"></category><category term="books"></category><category term="software design"></category><category term="test driven development"></category></entry><entry><title>Testing RESTful web services in Django -- Tantalizingly Close.</title><link href="https://slott56.github.io/2008_08_13-testing_restful_web_services_in_django_tantalizingly_close.html" rel="alternate"></link><published>2008-08-13T10:10:00-04:00</published><updated>2008-08-13T10:10:00-04:00</updated><author><name>S.Lott</name></author><id>tag:slott56.github.io,2008-08-13:/2008_08_13-testing_restful_web_services_in_django_tantalizingly_close.html</id><summary type="html">&lt;p&gt;Here's what's great about &lt;a class="reference external" href="http://www.djangoproject.com"&gt;Django&lt;/a&gt;  coupled with the &lt;a class="reference external" href="http://code.google.com/p/django-rest-interface/"&gt;Django-REST Interface&lt;/a&gt; :  It's almost all model.  You define the model, write some tests.  Add the URL mappings, write some tests using the built-in Django client.&lt;/p&gt;
&lt;p&gt;We're almost there, but this doesn't work out perfectly.  To do complete tests, we have to either …&lt;/p&gt;</summary><content type="html">&lt;p&gt;Here's what's great about &lt;a class="reference external" href="http://www.djangoproject.com"&gt;Django&lt;/a&gt;  coupled with the &lt;a class="reference external" href="http://code.google.com/p/django-rest-interface/"&gt;Django-REST Interface&lt;/a&gt; :  It's almost all model.  You define the model, write some tests.  Add the URL mappings, write some tests using the built-in Django client.&lt;/p&gt;
&lt;p&gt;We're almost there, but this doesn't work out perfectly.  To do complete tests, we have to either subclass the Django Client to add &amp;quot;put&amp;quot; and &amp;quot;delete&amp;quot; or curry in methods for &amp;quot;put&amp;quot; and &amp;quot;delete&amp;quot;.  Then we can almost test our complete set of web services functions.&lt;/p&gt;
&lt;p&gt;At this point, the core of the application is -- well -- done.  It works, it handles the web services requests.  We can then start folding in HTML pages for the endlessly negotiated human interface.&lt;/p&gt;
&lt;p&gt;However, we're still not ready for deployment.&lt;/p&gt;
&lt;div class="section" id="authorization-differences"&gt;
&lt;h2&gt;Authorization Differences&lt;/h2&gt;
&lt;p&gt;First, we haven't really got a solid security model in place.  Sure, we can add &amp;#64;login_required decorators to any view functions.  But that doesn't really secure the REST interface at all.  That's where the going gets tough.&lt;/p&gt;
&lt;p&gt;The Django-REST Collection has an 'authentication' attribute that checks passwords.  It has an HttpDigestAuthentication class that handles more-secure password digests.  This looks perfect for web services.  But, it has two problems.&lt;/p&gt;
&lt;ol class="arabic simple"&gt;
&lt;li&gt;We don't have MD5 digests readily available.  Django uses SHA1 digests of password only, not an MD5 digest of username:realm:password.&lt;/li&gt;
&lt;li&gt;We can't easily test using digest authentication with the off-the-shelf Django test Client.  Not only does the test client lack Put and Delete, but it can't handle HTTP Digest authentication, either.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Sigh.  I thought we'd be done in &lt;a class="reference external" href="http://showmedo.com/videos/video?name=2000080&amp;amp;fromSeriesID=200"&gt;20 minutes&lt;/a&gt; .  Turns out, I have to actually do some work.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="adding-md5-digests"&gt;
&lt;h2&gt;Adding MD5 Digests&lt;/h2&gt;
&lt;p&gt;MD5 digests seem to work out best with the 'Profile' extension to the Django authorization application.  The model is delightfully simple, just a single CharField to hold the MD5 hexdigest of username:realm:password.&lt;/p&gt;
&lt;p&gt;One consequence is that we now have two password digests, the default SHA1 in the User model and our Web Services MD5 in the Profile extension.  This means that our page for password resets must have a view that sets both passwords.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="testing-complications"&gt;
&lt;h2&gt;Testing Complications&lt;/h2&gt;
&lt;p&gt;In the long run, we have to provide WS client libraries.  While the application is entirely RESTful, the marketplace expects an API library that they can install.  We have to provide Python, .NET and Java libraries to invoke our service.  This isn't very complex.&lt;/p&gt;
&lt;p&gt;For Python, it would be simplest to leverage the &lt;a class="reference external" href="http://docs.python.org/lib/module-urllib2.html"&gt;urllib2&lt;/a&gt;  package.   We can provide some classes which act as remote procedure call proxies; these classes have methods that invoke our REST services (GET, POST, PUT and DELETE) on various resources or collections.&lt;/p&gt;
&lt;p&gt;Something like the following:&lt;/p&gt;
&lt;pre class="literal-block"&gt;
class MyProxy( object ):
    def __init__( self, host, port, username, password, realm ):
        self.urlBase= &amp;quot;http://%s:%s&amp;quot; % ( host, port )
        # Build Handler to support HTTP Digest Authentication...
        digest_handler = urllib2.HTTPDigestAuthHandler()
        if username is not None:
            digest_handler.add_password(realm, self.urlBase, username, password)
        # Build Handler to support HTTP Basic Authentication...
        basic_handler = urllib2.HTTPBasicAuthHandler()
        if username is not None:
            basic_handler.add_password(realm, self.urlBase, username, password)
        # Build Handler to treat 201 as a normal response, not an exception...
        error_handler= RESTHTTPHandler()
        self.server = urllib2.build_opener(digest_handler,basic_handler,error_handler)
    def request( self, method, uri ):
        assert method in ( &amp;quot;GET&amp;quot;, &amp;quot;POST&amp;quot;, &amp;quot;PUT&amp;quot;, &amp;quot;DELETE&amp;quot; )
        data= urllib.urlencode( argDict )
        theReq= RESTRequest( method, self.urlBase + path, data )
        try:
            response= self.server.open( theReq )
            # fold in attributes that are compatible with Django HttpResponse
            response.status_code = response.code
            response.content= response.read()
            return response
        except:
            ... handle various kinds of IOError, HTTPError exceptions...
    def getSomeResource( self, key ):
        response= self.request( &amp;quot;GET&amp;quot;, &amp;quot;/path/to/resource/%s&amp;quot; % key )
        ... examine response.content, maybe do simplejson decode or xml.etree parse...
&lt;/pre&gt;
&lt;p&gt;The problem is that the Django test client and the urllib2 packages are wildly incompatible.&lt;/p&gt;
&lt;p&gt;Okay, maybe not &lt;em&gt;wildly&lt;/em&gt; , but seriously incompatible.&lt;/p&gt;
&lt;p&gt;First, the Django Client's HttpResopnse includes attributes status_code and content.  The urllib2.addinfourl response uses code and is -- itself -- a file-like object.&lt;/p&gt;
&lt;p&gt;Second, and more important, the Django Client's HttpResponse is a dictionary full of headers.  The urllib2.addinfourl is a file with an info() method that contains the headers.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="choices"&gt;
&lt;h2&gt;Choices&lt;/h2&gt;
&lt;p&gt;We have a tantalizing set of alternatives.&lt;/p&gt;
&lt;ol class="arabic simple"&gt;
&lt;li&gt;Make urllib2's response look more like Django's response.  This requires adding a few additional attributes, and a __getitem__ method.  Not too difficult to do.  But only because our unit tests are not very demanding.&lt;/li&gt;
&lt;li&gt;Create a Facade over urllib2.addinfourl and django.http.HttpResponse that is independent of both, and can work with both as implementation classes.  While cool-sounding, and easy to implement in our WS client package, we'd have to do a tiny bit of extra work in our unit tests to create a Facade-based client rather than use the default client.&lt;/li&gt;
&lt;li&gt;Get a proper Python RESTful client.  Like &lt;a class="reference external" href="http://restclient.org/"&gt;RESTClient&lt;/a&gt;  or &lt;a class="reference external" href="http://code.google.com/p/python-rest-client/"&gt;Python-rest-client&lt;/a&gt; .  The approach in &lt;a class="reference external" href="http://www.infectmac.com/2008/08/restful-python.html"&gt;RESTful Python&lt;/a&gt;  -- a decorator -- is another possibility.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;The problem with #1 is that the Python client package we distribute will have this odd-looking design that adds a bunch of random-looking features to urllib2.addinfourl.  A lot of explanation (like this Blog posting) doesn't remove the oddness.  The Java and .Net packages will be fine.&lt;/p&gt;
&lt;p&gt;The problem with #2 is that the Python client package will be even more complex than #1, with little recognizable value to anyone for the complexity.&lt;/p&gt;
&lt;p&gt;There's no problem with #3.  Indeed, this might be best in the long run.&lt;/p&gt;
&lt;/div&gt;
</content><category term="Python"></category><category term="#python"></category><category term="unit testing"></category></entry><entry><title>The Schema Evolution Problem</title><link href="https://slott56.github.io/2008_08_06-the_schema_evolution_problem.html" rel="alternate"></link><published>2008-08-06T10:21:00-04:00</published><updated>2008-08-06T10:21:00-04:00</updated><author><name>S.Lott</name></author><id>tag:slott56.github.io,2008-08-06:/2008_08_06-the_schema_evolution_problem.html</id><summary type="html">&lt;p&gt;Fundamentally, we need to provide explicit version identification on a schema.   This is technically easy, but organizationally nearly impossible.&lt;/p&gt;
&lt;p&gt;Technically, we need to use some kind of version control software for our model and the resulting DDL.  We need some meta-meta-data to track schema names and version numbers.  If we …&lt;/p&gt;</summary><content type="html">&lt;p&gt;Fundamentally, we need to provide explicit version identification on a schema.   This is technically easy, but organizationally nearly impossible.&lt;/p&gt;
&lt;p&gt;Technically, we need to use some kind of version control software for our model and the resulting DDL.  We need some meta-meta-data to track schema names and version numbers.  If we like doing too much work, we can introduce a meta-meta-data table with schema name and version numbers.  If we're lazy, there's an even simpler, more reliable approach.&lt;/p&gt;
&lt;p&gt;Organizationally, we need the discipline to track every single schema change and determine the level of compatibility with application software.  Garden-variety ALTER statements (to add columns, or extend the size of a column) won't break software; bumping the minor version number is fine.  Adding new tables or views won't break software.  Renames and drops, however, will break software and require a bump to the major version number.&lt;/p&gt;
&lt;div class="section" id="what-is-a-schema"&gt;
&lt;h2&gt;What is a Schema?&lt;/h2&gt;
&lt;p&gt;First, a schema isn't the &lt;em&gt;entire&lt;/em&gt;  set of metadata in a single database instance.  Even if your data is organized in one massive, flat schema with thousands of tables, you still have many smaller &amp;quot;schema&amp;quot; within that single SQL schema owned by &amp;quot;PROD&amp;quot; or &amp;quot;OPS&amp;quot; or &amp;quot;DBA&amp;quot; or &amp;quot;SYS&amp;quot; or whoever owns your production tables.&lt;/p&gt;
&lt;p&gt;We'll distinguish between the practical, conceptual schema and the often-misused SQL schema.  Sometimes they overlap, but this is rare.&lt;/p&gt;
&lt;p&gt;Your smaller conceptual schemas are the &amp;quot;application-specific&amp;quot; subsets of your overall SQL schema.  If you're smart, your SQL schemas match your conceptual schemas.  If you're lazy, you have a single massive SQL schema and use table name prefixes to try and separate tables into smaller conceptual schemas.&lt;/p&gt;
&lt;p&gt;Here's the bottom-line suggestion. Use SQL schema.  Don't use table prefixes.&lt;/p&gt;
&lt;p&gt;[In a Big IT organization, this can't happen because it would &amp;quot;break everything&amp;quot;.  Everyone depends on there being a single, flat anonymous schema.  This isn't true, as new applications and maintenance to existing applications are an opportunity to restructure the SQL schema to match the actual use of the tables.  Sadly, it only reduces future maintenance costs, so it doesn't have any current-year impact, so no one ever does this.]&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="what-about-the-applications"&gt;
&lt;h2&gt;What About the Applications?&lt;/h2&gt;
&lt;p&gt;The applications exist independent of the data.  Stored procedures (&lt;a class="reference external" href="https://slott56.github.io/2008_08_03-stored_procedures_are_a_configuration_management_nightmare_revised.html"&gt;A Configuration Management Nightmare&lt;/a&gt; ) are in the application model, not the data model, and evolve independently from the data schema.  However, this isn't always understood, and stored procedures are often mis-managed.&lt;/p&gt;
&lt;p&gt;An application could check the schema meta-meta-data to be sure that the application is compatible with the schemas it uses.  It can be a simple query, and an exception gets thrown to indicate that the application can't start and run with the given mix of database schemas.  We know that production programs shouldn't work with the new, upgraded integration test database.  However, we also see this happen; sometimes they crash and we fix them, other times they don't crash, but don't produce right answers, either.  Sigh.&lt;/p&gt;
&lt;p&gt;There's a simpler approach, however, than a query.&lt;/p&gt;
&lt;p&gt;Should the application and schema version numbers track?  Should the application go through version 2.1.2 and 2.1.3 to indicate that it requires schema version 2.1?  Not necessarily.  There is not a tidy 1:1 mapping between software components and database schema objects.  Generally, database schema objects are shared -- widely -- by software components.  Version 2.1 of application X and version 4.2 of application Y may both depend on version 3.x of database schema Z.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="how-to-make-this-work"&gt;
&lt;h2&gt;How To Make This Work&lt;/h2&gt;
&lt;p&gt;Put the version number in the schema name.&lt;/p&gt;
&lt;p&gt;First, don't create a bunch of XYZ_table1, XYZ_table2 names in a single, flat schema.  Create table1 and table2 in schema XYZ.  Use lots of schemas.  That's why they're available to you.&lt;/p&gt;
&lt;p&gt;[Yes, your historical, legacy applications didn't use schemas.  I'm aware that this is new.  Start now.]&lt;/p&gt;
&lt;p&gt;Second, don't simply create a &amp;quot;timeless&amp;quot; XYZ schema, use the major release number as part of the name.  Create an XYZ_2 schema.  This will work for all 2.x versions of the schema.&lt;/p&gt;
&lt;p&gt;When you move to version 3.1, create a new XYZ_3 schema.  &lt;strong&gt;New&lt;/strong&gt;.  Migrate the data from the XYZ_2 schema.  Then, rename XYZ_2 to XYZ_2_OLD, so that any program that improperly uses the old schema will throw an exception and die.  When you need to recover the space, you can drop the XYZ_2_OLD schema, knowing that no program is expected to use it; any program that does use it, needs a fix.&lt;/p&gt;
&lt;p&gt;Wait!  That's potentially a lot of code to touch.  Or, if your a mainframer, that's a lot of programs that need to be rebound to the new SQL.  Yep.  It is.  It's a trivial administrative task.  If you can't recompile or rebind your programs, you have serious quality issues that you &lt;strong&gt;must&lt;/strong&gt;  fix.&lt;/p&gt;
&lt;p&gt;If you can't make simple SQL changes, you have serious flaws in your application software and your overall IT processes.  You &lt;strong&gt;must&lt;/strong&gt;  fix these application design flaws and organizational process flaws.  I'm sorry for pointing this out.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="implementation-steps"&gt;
&lt;h2&gt;Implementation Steps&lt;/h2&gt;
&lt;p&gt;Name your schema.  Allocate your tables to appropriate schema.  More schemas is a better approach than fewer schemas.  There's no performance penalty.  Design and maintenance are simpler.  When you design software, You don't design a single, massive, does-everything application, you write small, focused application programs.  Your database, similarly, should be structured in small, conceptually simple modules.&lt;/p&gt;
&lt;p&gt;For Java programmers, use &lt;a class="reference external" href="http://ibatis.apache.org/"&gt;iBatis&lt;/a&gt;  to extract your SQL from your programs.  The schema changes will be isolated to the iBatis configuration files, mostly.&lt;/p&gt;
&lt;p&gt;For Python programmers, you can use &lt;a class="reference external" href="http://www.sqlalchemy.org/"&gt;SQLAlchemy&lt;/a&gt;  to isolate most of the SQL from your overall application.  Put each schema definition in a separate &amp;quot;models&amp;quot; file.  Include the SQLAlchemy table definitions as well as the Python classes and the mappings.  You can, without too much difficulty, include a few convenience functions that will create or drop-and-create the schema.&lt;/p&gt;
&lt;p&gt;If you're creating Python/Django applications, consider including the schema version number on your Django application name.  Your Django project folder for a given site might include things like someapp_1 and someapp_2.  The older application (someapp_1) has one model, and the newer version (someapp_2) has the expanded, incompatible model.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="change-management"&gt;
&lt;h2&gt;Change Management&lt;/h2&gt;
&lt;p&gt;Rather than mess with an complex, risky in-place conversion, you are &lt;em&gt;adding&lt;/em&gt;  to the database.  You can write a simple batch application to create the someapp_2 data objects from the someapp_1 objects.  Once the data is migrated, you can switch the settings.py and the urls.py files to use someapp_2 instead of someapp_1.  You can easily dry-run this conversion process in an integration test or staging instance of your web site.  If it works there, you can do it again in production.&lt;/p&gt;
&lt;p&gt;The best part about keeping the two schema in parallel for a time is the ability to fall-back to the previous version and try the conversion again after fixing the bugs.  You're never replacing anything; you're simply adding a schema and directing the application programs at the new schema.&lt;/p&gt;
&lt;/div&gt;
</content><category term="Python"></category><category term="#python"></category><category term="database"></category></entry><entry><title>Stored Procedures Are A Configuration Management Nightmare (revised)</title><link href="https://slott56.github.io/2008_08_03-stored_procedures_are_a_configuration_management_nightmare_revised.html" rel="alternate"></link><published>2008-08-03T16:44:00-04:00</published><updated>2008-08-03T16:44:00-04:00</updated><author><name>S.Lott</name></author><id>tag:slott56.github.io,2008-08-03:/2008_08_03-stored_procedures_are_a_configuration_management_nightmare_revised.html</id><summary type="html">&lt;p&gt;I've been asked about the proper location of Stored Procedures more than once.  I've come down very strongly in opposition to triggers and stored procedures.&lt;/p&gt;
&lt;p&gt;First, &lt;a class="reference external" href="https://slott56.github.io/2007_05_27-plsql_and_java_the_benchmark_challenge_revised.html"&gt;PL/SQL is slow&lt;/a&gt; .  Anecdotally, people claim that introducing PL/SQL made an app faster.  I submit that they restructured the application significantly to …&lt;/p&gt;</summary><content type="html">&lt;p&gt;I've been asked about the proper location of Stored Procedures more than once.  I've come down very strongly in opposition to triggers and stored procedures.&lt;/p&gt;
&lt;p&gt;First, &lt;a class="reference external" href="https://slott56.github.io/2007_05_27-plsql_and_java_the_benchmark_challenge_revised.html"&gt;PL/SQL is slow&lt;/a&gt; .  Anecdotally, people claim that introducing PL/SQL made an app faster.  I submit that they restructured the application significantly to create small, focused transactions, and that's what created the improvement.  As a practical matter, you need to write focused, PL/SQL-like transaction methods in your Java programs.  While technically possible, you can't casually execute SQL statements willy-nilly.&lt;/p&gt;
&lt;p&gt;Second, it's hard to do configuration management on stored procedures.  Not impossible, but very hard.  The reasons are entirely organizational.&lt;/p&gt;
&lt;p&gt;Recently I received an email that was nearly opaque, but seemed to indicate that the organization couldn't clone production to create another test, and couldn't rationalize the versions of their various stored procedures.   I think they wanted a puff of &lt;strong&gt;Faerie Dust&lt;/strong&gt;™ that would allow stored procedure X to determine if it was being used by package Y or package Z and behave differently in the different contexts.  The request makes no sense -- this is just a version control issue.  Clearly, there are two versions of X, but the emailer claimed there was one version of X and it had to determine it's behavior dynamically.&lt;/p&gt;
&lt;div class="section" id="conflation-the-organizational-root-cause"&gt;
&lt;h2&gt;Conflation - The Organizational Root Cause&lt;/h2&gt;
&lt;p&gt;A stored procedure lives in the database.  Consequently, it's conflated with persistent data and schema definitions and assigned -- for no good reason -- to the DBA's.  These three things -- data, schema and processing -- have little to do with each other.  They emphatically do not belong together.   However, they're almost always conflated into a murky puddle of SQL.&lt;/p&gt;
&lt;p&gt;Let's break these things apart.&lt;/p&gt;
&lt;dl class="docutils"&gt;
&lt;dt&gt;&lt;strong&gt;Data&lt;/strong&gt;&lt;/dt&gt;
&lt;dd&gt;is the organization's actual data.  Some (but not all) of the business records lives in managed databases.  Some live in desktop application documents (word processing, spreadsheets, unmanaged desktop databases, etc.)  Data is precious, perhaps the most precious thing in the organization.&lt;/dd&gt;
&lt;dt&gt;&lt;strong&gt;Schema&lt;/strong&gt;&lt;/dt&gt;
&lt;dd&gt;(or metadata) is table, column, view and index definitions.  It's also physical stuff like tablespaces, files, instances, etc.  Some of this is important, some of it is subject to change without notice.  Tablespace configuration parameters rarely matter except as an implementation detail.&lt;/dd&gt;
&lt;dt&gt;&lt;strong&gt;Processing&lt;/strong&gt;&lt;/dt&gt;
&lt;dd&gt;is triggers, stored procedures and all of the application programs that live outside the database.  Note that there is no crisp distinction between &amp;quot;low-level&amp;quot; and &amp;quot;high-level&amp;quot; processing.  Many DBA's have tried to explain to me that CRUD rules are &amp;quot;low-level&amp;quot;, but then they add some foreign-key relationships, after that they also need to add some many-to-many relationships and the intermediate bridge tables, then they start adding other things that are part of larger and more complex relationships.  Stop!  If you can't find a boundary easily, it doesn't really exist.&lt;/dd&gt;
&lt;/dl&gt;
&lt;/div&gt;
&lt;div class="section" id="version-control"&gt;
&lt;h2&gt;Version Control&lt;/h2&gt;
&lt;p&gt;Data -- typically -- has fairly loose version control.  The RDBMS often has secret sequence numbers (SCN's) that are used internally to manage cache and synchronize physical files.  These transaction sequence numbers are, effectively, a kind of version number for the data.&lt;/p&gt;
&lt;p&gt;Often, we'll have a &amp;quot;last changed date&amp;quot; in a database record.  This is a surrogate version number for the record.  It tells us when the data changed.  Most applications don't record a complete change log for the data, we simply update the change date.  A few applications do create detailed change logs.  In some cases, people try to leverage the database logging facilities to back into a formal change log for the data.&lt;/p&gt;
&lt;p&gt;Schema is rarely under any kind of version control.  Metadata is often the least disciplined part of the enterprise infrastructure.  It's easy (really easy) to have formal version control over metadata.  It's rarely done, however.  For some reason, DBA's don't seem to use version control software.&lt;/p&gt;
&lt;p&gt;Application software, typically, has the best version control.  Many organizations use some formal version control software (CVS, Subversion or some commercial product like MKS, VSS or PVCS.)  This can easily apply version control information to the source code (and even the resulting .class files.)&lt;/p&gt;
&lt;p&gt;[Some organizations can't even put their application software under version control.  This doesn't change the issue of conflating data, schema and application.]&lt;/p&gt;
&lt;p&gt;For no good reason stored procedures are the province of the DBA's (who don't use version control software.)  Consequently, the external application software (in Java, Python or whatever) may have version control information, but the stored procedures never have version control.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="schema-versions"&gt;
&lt;h2&gt;Schema Versions&lt;/h2&gt;
&lt;p&gt;The database schema (the tables, columns, indexes, views and sequence generators) has a version number.  The version number for a schema -- like the version number for software -- defines &amp;quot;compatibility&amp;quot;.&lt;/p&gt;
&lt;p&gt;Schema version 2.1 and 2.2 are &amp;quot;compatible&amp;quot; in some sense.  Schema versions 3.5 and 4.1 are incompatible.&lt;/p&gt;
&lt;p&gt;What defines &amp;quot;compatibility&amp;quot;?  Clearly, &amp;quot;compatible&amp;quot; means &amp;quot;compatible with SQL DML&amp;quot;.  If you've done standard database ALTER statements (adding columns, expanding the sizes of columns) or changing indexes or adding views, you haven't broken any SQL DML.  The old SQL still works with the new schema.  This is a 2.2 to 2.3 kind of change.&lt;/p&gt;
&lt;p&gt;If you've dropped a column or table or view, or you've shortened a column, or changed the type of a column, then you've made a change which may break existing SQL.  When you've make this kind of change, you'll need to bump the major version number.&lt;/p&gt;
&lt;p&gt;You need two things:&lt;/p&gt;
&lt;dl class="docutils"&gt;
&lt;dt&gt;&lt;strong&gt;Discipline&lt;/strong&gt;.&lt;/dt&gt;
&lt;dd&gt;This doesn't happen by default.&lt;/dd&gt;
&lt;dt&gt;&lt;strong&gt;Some meta-meta-data&lt;/strong&gt;.&lt;/dt&gt;
&lt;dd&gt;A table that has schema names and version numbers is all you really need.  It's nice to fold in &amp;quot;applicable dates&amp;quot; and &amp;quot;responsible person&amp;quot;, etc., but not essential.   In some cases, you can use database comments for this.&lt;/dd&gt;
&lt;/dl&gt;
&lt;p&gt;When you make database changes, you must create a script that (a) makes the change and (b) updates the database schema version table.  That's about it.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="what-about-stored-procedures"&gt;
&lt;h2&gt;What About Stored Procedures?&lt;/h2&gt;
&lt;p&gt;Why can't we annotate our stored procedures with version numbers and put them under version control like the rest of the database?&lt;/p&gt;
&lt;p&gt;The question is rhetorical.  Of course we can put stored procedures under version control.  It just requires some discipline.  And -- perhaps -- making stored procedures part of application software's responsibility, and not part of the DBA's job.&lt;/p&gt;
&lt;p&gt;If we take stored procedures away from the DBA's, we need a formal turnover procedure for putting a particular suite of stored procedures into a database.&lt;/p&gt;
&lt;p&gt;Separating the stored procedures from the schema via a formal turnover has some marvelous consequences.&lt;/p&gt;
&lt;ol class="arabic simple"&gt;
&lt;li&gt;You can reconstruct the stored procedures from your source code repository exactly the same way you extract your Python or Java.  Indeed, you can make a complete software package with all of the various language elements.  You can extract all of the procedure creates as a big script and run it any time you need to.&lt;/li&gt;
&lt;li&gt;The database has two distinct parts:  the Data, the Processing.  These two are matched by schema version number.  The DBA's are responsible for the data; the schema versions; the preservation of essential corporate information.  The DBA's are also responsible for running the scripts that upgrade that portion of the application software that happens to live in the database.  The DBA's aren't responsible for stored procedures.&lt;/li&gt;
&lt;li&gt;The migration of a database from development to test is a two-part job.  Move the schema and data from the developers to a test environment.  Separately, run all of the scripts to build the proper software version that matches the schema of the data.&lt;/li&gt;
&lt;li&gt;You have explicit compatibility checks.  Version 2.x of schema and software are being used in production.  Version 3.x of schema and software is in some kind of parallel test prior to conversion.  Version 3.y of schema and software is in some early test; 3.z is in development.&lt;/li&gt;
&lt;li&gt;You can begin to wean yourself away from the nightmare of stored procedure management.  Once you take this out of the DBA's hands, you find that a consistent set of Python (or Java) packages that define the Model layer does everything that stored procedures and triggers do, only more simply and more maintainably.&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;
&lt;div class="section" id="what-s-so-hard"&gt;
&lt;h2&gt;What's So Hard?&lt;/h2&gt;
&lt;p&gt;It's very easy to put stored procedures under explicit, clear version control.  With a little care, even a database schema can be put under version control.&lt;/p&gt;
&lt;p&gt;What's so hard is actually making the organizational change.  Ask around.  The DBA's will tell you that they are overworked, because they're &amp;quot;forced&amp;quot; to write all the stored procedures and triggers.  Forced?  By whom?&lt;/p&gt;
&lt;p&gt;Generally, the &amp;quot;organization&amp;quot; seems to mandate that everything SQL -- tables, columns, indexes, views, stored procedures and triggers -- pass through the DBA's.  The distinction between data and processing is somehow lost.  Splitting it up will often anger the manager of the DBA's, who'll make the case that no one else can be trusted to create stored procedures.&lt;/p&gt;
&lt;p&gt;When testing stops because of version control issues, when production fails, it seems like the problem should be addressed.  It's usually obvious that there are serious version control problems between the schema and stored procedures.&lt;/p&gt;
&lt;p&gt;I only know that there's a long-standing, steadfast refusal to split the database into data and processing elements.  The consequence of this is that stored procedures are unmaintainable, testing is nearly impossible, and production problems are rampant.&lt;/p&gt;
&lt;p&gt;Consequently, I suggest that stored procedures and triggers never be used.  Ever.&lt;/p&gt;
&lt;/div&gt;
</content><category term="Python"></category><category term="#python"></category><category term="database"></category></entry><entry><title>Denormalization or "What did you mean by that?"</title><link href="https://slott56.github.io/2008_06_14-denormalization_or_what_did_you_mean_by_that.html" rel="alternate"></link><published>2008-06-14T11:59:00-04:00</published><updated>2008-06-14T11:59:00-04:00</updated><author><name>S.Lott</name></author><id>tag:slott56.github.io,2008-06-14:/2008_06_14-denormalization_or_what_did_you_mean_by_that.html</id><summary type="html">&lt;p&gt;I use the word denormalization heavily, to make a point to a certain class of developers.  Other developers object to the term, since it doesn't have a precise meaning.&lt;/p&gt;
&lt;p&gt;The point I often have to make this:&lt;/p&gt;
&lt;ol class="arabic simple"&gt;
&lt;li&gt;3rd Normal Form is for Updates.&lt;/li&gt;
&lt;li&gt;Data Warehousing is about Insert and Select …&lt;/li&gt;&lt;/ol&gt;</summary><content type="html">&lt;p&gt;I use the word denormalization heavily, to make a point to a certain class of developers.  Other developers object to the term, since it doesn't have a precise meaning.&lt;/p&gt;
&lt;p&gt;The point I often have to make this:&lt;/p&gt;
&lt;ol class="arabic simple"&gt;
&lt;li&gt;3rd Normal Form is for Updates.&lt;/li&gt;
&lt;li&gt;Data Warehousing is about Insert and Select; there are no Updates (to speak of).&lt;/li&gt;
&lt;li&gt;Consequently, the traditional normalization rules (Third Normal Form a/k/a 3NF) doesn't apply to data warehousing.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;My habit is to describe the star-schema (or snowflake schema) as &amp;quot;denormalized&amp;quot;.  This isn't really correct, but it does emphasize my point.  I have to make this point emphatically because we have to get past the Data Cartel's Standard Objection: &lt;strong&gt;New Technology Won't Work&lt;/strong&gt;.  Most DBA's who are new to Data Warehousing and the star schema will exercise their veto authority over new technology, claim that the design is &amp;quot;inefficient&amp;quot; and stop (or delay) the project.&lt;/p&gt;
&lt;div class="section" id="dba-objections"&gt;
&lt;h2&gt;DBA Objections&lt;/h2&gt;
&lt;p&gt;DBA's can object in &lt;a class="reference external" href="https://slott56.github.io/2007_11_29-the_passive_aggressive_programmer_or_why_nothing_gets_done_revised.html"&gt;Passive-Aggressive&lt;/a&gt;  (and &lt;a class="reference external" href="https://slott56.github.io/2008_03_24-the_passive_aggressive_programmer_part_ii.html"&gt;Passive-Aggressive Part II&lt;/a&gt; ) mode -- where they don't have a better solution, they just have &amp;quot;concerns&amp;quot; about the standard DW solution.  Here are some things I've heard.&lt;/p&gt;
&lt;ol class="arabic"&gt;
&lt;li&gt;&lt;p class="first"&gt;&lt;strong&gt;It isn't normalized&lt;/strong&gt;.  Which is a WTF? kind of point.  It isn't normalized for updates because there aren't any (to speak of).  It's normalized for SELECT SUM(*) GROUP BY, which is the canonical dimensional query.  I call this &amp;quot;denormalization&amp;quot; to make the point; perhaps I should call it star-schema normalization.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p class="first"&gt;&lt;strong&gt;It doesn't use &amp;quot;natural keys&amp;quot; correctly&lt;/strong&gt;.  I'm pretty sure that natural keys don't actually exist.  Almost everything is either an attribute (which can change) or a surrogate key (which isn't very likely to change).  A changeable attribute isn't really a key, is it?&lt;/p&gt;
&lt;p&gt;When writing ETL programs, we sometimes have a blurry edge when an external application assigns a truly permanent surrogate key.  In these cases, the external surrogate is often something that the organization uses heavily -- as if it was a natural key.  In other cases, they have a surrogate-like key that can (it turns out) change, making it just an attribute.  In the warehouse, it's usually best to simply assign warehouse surrogates and not burn up brain calories trying to make too many distinctions in the source applications.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p class="first"&gt;&lt;strong&gt;All those joins are inefficient&lt;/strong&gt;.  This can -- in the extreme case -- lead to &lt;strong&gt;The Uni-Table&lt;/strong&gt;.  This is the pre-joined ur-fact table that contains all dimensional attributes and all fact values.  It works, but it repeats all of the dimensional attributes and it doesn't track dimensional change at all.  Yes, I've seen it done.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p class="first"&gt;&lt;strong&gt;It uses too much storage&lt;/strong&gt;.  This is just silly, but it comes up.  Once, I caught the sysadmins and DBA's in a meeting where they were quibbling about log sizes so that they could micro-manage storage at the 100Gb increment.  &amp;quot;There's four people in this meeting.  At your hourly cost, I could have bought 400Gb at Circuit City.&amp;quot;  And the price of storage continues to plummet.  Nowadays, I think I could buy a terabyte.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p class="first"&gt;&lt;strong&gt;Fact updates can be inefficient&lt;/strong&gt;.  This is crazy, because changing a fact's measurement value is a single row update; it's fine if you're correcting errors.  Changing a batch of fact's measurements is -- what? -- criminal mischief?  Who changes batches of facts?  Considerer deleting the incorrect ones and reloading correct ones.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Changing the association between a batch of facts and a dimension is even spookier.  The historical fact is what you recorded.  You don't get to change the facts; it's called perjury.  If you're restating your books, you usually have new facts that apply to a historical time period.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="star-schema-normalization"&gt;
&lt;h2&gt;Star Schema Normalization&lt;/h2&gt;
&lt;p&gt;To get past the DBA objections, we need to have several heart-to-heart conversations on star-schema normalization.  Generally, these are painful because DBA's are overworked and sometimes underqualified.  Examples on paper don't help much.  Telling them to read Kimball does nothing.  Loading up realistic sets of data is the only workable approach to showing them that the storage is manageable, the joins won't kill you, surrogate keys work, and the star schema is a &amp;quot;real&amp;quot; thing.&lt;/p&gt;
&lt;p&gt;Once we've aired out an example, we then have to revisit the star schema thing over and over again.  Most DBA's are so habituated to 3NF that they can't get past it to see that a star schema is an alternative normal form.  Except in rare cases, the best we get is grudging tolerance.  [In the rare cases where the DBA's embrace a star schema approach, no one needs me, except to validate the design.]&lt;/p&gt;
&lt;p&gt;The basic 1NF and 2NF rules apply to the star schema normal form as well as transactional normal form.  Arrays are still a bad idea in the relational world.  Foreign attributes (those not functionally dependent on the key) are still a bad idea.  However, 3NF is out the window -- derived data is a helpful thing.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="derived-data-what-about-updates"&gt;
&lt;h2&gt;Derived Data?  What About Updates?&lt;/h2&gt;
&lt;p&gt;DBA standard objection #5 -- updates hurt -- often surfaces when discussing the approach of persisting derived data.  This is a focused &amp;quot;denormalization&amp;quot; that unwinds just 3NF to avoid repeating a calculation.  In the case of a data warehouse (load once, query an infinite number of times) all calculations done at load time are amortized across an infinite number of queries, making them delightfully efficient.&lt;/p&gt;
&lt;p&gt;The &amp;quot;update&amp;quot; issue can't arise.  Let's look at some common dimensions.&lt;/p&gt;
&lt;dl class="docutils"&gt;
&lt;dt&gt;&lt;strong&gt;Time&lt;/strong&gt;.&lt;/dt&gt;
&lt;dd&gt;You don't change the day of the week for March 8, 1987.  It is, was, and always will be Sunday.&lt;/dd&gt;
&lt;dt&gt;&lt;strong&gt;Space&lt;/strong&gt;.&lt;/dt&gt;
&lt;dd&gt;Geographical boundaries change.  However, this is the canonical Slowly Changing Dimension (SCD) problem that Kimball covers in detail.  [If you have what Kimball calls a &amp;quot;type 3&amp;quot; SCD, you have the most common example of an update in a data warehouse; the change of status from &amp;quot;current&amp;quot; to &amp;quot;previous&amp;quot;.]&lt;/dd&gt;
&lt;dt&gt;&lt;strong&gt;Customer&lt;/strong&gt;.&lt;/dt&gt;
&lt;dd&gt;Your customers (either individuals in huge collections or other businesses in small collections) have numerous changes.  However, they often have attributes which can't change as well as attributes which frequently change.  For example, demographics change very slowly (if at all).  Customers often requires more sophisticated &amp;quot;snowflake schema&amp;quot; techniques.  There still aren't any updates, but there are SCD techniques for handling this.&lt;/dd&gt;
&lt;dt&gt;&lt;strong&gt;Product&lt;/strong&gt;.&lt;/dt&gt;
&lt;dd&gt;Your products, product lines, product families, product groupings, solutions, technologies, platforms, services, etc., are all grouped by marketing in the randomest ways.  These groupings and hierarchies and clusters and affinities are just ways that marketing tries to portray your company; and it changes with every whim and brain-fart.  This is also a basic SCD issue; you simply add the alternative hierarchies and groupings and do alternate joins on the facts.&lt;/dd&gt;
&lt;dt&gt;&lt;strong&gt;Cost Centers&lt;/strong&gt;.&lt;/dt&gt;
&lt;dd&gt;Your internal cost structure changes.  Sometimes frequently.  This is still SCD.  No updates, just inserts.&lt;/dd&gt;
&lt;/dl&gt;
&lt;/div&gt;
&lt;div class="section" id="recent-example"&gt;
&lt;h2&gt;Recent Example&lt;/h2&gt;
&lt;p&gt;&lt;a class="reference external" href="https://slott56.github.io/2008_06_06-my_query_is_slow_what_to_do_or_dumb_as_a_post_sql_revised.html"&gt;Recently&lt;/a&gt; , I aired out some brain-dead non-solutions to a simple reporting problem where number of rows (500 new rows per hour) might have been the design problem being solved.  The non-solution involved a five ways of avoiding a viable solution.  As follow-up to my suggestions, I was given the following variations on DBA objection 1; each affixing blame somewhere else.&lt;/p&gt;
&lt;p&gt;1.1.  The customer cannot accept a &amp;quot;denormalized&amp;quot; table that pre-computes the values.  [The customer is at fault.]&lt;/p&gt;
&lt;p&gt;1.2.  Since we can't directly use the &amp;quot;denormalized&amp;quot; table in your blog posting, the idea of denormalization is broken, and we can never talk about it in any form whatsoever.  Temporary tables, materialized views and other techniques are off the table, &lt;em&gt;a priori&lt;/em&gt;.  [I'm at fault for not providing the expected solution, which involved some kind of &lt;strong&gt;Faerie Dust&lt;/strong&gt;™ that would make a bad table process quickly.]&lt;/p&gt;
&lt;p&gt;1.3.  The organization can't learn anything new.  Talking about &amp;quot;denormalization&amp;quot; would be new, and is therefore forbidden.  The idea of persistent derived values is off the table, &lt;em&gt;a priori&lt;/em&gt;.  [The organization is at fault.]&lt;/p&gt;
&lt;p&gt;At this point, any suggestion I might have has been trumped by the DBA's opposition to denormalization.  Blame has been assigned everywhere.  I think this is because I used the word &amp;quot;denormalization&amp;quot; incautiously and set myself up for three flavors of DBA objection #1 (&amp;quot;It isn't normalized.&amp;quot;)&lt;/p&gt;
&lt;p&gt;Perhaps, if I'd said &amp;quot;persistent derived values&amp;quot; instead of &amp;quot;denormalization&amp;quot; we might have gotten somewhere.  Ideally, they would have suggested a temporary table or materialized view as an implementation technique.  But, we stalled out at my incautious use of a loaded buzzword.&lt;/p&gt;
&lt;/div&gt;
</content><category term="Python"></category><category term="#python"></category><category term="database"></category></entry><entry><title>Genius Move -- Characteristic Functions</title><link href="https://slott56.github.io/2008_06_07-genius_move_characteristic_functions.html" rel="alternate"></link><published>2008-06-07T13:54:00-04:00</published><updated>2008-06-07T13:54:00-04:00</updated><author><name>S.Lott</name></author><id>tag:slott56.github.io,2008-06-07:/2008_06_07-genius_move_characteristic_functions.html</id><summary type="html">&lt;p&gt;The comment was eaten by Haloscan, but here's the text...&lt;/p&gt;
&lt;p&gt;You need to read Rozhenstein on characteristic functions.&lt;/p&gt;
&lt;pre class="literal-block"&gt;
select
sum(case when a &amp;lt; .5 then 1 else 0 end) 'A'
,sum(case when a &amp;gt;= .5 and a &amp;lt; .75 then 1 else 0 end) 'B'
,sum(case when a &amp;gt;= .75 then …&lt;/pre&gt;</summary><content type="html">&lt;p&gt;The comment was eaten by Haloscan, but here's the text...&lt;/p&gt;
&lt;p&gt;You need to read Rozhenstein on characteristic functions.&lt;/p&gt;
&lt;pre class="literal-block"&gt;
select
sum(case when a &amp;lt; .5 then 1 else 0 end) 'A'
,sum(case when a &amp;gt;= .5 and a &amp;lt; .75 then 1 else 0 end) 'B'
,sum(case when a &amp;gt;= .75 then 1 else 0 end) 'C'
,bar
from foo
group by bar
&lt;/pre&gt;
&lt;p&gt;So, I googled it, and figured out what I'd been missing.&lt;/p&gt;
&lt;div class="section" id="skip-the-math"&gt;
&lt;h2&gt;Skip the Math&lt;/h2&gt;
&lt;p&gt;The Google page on characteristic functions is heavy going.  The issue here is to characterize the frequency distribution of some more-or-less random variable.  This is a real close fit with the formal definition of a characteristic function.&lt;/p&gt;
&lt;p&gt;When should we apply the characteristic function?  Load time or query time?  The comment showed it at query time.  However, we could also do it at load time.&lt;/p&gt;
&lt;p&gt;Here's the genius part.&lt;/p&gt;
&lt;p&gt;If we define it as a separate function, we can defer this decision based on which implementation meets our performance guidelines.&lt;/p&gt;
&lt;p&gt;We have this situation.&lt;/p&gt;
&lt;pre class="literal-block"&gt;
def c1( value ):
    a,b = divmod( int(value*100), 10 )
    if b == 0:
        return &amp;quot;== 0.%d&amp;quot; % ( a, )
    else:
        return &amp;quot;0.%d - 0.%d&amp;quot; % ( a, a+1 )
&lt;/pre&gt;
&lt;p&gt;We can then use this during load or we can use it in a fetch loop.  Quite cool.  Very elegantly separated from other parts of the processing.&lt;/p&gt;
&lt;/div&gt;
</content><category term="Python"></category><category term="#python"></category><category term="database"></category></entry><entry><title>My Query Is Slow -- What To Do? Or Dumb-As-A-Post SQL (Revised)</title><link href="https://slott56.github.io/2008_06_06-my_query_is_slow_what_to_do_or_dumb_as_a_post_sql_revised.html" rel="alternate"></link><published>2008-06-06T22:30:00-04:00</published><updated>2008-06-06T22:30:00-04:00</updated><author><name>S.Lott</name></author><id>tag:slott56.github.io,2008-06-06:/2008_06_06-my_query_is_slow_what_to_do_or_dumb_as_a_post_sql_revised.html</id><summary type="html">&lt;p&gt;First, let me point out that the Data Cartel (&amp;quot;DBA&amp;quot; means Don't Bother Asking) won't release all the information I requested, so some of this is a guess.&lt;/p&gt;
&lt;p&gt;We'll look at a number of dumb-as-a-post SQL techniques.  This is proof -- if any were needed -- that bad SQL is worse than …&lt;/p&gt;</summary><content type="html">&lt;p&gt;First, let me point out that the Data Cartel (&amp;quot;DBA&amp;quot; means Don't Bother Asking) won't release all the information I requested, so some of this is a guess.&lt;/p&gt;
&lt;p&gt;We'll look at a number of dumb-as-a-post SQL techniques.  This is proof -- if any were needed -- that bad SQL is worse than no SQL.&lt;/p&gt;
&lt;p&gt;The table appears to have 2 columns, a date and a floating-point value in the range 0.0 to 1.0.  Rows arrive at the rate of 500 an hour.&lt;/p&gt;
&lt;p&gt;Someone wants a weekly summary (about 90,000 rows) binned into 10 ranges 0.0 to 0.1, 0.1 to 0.2, 0.2 to 0.3, etc.  The algorithm might be slightly more complex (to separate &lt;span class="math"&gt;\(n = 0.1\)&lt;/span&gt; from &lt;span class="math"&gt;\(0.1 &amp;lt; n \leq 0.2\)&lt;/span&gt;.)&lt;/p&gt;
&lt;p&gt;[Again, the DBA steadfastly refuses to provide the use cases, so I'm doing a lot of this with minimal information.  However, I did get a spreadsheet showing an Excel version of the algorithm.  Not PL/SQL, not pure SQL, not Java, but Excel.]&lt;/p&gt;
&lt;div class="section" id="what-s-the-issue"&gt;
&lt;h2&gt;What's the Issue?&lt;/h2&gt;
&lt;p&gt;The issue is that some programmers can't be trusted to find their ass groping with both hands.  I was sent three versions of the obvious SQL query, each more contrived and senseless than the last.&lt;/p&gt;
&lt;p&gt;I was asked -- really -- &amp;quot;What is a more scalable approach to the problem ?&amp;quot;.  &amp;quot;Scalable&amp;quot;? WTF?  Scalable with respect to what?  Rows?  Physical I/O's?  Elapsed Time?  CPU use?  User queries?  Web page hits?  Shots of Tequila?  If I look at the &lt;a class="reference external" href="http://www.zifa.com/"&gt;Zachman Framework&lt;/a&gt;  or the &lt;a class="reference external" href="http://www.sei.cmu.edu/str/taxonomies/view_qm_body.html"&gt;SEI Quality Measures Taxonomy&lt;/a&gt; , I can come up with at least a half-dozen more dimensions of potential &amp;quot;scalability&amp;quot;.&lt;/p&gt;
&lt;p&gt;[Yes, I asked.  No, I didn't get an answer.  &amp;quot;Scalability&amp;quot; appears to mean the same thing as &amp;quot;Better&amp;quot;.]&lt;/p&gt;
&lt;p&gt;I'm assuming that the volume has increased or something, and the old query isn't fast enough.  Or something.  There's a claim that some query is run 83 million times each week; that's 138 times per second, a number I just don't believe.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="fetishize-a-feature"&gt;
&lt;h2&gt;Fetishize a Feature&lt;/h2&gt;
&lt;p&gt;Someone has an Oracle Bulk Bind fetish.  I've listened to this tripe before.  There are probably places where it helps.  I haven't seen any, but I haven't really made a study of the feature.   Apparently, they couldn't get it to work for the required 80,000 rows.  It gets what they call &amp;quot;the standard ORA-04030 error.&amp;quot;&lt;/p&gt;
&lt;p&gt;The sent me a copy of solution one: a big pile of PL/SQL including some BULK COLLECT stuff.  PL/SQL they couldn't get it to work.  I'm not sure what's going on here, but it's clearly the first dumb-as-a-post SQL programming technique:  &lt;strong&gt;Fetishize a Feature&lt;/strong&gt;.  You pick something and stick with it.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="drown-it-in-documentation"&gt;
&lt;h2&gt;Drown it in Documentation&lt;/h2&gt;
&lt;p&gt;Here's the best part of solution one.  It didn't work.  And they provided me with extensive documentation -- on the feature they couldn't get to work.  I like that.  So technique two is to quote a lot of documentation -- as if &lt;strong&gt;Drowning It In Documentation&lt;/strong&gt;  somehow make the feature start working.&lt;/p&gt;
&lt;p&gt;I suppose I could try and debug it, but I really don't have the patience.  There are simpler, provably faster techniques.  Why debug something that is highly Oracle-specific, and doesn't seem to work very well?&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="write-more-code"&gt;
&lt;h2&gt;Write More Code&lt;/h2&gt;
&lt;p&gt;Solution two was to purge the bulk-bind syntax from solution one and see if a big pile of PL/SQL will work.  PL/SQL is a demonstrably slow platform.  There are some anecdotal stories of applications that were made faster by replacing external application programs with PL/SQL.  I believe that those stories involve comparing the performance of a Bentley with an Etap 37S.  One's a car, the other's a boat.  PL/SQL is faster when you change your application design to make better use of PL/SQL features.&lt;/p&gt;
&lt;p&gt;In this case, the PL/SQL solution is a huge amount of code for something that is -- as far as I can tell -- a SELECT COUNT(*) GROUP BY operation.  It's hard to be completely sure, since the code is bad, and obscures the intent.&lt;/p&gt;
&lt;p&gt;Rather than summarize and simplify, they &lt;strong&gt;Wrote More Code&lt;/strong&gt;.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="don-t-do-the-obvious"&gt;
&lt;h2&gt;Don't Do The Obvious&lt;/h2&gt;
&lt;p&gt;Another generally dumb technique is to avoid writing the obvious SQL because -- well -- I don't know why.  I don't have the actual requirements.  However, each example strives to produce one line of output with the frequency table spread out horizontally.  This is fairly hard to do in SQL, and requires lots of copy and paste programming to repeat the CASE expressions over and over again.&lt;/p&gt;
&lt;p&gt;The basic SELECT COUNT(*) GROUP BY produces a number of rows, each of which has a key and a count.  This can be rotated into a horizontal configuration by a reporting program.  For some reason, we're locked into a single form for the report, making it so we can't &lt;strong&gt;Do The Obvious&lt;/strong&gt;.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="refuse-to-change-the-structure"&gt;
&lt;h2&gt;Refuse to Change the Structure&lt;/h2&gt;
&lt;p&gt;Data structures and algorithms are two complementary sides of the same coin.  You can't fix the algorithm without fixing the data structure, and vice versa.  In this case, the table design was bad, but no one seemed prepared to fix it.&lt;/p&gt;
&lt;p&gt;About a year ago, I had told a member of data cartel to read Ralph Kimball's Data Warehouse Toolkit.  They claimed they read it.  Since they got nothing out of it, I'm not sure what they meant by &amp;quot;read&amp;quot;.  Data warehouse folks know that you have to denormalize for reporting.  A relentless focus on &amp;quot;normalization&amp;quot; -- when dealing with non-updatable reporting-only data -- is simply wrong.&lt;/p&gt;
&lt;p&gt;In this case, the floating point numbers had to be split up into bins.  The calculation must be done at load time, and must be a permanent part of the table.&lt;/p&gt;
&lt;pre class="literal-block"&gt;
TABLE data(
time DATETIME,
value FLOAT );
&lt;/pre&gt;
&lt;p&gt;This isn't really sufficient for reporting.  You need something more like the following:&lt;/p&gt;
&lt;pre class="literal-block"&gt;
TABLE data(
time DATETIME,
week INTEGER,
month INTEGER,
year INTEGER,
value FLOAT,
bin INTEGER );
&lt;/pre&gt;
&lt;p&gt;The various derived values are all trivial to calculate at load time.  Once they're calculated, your query reduces to a trivial SELECT bin, COUNT(*) FROM DATA GROUP BY bin.  It isn't the absolutely fastest way to process the data, but it's a far, far sight faster than on-the-fly CASE expressions or PL/SQL loops.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="the-hubris-of-time-calculations"&gt;
&lt;h2&gt;The Hubris of Time Calculations&lt;/h2&gt;
&lt;p&gt;There's more that's wrong in the various examples I was sent.   Specifically, they use &amp;quot;closed-ended date ranges&amp;quot;.  A serious mistake that is caused by simple hubris.  Time is subtle and complex and easy to get wrong.&lt;/p&gt;
&lt;p&gt;Here's their code.&lt;/p&gt;
&lt;pre class="literal-block"&gt;
time &amp;gt;= TO_DATE( '05/01/2008 00:00:00', 'MM/DD/YYYY HH24:MI:SS') AND
time &amp;lt;= TO_DATE( '05/08/2008 23:59:59', 'MM/DD/YYYY HH24:MI:SS');
&lt;/pre&gt;
&lt;p&gt;It can't -- in general -- work.&lt;/p&gt;
&lt;p&gt;There's a 1-second gap between the two times.  You have use half-open intervals to avoid losing a row that happens to have a timestamp in the gap.  [Don't waste time adding .999's, either, because the decimal value doesn't provide down-to-the-last bit way to encode the internal binary values.]&lt;/p&gt;
&lt;pre class="literal-block"&gt;
time &amp;gt;= TO_DATE('05/01/2008','MM/DD/YYYY')
AND time &amp;lt; TO_DATE('05/08/2008','MM/DD/YY' )
&lt;/pre&gt;
&lt;p&gt;This has NO gap.&lt;/p&gt;
&lt;p&gt;However, this still isn't very good.  As shown in the table definitions above, you need to denormalize the time-stamp into the buckets you actually want to use for selection and grouping.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="real-speed"&gt;
&lt;h2&gt;Real Speed&lt;/h2&gt;
&lt;p&gt;I don't have the table or sample data, so I can't compare my results with their performance numbers.  However, their numbers are sad.&lt;/p&gt;
&lt;p&gt;First, they couldn't get the bulk bind to work, but sent me the code, as if it mattered.&lt;/p&gt;
&lt;p&gt;Second, their massive PL/SQL loop ran for an hour.  Apparently, this is unacceptable, but they sent me the code, as if it mattered.  Which is sad.&lt;/p&gt;
&lt;p&gt;Third, their SQL GROUP-BY with all the CASE expressions ran in 12 minutes.  I don't know if that's too long or uses too much memory or takes too many tequila shots.&lt;/p&gt;
&lt;p&gt;The real SELECT COUNT(*) GROUP BY, with denormalized data, is fast.  On my little 1Gb RAM, 1.7Ghz Dell, running Fedora Core 8 and using SQLite, a basic SELECT COUNT(*) processes 100,000 records in about 3 seconds.&lt;/p&gt;
&lt;p&gt;That's about as fast as this little drip of code.&lt;/p&gt;
&lt;pre class="literal-block"&gt;
import collections
    count= collections.defaultdict(lambda:0)
    for row in q.execute().fetchall():
        b, exact = divmod( int(row[1]*100), 10 )
        band= &amp;quot;==0.%d&amp;quot;%(b,) if exact == 0 else &amp;quot;0.%d-0.%d&amp;quot;%(b,b+1)
        count[band] += 1
    print count
&lt;/pre&gt;
&lt;p&gt;In SQLite, for 100,000 rows, this is the same speed as SQL.  Why?  Because we're not asking the database to do anything much more than fetch rows.&lt;/p&gt;
&lt;p&gt;Interestingly, in Oracle, the &lt;tt class="docutils literal"&gt;SELECT &lt;span class="pre"&gt;COUNT(*)&lt;/span&gt; GROUP BY&lt;/tt&gt; is much, much faster.  Why?  Because Oracle queries involve a context switch, where SQLite does not.  A simple fetch loop in Oracle is relatively slow without using some kind of buffering.&lt;/p&gt;
&lt;p&gt;The database fetch time still dominates what we're doing.  A table design change, and doing all processing at load time will minimizes the query time.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="how-many-bad-things-can-we-do"&gt;
&lt;h2&gt;How Many Bad Things Can We Do?&lt;/h2&gt;
&lt;p&gt;Let's enumerate them:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;strong&gt;Fetishize a Feature&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Drown It In Documentation&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Write More Code&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Refuse to Change the Structure&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;The Hubris of Time Calculation&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;All of these habits get in the way of a simple denormalization that makes the obvious query work at amazing speeds.&lt;/p&gt;
&lt;/div&gt;
&lt;script type='text/javascript'&gt;if (!document.getElementById('mathjaxscript_pelican_#%@#$@#')) {
    var align = "center",
        indent = "0em",
        linebreak = "false";

    if (false) {
        align = (screen.width &lt; 768) ? "left" : align;
        indent = (screen.width &lt; 768) ? "0em" : indent;
        linebreak = (screen.width &lt; 768) ? 'true' : linebreak;
    }

    var mathjaxscript = document.createElement('script');
    mathjaxscript.id = 'mathjaxscript_pelican_#%@#$@#';
    mathjaxscript.type = 'text/javascript';
    mathjaxscript.src = 'https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.3/latest.js?config=TeX-AMS-MML_HTMLorMML';

    var configscript = document.createElement('script');
    configscript.type = 'text/x-mathjax-config';
    configscript[(window.opera ? "innerHTML" : "text")] =
        "MathJax.Hub.Config({" +
        "    config: ['MMLorHTML.js']," +
        "    TeX: { extensions: ['AMSmath.js','AMSsymbols.js','noErrors.js','noUndefined.js'], equationNumbers: { autoNumber: 'none' } }," +
        "    jax: ['input/TeX','input/MathML','output/HTML-CSS']," +
        "    extensions: ['tex2jax.js','mml2jax.js','MathMenu.js','MathZoom.js']," +
        "    displayAlign: '"+ align +"'," +
        "    displayIndent: '"+ indent +"'," +
        "    showMathMenu: true," +
        "    messageStyle: 'normal'," +
        "    tex2jax: { " +
        "        inlineMath: [ ['\\\\(','\\\\)'] ], " +
        "        displayMath: [ ['$$','$$'] ]," +
        "        processEscapes: true," +
        "        preview: 'TeX'," +
        "    }, " +
        "    'HTML-CSS': { " +
        "        availableFonts: ['STIX', 'TeX']," +
        "        preferredFont: 'STIX'," +
        "        styles: { '.MathJax_Display, .MathJax .mo, .MathJax .mi, .MathJax .mn': {color: 'inherit ! important'} }," +
        "        linebreaks: { automatic: "+ linebreak +", width: '90% container' }," +
        "    }, " +
        "}); " +
        "if ('default' !== 'default') {" +
            "MathJax.Hub.Register.StartupHook('HTML-CSS Jax Ready',function () {" +
                "var VARIANT = MathJax.OutputJax['HTML-CSS'].FONTDATA.VARIANT;" +
                "VARIANT['normal'].fonts.unshift('MathJax_default');" +
                "VARIANT['bold'].fonts.unshift('MathJax_default-bold');" +
                "VARIANT['italic'].fonts.unshift('MathJax_default-italic');" +
                "VARIANT['-tex-mathit'].fonts.unshift('MathJax_default-italic');" +
            "});" +
            "MathJax.Hub.Register.StartupHook('SVG Jax Ready',function () {" +
                "var VARIANT = MathJax.OutputJax.SVG.FONTDATA.VARIANT;" +
                "VARIANT['normal'].fonts.unshift('MathJax_default');" +
                "VARIANT['bold'].fonts.unshift('MathJax_default-bold');" +
                "VARIANT['italic'].fonts.unshift('MathJax_default-italic');" +
                "VARIANT['-tex-mathit'].fonts.unshift('MathJax_default-italic');" +
            "});" +
        "}";

    (document.body || document.getElementsByTagName('head')[0]).appendChild(configscript);
    (document.body || document.getElementsByTagName('head')[0]).appendChild(mathjaxscript);
}
&lt;/script&gt;</content><category term="Python"></category><category term="database"></category><category term="jquery"></category><category term="sql"></category><category term="design"></category></entry><entry><title>The Django World-View: Model+Admin First; Built-in Transparency and Trustworthiness</title><link href="https://slott56.github.io/2008_03_24-the_django_world_view_modeladmin_first_built_in_transparency_and_trustworthiness.html" rel="alternate"></link><published>2008-03-24T18:24:00-04:00</published><updated>2008-03-24T18:24:00-04:00</updated><author><name>S.Lott</name></author><id>tag:slott56.github.io,2008-03-24:/2008_03_24-the_django_world_view_modeladmin_first_built_in_transparency_and_trustworthiness.html</id><summary type="html">&lt;p&gt;See Michael Hugos &amp;quot;&lt;a class="reference external" href="http://www.computerworld.com/action/article.do?command=viewArticleBasic&amp;amp;articleId=314557"&gt;Think about screens and the data on them to simplify system development&lt;/a&gt; &amp;quot; for some helpful insight on what an &amp;quot;application&amp;quot; really is -- access to data.  Simple transparency is lifted up as a critical value for software.&lt;/p&gt;
&lt;p&gt;I liked the &amp;quot;If you don't believe it could be this …&lt;/p&gt;</summary><content type="html">&lt;p&gt;See Michael Hugos &amp;quot;&lt;a class="reference external" href="http://www.computerworld.com/action/article.do?command=viewArticleBasic&amp;amp;articleId=314557"&gt;Think about screens and the data on them to simplify system development&lt;/a&gt; &amp;quot; for some helpful insight on what an &amp;quot;application&amp;quot; really is -- access to data.  Simple transparency is lifted up as a critical value for software.&lt;/p&gt;
&lt;p&gt;I liked the &amp;quot;If you don't believe it could be this simple, consider the reasons for your response&amp;quot; insight.  I didn't like Hugos' sample response, &amp;quot;complex code that you can brag about&amp;quot;.  I don't think complexity for the sake of complexity is a real problem.&lt;/p&gt;
&lt;p&gt;Complexity can be defined as everything that separates the user from their data.  As Hugos' notes, the data model and the simplest, most direct presentation is the best design.  Everything else is complexity that obscures the real purpose of the software.&lt;/p&gt;
&lt;p&gt;Complexity comes from several sources.  I've blogged about complexity &lt;a class="reference external" href="https://slott56.github.io/2005_09_03-why_are_things_so_complicated_7_deadly_reasons.html"&gt;before&lt;/a&gt; , ever since &lt;a class="reference external" href="http://www.mindspring.com/~mgrand/"&gt;Mark Grand&lt;/a&gt;  gave me the hint that complexity was part of the IT culture.&lt;/p&gt;
&lt;p&gt;Here are seven kinds of complexity that get between a user and their data.&lt;/p&gt;
&lt;ol class="arabic simple"&gt;
&lt;li&gt;&lt;strong&gt;&amp;quot;The Conflict Is The Problem&amp;quot;&lt;/strong&gt;. The inherent conflicts in the relationship between developers and buyers or users make the problem appear complex. Often this is because buyers insist on their solution -- irrespective of the actual problem.  Rather than describe the underlying problem, buyers describe a solution based on their favorite technology.  They insist they have to do this because the job of the business analyst is to translate the business problem to technology terms -- usually oriented around a complex non-solution.  After all, when you try to solve a business problem with a spreadsheet, you've created two business problems.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&amp;quot;Fear of Showing Weakness&amp;quot;&lt;/strong&gt;. Simplicity isn't valued.  Some aspect of the problem is (or appears) complex, so we need lots of complex software.  Many &amp;quot;business rules&amp;quot; are transient; an orientation around the decisions a person needs to make is more helpful than over-specifying something that handles 1% of the dollar value of an application.  Rather than simply expose the data (and the business process) to the people, we overdesign &amp;quot;automation&amp;quot; that makes the exceptions and special cases a larger and more complex problem than they deserve to be.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&amp;quot;Quality vs. Quantity of Ideas&amp;quot;&lt;/strong&gt;. It's hard to let go of the first idea, no matter how bad it is.  Someone with deep experience in legacy technology will often be the root cause of complexity.  Just because batch processing was once the vogue doesn't mean it is essential or even necessary.  Many, many things can be handled via an asynchronous message queue rather than an overnight batch process.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&amp;quot;Form vs. Function&amp;quot;&lt;/strong&gt;. If we fail to define the problem in the first place, we don't know what problem we're solving.  We're left applying technology inappropriately, filling in the form of a solution, manufacturing complexity because we're vague on what the actual function should be.  Rather than simply present data, we feel that application logic is &amp;quot;important&amp;quot; and should be part of the system; simple presentation of data isn't appropriate.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&amp;quot;When I Grow Up&amp;quot;&lt;/strong&gt;. If we don't have a mature process for solving problems, we're stuck applying inappropriate technology.  Often we're forced into building something prematurely, and the previous problems all surface: we lock onto the first bad idea, we don't back down from that bad idea, it fills the form of software we think we understand.&lt;/li&gt;
&lt;li&gt;&amp;quot;&lt;strong&gt;If I Had A Hammer&amp;quot;&lt;/strong&gt;. Tied in with the lack of quality ideas or well-defined problems, we make inappropriate use of tools or solution design patterns; we view all fastener problems as nails because we only understand hammers.  We have very, very sophisticated application software development tools; we don't need to write mountains of code when we have sophisticated technology stacks like Linux/Apache/MySQL/Python and Django.&lt;/li&gt;
&lt;li&gt;&amp;quot;&lt;strong&gt;How Hard Can It Be?&amp;quot;&lt;/strong&gt; Failure to assess risks appropriately biases users against a simple solution.  More programming seems -- in some views -- to be less risky.  More automation isn't a solution.  Appropriate controls are more important than volume of software.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Read Thibodeau's &amp;quot;&lt;a class="reference external" href="http://www.computerworld.com/action/article.do?command=viewArticleBasic&amp;amp;articleId=9066618"&gt;D.C.'s tax system won plaudits but couldn't stop alleged insider thefts&lt;/a&gt; &amp;quot;.  Complexity and technology aren't the answer.  Good old-fashioned controls and audits are what matters.  Audits and controls require transparency, not complexity.&lt;/p&gt;
</content><category term="Python"></category><category term="#python"></category><category term="database"></category></entry><entry><title>How Essential Is Unit Testing? Or, How Do We Make It Essential?</title><link href="https://slott56.github.io/2007_12_24-how_essential_is_unit_testing_or_how_do_we_make_it_essential.html" rel="alternate"></link><published>2007-12-24T11:31:00-05:00</published><updated>2007-12-24T11:31:00-05:00</updated><author><name>S.Lott</name></author><id>tag:slott56.github.io,2007-12-24:/2007_12_24-how_essential_is_unit_testing_or_how_do_we_make_it_essential.html</id><summary type="html">&lt;p&gt;See &lt;a class="reference external" href="http://thomas.apestaart.org/log/?p=559"&gt;Present Perfect&lt;/a&gt;  for some thoughts on unit testing.   See some other commentary on the discipline required to write Python programs in &lt;a class="reference external" href="http://panela.blog-city.com/gnome_devs_too_lazy_for_python.htm"&gt;Gnome devs too lazy for python&lt;/a&gt; .  I think I see the disconnect that makes testing appear to be too costly; I think that some basic &amp;quot;meta-quality attributes&amp;quot; are …&lt;/p&gt;</summary><content type="html">&lt;p&gt;See &lt;a class="reference external" href="http://thomas.apestaart.org/log/?p=559"&gt;Present Perfect&lt;/a&gt;  for some thoughts on unit testing.   See some other commentary on the discipline required to write Python programs in &lt;a class="reference external" href="http://panela.blog-city.com/gnome_devs_too_lazy_for_python.htm"&gt;Gnome devs too lazy for python&lt;/a&gt; .  I think I see the disconnect that makes testing appear to be too costly; I think that some basic &amp;quot;meta-quality attributes&amp;quot; are essential to understanding unit testing.&lt;/p&gt;
&lt;p&gt;Here's the original C# vs. Python analysis in &lt;a class="reference external" href="http://joeshaw.org/2007/10/28/496"&gt;Monotonous&lt;/a&gt; .&lt;/p&gt;
&lt;p&gt;Here's the famous quote: &amp;quot;Writing real applications in Python requires a discipline that unfortunately most people (including myself, at that time) are unwilling to adhere to, and this easily leads to buggy and hard to maintain programs. You have to be very diligent about unit tests and code coverage for every line of code, because you can’t rely on the compiler to catch errors for you.&amp;quot;&lt;/p&gt;
&lt;p&gt;I didn't take careful notes at a client meeting where this came up, so I don't have good quotes.  The client balked at the very idea of Test Driven Development.  In a larger presentation on testing (technology, environments, process, etc.) I had lifted up TDD as a direction that would benefit the developers.  The response from the director of development was a series of &amp;quot;how would you do that?&amp;quot; questions.&lt;/p&gt;
&lt;p&gt;These weren't practical &amp;quot;how to&amp;quot; questions.  They were rhetorical &amp;quot;that isn't possible&amp;quot; statements, framed as questions.  In order to portray TDD as impossible the questions quickly devolved into how TDD interacts with requirements gathering and business analysis; I couldn't successfully bracket the questions as part of the fringe of TDD.  I think the disconnect was their certain knowledge that test cases come only from requirements and nowhere else.&lt;/p&gt;
&lt;div class="section" id="it-hurts-when-i-do-that"&gt;
&lt;h2&gt;It Hurts When I Do That&lt;/h2&gt;
&lt;p&gt;TDD is -- certainly -- a pain the neck.  I think I see two complaints.  First, it's a lot of &amp;quot;extra&amp;quot; code.  I'm guessing that there's a &amp;quot;non-deliverable&amp;quot; view of test cases that pervades some people's thinking.  I've been measuring the lines of code in both parts of a project, and the total volume of source is about 50% test cases and 50% operational.&lt;/p&gt;
&lt;p&gt;Many years ago, we made a distinction between &amp;quot;deliverable&amp;quot; and &amp;quot;non-deliverable&amp;quot; software.  We used to carefully segregate any non-deliverable software so that we could wring our hands over how to estimate the cost for it.  Since it wasn't &amp;quot;deliverable,&amp;quot; some managers felt we couldn't charge the customer for it; the logical conclusion was that we should exclude it from our project plans.&lt;/p&gt;
&lt;p&gt;I maintained that &amp;quot;non-deliverable&amp;quot; is still &amp;quot;essential&amp;quot;, so we must include it in our plans.  The &amp;quot;compromise&amp;quot; was to inflate the estimated size of the deliverable to include a pro-rated version of the non-deliverable code.  The claim was that non-deliverable software was half the cost of deliverable software.  It had less documentation and less testing or some such.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="i-can-t-cope-with-that"&gt;
&lt;h2&gt;I Can't Cope With That&lt;/h2&gt;
&lt;p&gt;The second complaint seems to be that programmers can't be trusted.  Because they can't be trusted, we must have full definitions of all deliverables, complete up-front design, rigorous schedules for code creation, and a fungible, compressible schedule for testing.&lt;/p&gt;
&lt;p&gt;We can't engage in an agile test-driven process for a number of reasons.  First, and foremost, programmers are the root cause of scope creep.  We all know that programmers will &amp;quot;gold-plate&amp;quot; the simplest thing; they'll spend years polishing and improving something of limited business value.  [Why did we let them get started on something of limited value?  Why aren't we willing to invest in making it work correctly every time?]&lt;/p&gt;
&lt;p&gt;We can't trust programmers to do just enough design because they are lazy slobs and won't ever get anything to work.  If we try to let them fire at half-cock, they'll never get anything useful accomplished. After all, we -- as managers -- have a vision of something trivially simple.  The programmers keep introducing some technology nuance that makes a simple thing horribly complex and difficult.  [Who -- specifically -- told us that introducing new technology will be simpler?  Or did we just make that part up?]&lt;/p&gt;
&lt;p&gt;We can't trust programmers to evolve code, design and test hand-in-hand.  If we did, there'd be scope creep and they'd just play with the technology.  Worse, of course, they'd miss the schedule.&lt;/p&gt;
&lt;p&gt;We certainly can't trust programmers and end-users to collaborate.  If we did, they would change the focus of the project, and the schedule might be missed.  As managers, we don't fully understand the business value proposition; we don't completely get the technology, but we do understand the schedule.  Since we really, truly, deeply understand the calendar, that is the one thing we can manage to.  [Why is schedule more important than delivered features?]&lt;/p&gt;
&lt;p&gt;Above all, we can't trust programmers to create test cases.  Only end-users can create tests, and those tests must be married to the requirements.  There's no reason to elaborate the tests to match the design, or elaborate the tests to match the details embodied in the code.  Tests based on design or programming amount to letting a programmer do their own tests; programmers are untrustworthy; therefore this can't work.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="the-tdd-alternatives"&gt;
&lt;h2&gt;The TDD Alternatives&lt;/h2&gt;
&lt;p&gt;I think there's just one disconnect underlying this.  This disconnect manifests itself as two alternatives to TDD.  The static language folks seem to like the idea that compiler type-checking is an alternative to testing.  I suppose -- to a limited extent -- this is true.  Rather than write a unit test to examine proper integration among classes or modules, we can trust the compiler.&lt;/p&gt;
&lt;p&gt;Everyone knows -- or should know -- that the compiler is easily fooled.  When using externally developed JAR files, we can easily compile against one version, and try to execute against a different version.  All the compile-time type-checking in the world can't cope with mis-configuration.  Eventually, we need Python-style dynamic testing.&lt;/p&gt;
&lt;p&gt;When we can't trust our programmers, we have a number of clever alternatives to TDD.  The primary approach is to define a process that imposes a waterfall approach to developing unit test cases in parallel with the code.  When asked &amp;quot;How does TDD interact with requirements gathering?&amp;quot; no answer I could give was acceptable.  What they wanted me to say was &amp;quot;Oh crap, you're right, I'm such an idiot.  Test cases are only based on requirements, never design or programming.&amp;quot;  They wanted me to agree that programmers can't be allowed write their own test cases.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="the-disconnect"&gt;
&lt;h2&gt;The Disconnect&lt;/h2&gt;
&lt;p&gt;I think both viewpoints stem from looking at testing as &amp;quot;final&amp;quot;, &amp;quot;end-user&amp;quot; or &amp;quot;acceptance&amp;quot; testing.  If testing is only for acceptance, then we should have other ways to test that the programming is correct and the design really works.  The compiler should -- somehow -- validate our basic programming via static type analysis.  The design, similarly, should be checked via some static analysis.  That leaves testing to focus on the requirements and nothing else.&lt;/p&gt;
&lt;p&gt;Additionally, we can't trust one person to interpret the requirements as test cases and as code.  We must apply a second pair of eyeballs to the requirements to create the acceptance-oriented test cases.&lt;/p&gt;
&lt;p&gt;It appears to me that TDD is dismissed as worthless because people don't see a need to test their designs or programming.  Either they hope that static type analysis will do this, or they simply dismiss this testing as worthless.&lt;/p&gt;
&lt;p&gt;It's hard to create a value proposition for testing the design and programming.  It requires emphasizing a sense of distrust.  And the level of distrust is already fairly high.  After all, unit testing requires a level of discipline that programmers are unwilling to adhere to.  The idea of adding testing at the design and code level only gets into complex philosophical discussions about &amp;quot;the role of requirements&amp;quot;.&lt;/p&gt;
&lt;p&gt;It's hard to break testing free from &amp;quot;End-User Acceptance.&amp;quot;  However, if we can portray testing as essential, it then becomes deliverable.  Indeed, it becomes essential to establishing confidence in the software.  It also becomes part of the documentation, since each API is demonstrated by at least a test case (in some cases, a whole test suite.)&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="meta-quality"&gt;
&lt;h2&gt;Meta-Quality&lt;/h2&gt;
&lt;p&gt;The logical conclusion is a set of meta-quality attributes.  Software quality attributes can be based on the SEI Quality Measures Taxonomy &lt;a class="reference external" href="http://www.sei.cmu.edu/str/taxonomies/view_qm.html"&gt;http://www.sei.cmu.edu/str/taxonomies/view_qm.html&lt;/a&gt;.  This taxonomy includes need satisfaction, resource use, maintainability, adaptability and cost factors.&lt;/p&gt;
&lt;p&gt;Meta-quality includes the quality attributes of the test cases.  There are probably a number of quality attributes regarding things like 'traceability to requirements&amp;quot;, &amp;quot;class coverage&amp;quot; and &amp;quot;method coverage&amp;quot; that determine how useful and complete the test cases are.  Looking at &amp;quot;traceability&amp;quot;, we examine how the test cases apply to end-user acceptance.&lt;/p&gt;
&lt;p&gt;I look at &amp;quot;class coverage&amp;quot; as  a way to to look at the design.  This includes classical &amp;quot;class-in-isolation&amp;quot; unit tests, as well as module- (or &amp;quot;component&amp;quot; or &amp;quot;package&amp;quot;) -level unit tests that examine a collection of classes to be sure that they interact properly.  This makes limited use of mock objects, since this is looking at integration of classes and modules.&lt;/p&gt;
&lt;p&gt;The &amp;quot;method coverage&amp;quot; is how we look at the programming.  This includes appropriate test cases to exercise each method more-or-less in isolation.  This level of testing makes heavy use of mock objects to be sure that the code in each method is actually correct.&lt;/p&gt;
&lt;p&gt;I think that these meta-quality attributes of the test case code is as important as the quality attributes of the &amp;quot;operational&amp;quot; code.&lt;/p&gt;
&lt;/div&gt;
</content><category term="Python"></category><category term="#python"></category><category term="unit testing"></category></entry><entry><title>User Interface Testing</title><link href="https://slott56.github.io/2007_08_14-user_interface_testing.html" rel="alternate"></link><published>2007-08-14T10:34:00-04:00</published><updated>2007-08-14T10:34:00-04:00</updated><author><name>S.Lott</name></author><id>tag:slott56.github.io,2007-08-14:/2007_08_14-user_interface_testing.html</id><summary type="html">&lt;p&gt;The question seemed simple, which testing framework is the simplest?  The situation is complex.  There's a web application, there are developers and there are testers.  The developers develop, and the testers test.  So far, not so complex.&lt;/p&gt;
&lt;p&gt;Here's the complexity.  The testers are pretty focused on manual point-and-click testing.  They …&lt;/p&gt;</summary><content type="html">&lt;p&gt;The question seemed simple, which testing framework is the simplest?  The situation is complex.  There's a web application, there are developers and there are testers.  The developers develop, and the testers test.  So far, not so complex.&lt;/p&gt;
&lt;p&gt;Here's the complexity.  The testers are pretty focused on manual point-and-click testing.  They didn't like &lt;a class="reference external" href="http://httpunit.sourceforge.net/"&gt;HttpUnit&lt;/a&gt; , declaring it too complex.&lt;/p&gt;
&lt;div class="section" id="what-s-simpler-than-httpunit"&gt;
&lt;h2&gt;What's simpler than HttpUnit?&lt;/h2&gt;
&lt;p&gt;At first blush, my answer was to look at &lt;a class="reference external" href="http://www.openqa.org/selenium/"&gt;Selenium&lt;/a&gt; .  This is a widely-used, easily automated toolset for browser and UI testing.  But further conversation showed that this is the wrong approach.&lt;/p&gt;
&lt;p&gt;They aren't deeply interested in the kind of cross-browser testing that Selenium does well.  They're more interested in the essential functionality testing that HttpUnit does.  They need to know that the application works with the given target browser.  Articles like &amp;quot;&lt;a class="reference external" href="http://magpiebrain.com/blog/2007/01/28/selenium-rocks-and-you-dont-need-it/"&gt;Selenium rocks - and you don't need it&lt;/a&gt; &amp;quot; help to clarify this distinction between Selenium and HttpUnit&lt;/p&gt;
&lt;p&gt;My next answer was to look at &lt;a class="reference external" href="http://twill.idyll.org/"&gt;Twill&lt;/a&gt; .  Articles like the Advogato &amp;quot;&lt;a class="reference external" href="http://www.advogato.org/article/874.html"&gt;Introduction&lt;/a&gt; &amp;quot; are very compelling.&lt;/p&gt;
&lt;p&gt;It turns out, though, the real problem isn't &amp;quot;complexity&amp;quot; &lt;em&gt;per se&lt;/em&gt; .  The real problem is that the testers aren't interested in writing sophisticated test scripts.  They know the application, they know what they want to see, and they don't feel that programming is the best use of their time.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="unit-testing-101"&gt;
&lt;h2&gt;Unit Testing 101&lt;/h2&gt;
&lt;p&gt;This wasn't my idea, I'm just relaying the insight I got from the conversation.  I was busy shilling shamelessly for Twill when the real solution surfaced.&lt;/p&gt;
&lt;p&gt;The smart answer isn't to give the testers more tools.  The testers (as currently managed) don't see a need for tools.  The smart answer is to have the developers made officially responsible for unit tests, in HttpUnit (or Twill).  The developers need to put the unit tests into the source tree along with everything else.  They need to run the unit tests themselves.&lt;/p&gt;
&lt;p&gt;The official &amp;quot;testers&amp;quot; are now freed from the &amp;quot;test everything&amp;quot; requirement.  Instead, they can now do &amp;quot;guerilla testing&amp;quot; as well as review the unit test logs.&lt;/p&gt;
&lt;p&gt;At some point in time -- and at a higher level in the organization -- the testers need to be encouraged to use powerful scripting and unit testing tools as force multipliers.  They can claim that HttpUnit is too complex, but that's because they're looking at the wrong thing.&lt;/p&gt;
&lt;p&gt;They need to see that they can only point and click so fast.  A tool like Twill or HttpUnit can point and click a whole lost faster.  Until they're rewarded for speed, they don't have any incentive to master a tool.  Until they're given the incentive, every tool will be labeled as &amp;quot;too complex&amp;quot;.&lt;/p&gt;
&lt;/div&gt;
</content><category term="Python"></category><category term="#python"></category><category term="unit testing"></category></entry><entry><title>Another Dimensional Model Implementation</title><link href="https://slott56.github.io/2007_05_26-another_dimensional_model_implementation.html" rel="alternate"></link><published>2007-05-26T01:14:00-04:00</published><updated>2007-05-26T01:14:00-04:00</updated><author><name>S.Lott</name></author><id>tag:slott56.github.io,2007-05-26:/2007_05_26-another_dimensional_model_implementation.html</id><summary type="html">&lt;p&gt;The &lt;a class="reference external" href="http://sourceforge.net/projects/cubulus/"&gt;Cubulus&lt;/a&gt;  project and &lt;a class="reference external" href="http://alxtoth.webfactional.com/"&gt;Alexandru Toth&lt;/a&gt; 's page describe an &amp;quot;OLAP Aggregation Engine&amp;quot;.  It is very nice to see advanced work done on the dimensional model.&lt;/p&gt;
&lt;p&gt;The cited research dates from 1999 (V. Markl, F. Ramsak, R. Bayer, &amp;quot;Improving OLAP Performance by Multidimensional Hierarchical Clustering&amp;quot;, &lt;em&gt;Proceedings of the Intl. Database …&lt;/em&gt;&lt;/p&gt;</summary><content type="html">&lt;p&gt;The &lt;a class="reference external" href="http://sourceforge.net/projects/cubulus/"&gt;Cubulus&lt;/a&gt;  project and &lt;a class="reference external" href="http://alxtoth.webfactional.com/"&gt;Alexandru Toth&lt;/a&gt; 's page describe an &amp;quot;OLAP Aggregation Engine&amp;quot;.  It is very nice to see advanced work done on the dimensional model.&lt;/p&gt;
&lt;p&gt;The cited research dates from 1999 (V. Markl, F. Ramsak, R. Bayer, &amp;quot;Improving OLAP Performance by Multidimensional Hierarchical Clustering&amp;quot;, &lt;em&gt;Proceedings of the Intl. Database Engineering and Applications Symposium&lt;/em&gt; , pp. 165-177, 1999.)  I'm suspicious that it predates the &amp;quot;bit-mapped index&amp;quot;.&lt;/p&gt;
&lt;p&gt;It may be that this technique helps a lot with an RDBMS that doesn't support the star schema via bit-mapped indexes.  It may be that this technique only helps a little with a more modern RDBMS.&lt;/p&gt;
&lt;p&gt;However, the idea of a nice, tidy Python application that helps manipulate the dimensional model is a great thing.&lt;/p&gt;
</content><category term="Python"></category><category term="#python"></category><category term="database"></category></entry><entry><title>Just for a moment, I though I'd found something SQLAlchemy doesn't do perfectly.</title><link href="https://slott56.github.io/2007_05_18-just_for_a_moment_i_though_id_found_something_sqlalchemy_doesnt_do_perfectly.html" rel="alternate"></link><published>2007-05-18T17:40:00-04:00</published><updated>2007-05-18T17:40:00-04:00</updated><author><name>S.Lott</name></author><id>tag:slott56.github.io,2007-05-18:/2007_05_18-just_for_a_moment_i_though_id_found_something_sqlalchemy_doesnt_do_perfectly.html</id><summary type="html">&lt;p&gt;After having written a number of application-specific object-relational mappers, I have been on the prowl for an elegant, enduring solution.  I had started to come to grips with &lt;a class="reference external" href="http://www.djangoproject.com/"&gt;Django&lt;/a&gt; , and like much of the approach.  Django has a tiny infrastructure feature (the settings.py file) which made it unpleasant to …&lt;/p&gt;</summary><content type="html">&lt;p&gt;After having written a number of application-specific object-relational mappers, I have been on the prowl for an elegant, enduring solution.  I had started to come to grips with &lt;a class="reference external" href="http://www.djangoproject.com/"&gt;Django&lt;/a&gt; , and like much of the approach.  Django has a tiny infrastructure feature (the settings.py file) which made it unpleasant to separate the ORM from the rest of the framework.  (Not impossible, just fleetingly unpleasant.)&lt;/p&gt;
&lt;p&gt;My first look at SQLAlchemy made it look over the top.  However, after the PyCon 2007 presentation, I realized that the layers were cleanly separated, and I could use the ORM without messing about in the SQL-in-Python-Notation layer.&lt;/p&gt;
&lt;p&gt;Then, I figured out (&amp;quot;&lt;a class="reference external" href="../C465799452/E20070322201220/index.html"&gt;PL/SQL vs. Java, Which One is Really Faster?&lt;/a&gt; &amp;quot;) that stored procedures were slow.  Given that PL/SQL is slow, what else in the RDBMS world is slow?  How much SQL is too much SQL, when speed matters?  That answer is forthcoming -- I'm still fussing around with experiments.&lt;/p&gt;
&lt;div class="section" id="problem-child"&gt;
&lt;h2&gt;Problem Child&lt;/h2&gt;
&lt;p&gt;The central issue started out as the all-too-common situation of &lt;strong&gt;Disjoint Subentities&lt;/strong&gt;.  This is where a single table has distinct classes of entities.  The usual symptoms of this are indicators or NULL columns.  Often, both are used.  Sometimes, the indicator is omitted, and the pattern of NULLs has to be used to discriminate among the entity classes.&lt;/p&gt;
&lt;p&gt;In this specific experiment, a single table has two subentities, each with different granularity.  One subentity has to be summed to match the grain of the other.  This gives us a union of two kinds of SQL queries: detailed and summary.&lt;/p&gt;
&lt;p&gt;The detailed query, in SQLAlchemy, looks like this:&lt;/p&gt;
&lt;pre class="literal-block"&gt;
qrySingle= select(
    [stuff.c.groupName,stuff.c.amount,literal(1)],
    and_(stuff.c.status=='unmatched',
        stuff.c.subtype=='single'))
&lt;/pre&gt;
&lt;p&gt;In SQL, this is&lt;/p&gt;
&lt;pre class="literal-block"&gt;
SELECT &amp;quot;stuff&amp;quot;.&amp;quot;groupName&amp;quot;, &amp;quot;stuff&amp;quot;.amount, ?
FROM &amp;quot;stuff&amp;quot; WHERE &amp;quot;stuff&amp;quot;.status = ? AND &amp;quot;stuff&amp;quot;.subtype = ?
&lt;/pre&gt;
&lt;p&gt;This is precisely the SQL that would be coded &amp;quot;by hand&amp;quot;.  The literals (1, 'unmatched' and 'single') are bound into the SQL at run-time.&lt;/p&gt;
&lt;p&gt;The summary query looks like this in SQLAlchemy.&lt;/p&gt;
&lt;pre class="literal-block"&gt;
qryMulti= select(
     [stuff.c.groupName,func.sum(stuff.c.amount),literal(2)],
     and_(stuff.c.status=='unmatched',
             stuff.c.subtype=='multi'),
     group_by=[stuff.c.groupName])
&lt;/pre&gt;
&lt;p&gt;And produces the following SQL.&lt;/p&gt;
&lt;pre class="literal-block"&gt;
SELECT &amp;quot;stuff&amp;quot;.&amp;quot;groupName&amp;quot;, sum(&amp;quot;stuff&amp;quot;.amount), ?
FROM &amp;quot;stuff&amp;quot;
WHERE &amp;quot;stuff&amp;quot;.status = ? AND &amp;quot;stuff&amp;quot;.subtype = ?
GROUP BY &amp;quot;stuff&amp;quot;.&amp;quot;groupName&amp;quot;
&lt;/pre&gt;
&lt;p&gt;This is all very pleasant.  You can see that the literals (2, 'unmatched', 'multi') are bound in at run-time.  This technique often leads to a speed-up because the SQL statement can be reused by the RDBMS.  When coding by hand, this is easily overlooked.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="real-world"&gt;
&lt;h2&gt;Real World&lt;/h2&gt;
&lt;p&gt;In the &amp;quot;real world&amp;quot;, that is, the world of my clients, this kind of query is distressingly common.  And doing simulations and architectural recommendations is often made complex by having to cope with these kind of table designs.&lt;/p&gt;
&lt;p&gt;To work with this table, I needed a union, and (for a brief time) SQLAlchemy couldn't generate the correct SQL.&lt;/p&gt;
&lt;p&gt;Here's my union in SQLAlchemy.&lt;/p&gt;
&lt;pre class="literal-block"&gt;
invQry= union( qrySingle, qryMulti )
&lt;/pre&gt;
&lt;p&gt;Here's the SQL which was generated.  Note that the GROUP-BY vanished.&lt;/p&gt;
&lt;pre class="literal-block"&gt;
SELECT &amp;quot;stuff&amp;quot;.&amp;quot;groupName&amp;quot;, &amp;quot;stuff&amp;quot;.amount, ?
FROM &amp;quot;stuff&amp;quot;
WHERE &amp;quot;stuff&amp;quot;.status = ? AND &amp;quot;stuff&amp;quot;.subtype = ?
UNION SELECT &amp;quot;stuff&amp;quot;.&amp;quot;groupName&amp;quot;, sum(&amp;quot;stuff&amp;quot;.amount), ?
FROM &amp;quot;stuff&amp;quot;
WHERE &amp;quot;stuff&amp;quot;.status = ? AND &amp;quot;stuff&amp;quot;.subtype = ?
&lt;/pre&gt;
&lt;p&gt;Very disappointing.  However, it's since been fixed.  And the amazing speed of that fix is more reason to love SQLAlchemy and the folks who support it.  Many thanks!&lt;/p&gt;
&lt;p&gt;Now we can continue investigating which is faster: &amp;quot;Pure SQL&amp;quot; (i.e., complex stored procedures) or some programming language which uses SQL as necessary for persistence.&lt;/p&gt;
&lt;/div&gt;
</content><category term="Python"></category><category term="#python"></category><category term="database"></category></entry><entry><title>Dejavu and Python-based Dimensional Analysis</title><link href="https://slott56.github.io/2007_03_13-dejavu_and_python_based_dimensional_analysis.html" rel="alternate"></link><published>2007-03-13T10:14:00-04:00</published><updated>2007-03-13T10:14:00-04:00</updated><author><name>S.Lott</name></author><id>tag:slott56.github.io,2007-03-13:/2007_03_13-dejavu_and_python_based_dimensional_analysis.html</id><summary type="html">&lt;p&gt;Actually, the code looks like a clever expansion
on my example, in &lt;a class="reference external" href="https://slott56.github.io/2007_02_26-pycon_2007_revised.html"&gt;PyCon 2007
(Revised)&lt;/a&gt; .&lt;/p&gt;
&lt;p&gt;&amp;quot;But wait,&amp;quot; you
say.  &amp;quot;Creating a pivot table in
Python?&amp;quot;&lt;/p&gt;
&lt;p&gt;Of course.  Spreadsheets can
create pivot tables from dimensionally normalized data.  However, getting the
data in this form is often challenging and if there is …&lt;/p&gt;</summary><content type="html">&lt;p&gt;Actually, the code looks like a clever expansion
on my example, in &lt;a class="reference external" href="https://slott56.github.io/2007_02_26-pycon_2007_revised.html"&gt;PyCon 2007
(Revised)&lt;/a&gt; .&lt;/p&gt;
&lt;p&gt;&amp;quot;But wait,&amp;quot; you
say.  &amp;quot;Creating a pivot table in
Python?&amp;quot;&lt;/p&gt;
&lt;p&gt;Of course.  Spreadsheets can
create pivot tables from dimensionally normalized data.  However, getting the
data in this form is often challenging and if there is any manual operation at
all, the data quality is immediately
suspect.&lt;/p&gt;
&lt;p&gt;To have perfect transparency
-- with no possibility of manual transformations -- you need a simple
application program which reliably, auditably, and testably produces the correct
data.  Further, you want to reduce the manual operations to formatting and
presentation.  The ideal solution is to produce the data in the required pivot
table so that it can be loaded into a spreadsheet for display
only.&lt;/p&gt;
&lt;p&gt;With an object-relational mapper,
you can write a tidy query to fetch raw data, and compute a aggregate along two
dimensions.  You then assemble result columns on one dimension and rows on the
other dimension.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Elegant -- But Dirty -- Pool.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;The Elegant
Thing that makes this work pleasantly and simply in Python is being able to use
a tuple as the key to a mapping.  I can't say enough good things about this
simple, elegant piece of Pythonic programming.  You can easily handle complex,
multi-column keys in each dimension of the pivot table, by simply creating a
tuple of key values, and using a pair of tuples to locate the appropriate cell
in a mapping.&lt;/p&gt;
&lt;p&gt;Things like dimensional
conformance often create a gnarly algorithm in Java or -- shudder -- COBOL.  In
Python, it's a tuple that you can use to locate the dimension value in a
dictionary.  It works for everything except the Customer dimension, which in
some applications is too huge to retain in a simple in-memory
mapping.&lt;/p&gt;
&lt;p&gt;The graceful elegance of
&lt;strong&gt;Python's Mapping Indexed By A Tuple™&lt;/strong&gt;  (MXT) can really prevent a lot of
brain-cramping bugs.&lt;/p&gt;
</content><category term="Python"></category><category term="#python"></category><category term="database"></category></entry><entry><title>What a Data Warehouse Can Never Do</title><link href="https://slott56.github.io/2007_01_12-what_a_data_warehouse_can_never_do.html" rel="alternate"></link><published>2007-01-12T14:40:00-05:00</published><updated>2007-01-12T14:40:00-05:00</updated><author><name>S.Lott</name></author><id>tag:slott56.github.io,2007-01-12:/2007_01_12-what_a_data_warehouse_can_never_do.html</id><summary type="html">&lt;p&gt;In one form, the question is &amp;quot;How do we handle
the [X] transaction in the warehouse?&amp;quot;  Another form of the question is &amp;quot;What do
we do when [Y] changes?&amp;quot;   The third form is less clear, but essentially the
same: &amp;quot;How do we maintain [Z] in the
warehouse?&amp;quot;&lt;/p&gt;
&lt;p&gt;All of these …&lt;/p&gt;</summary><content type="html">&lt;p&gt;In one form, the question is &amp;quot;How do we handle
the [X] transaction in the warehouse?&amp;quot;  Another form of the question is &amp;quot;What do
we do when [Y] changes?&amp;quot;   The third form is less clear, but essentially the
same: &amp;quot;How do we maintain [Z] in the
warehouse?&amp;quot;&lt;/p&gt;
&lt;p&gt;All of these are questions
that superficially cover change management, but we're not really talking about
Kimball's &lt;strong&gt;Slowly Changing Dimension&lt;/strong&gt;  (SCD) design pattern.  It turns out,
we're talking about something more subtle and
confusing.&lt;/p&gt;
&lt;div class="section" id="system-of-record"&gt;
&lt;h2&gt;System of Record&lt;/h2&gt;
&lt;p&gt;The real questions are &lt;strong&gt;System of Record&lt;/strong&gt;  (SoR) questions.  In short, each
question is a version of &amp;quot;Where's the authoritative copy, and how do I keep it
current?&amp;quot;&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;The [X] transaction is, at least in
theory, part of a source application, a System of Record.  It is extracted from
SoR, transformed, and loaded into the warehouse.  The [X] transaction does not
change when the warehouse is implemented.  Unless, of course, there is no
SoR.&lt;/li&gt;
&lt;li&gt;The changes to [Y], similarly, should be
made in the SoR.  This is almost the same question as the &amp;quot;[X] transaction&amp;quot;
question, but it's asked about a piece of data, not a named business process.
The distinction reveals much about the processes which the warehouse must
support.&lt;/li&gt;
&lt;li&gt;The maintenance of [Z], clearly, should
be made in the SoR.  This is similar to the &amp;quot;changes to [Y]&amp;quot; question, but shows
a different point of view on what data is and why it
exists.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;We'll look at these questions in a bit of depth.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="transactions-in-the-warehouse"&gt;
&lt;h2&gt;Transactions in the Warehouse&lt;/h2&gt;
&lt;p&gt;When someone asks
about the &amp;quot;[X] transaction,&amp;quot; they're often summarizing a business process.  In
general, every business process is either informal, or formalized.  Informal
transactions are done manually using desktop tools: email, spreadsheet, word
processing, etc.  No piece of software captures, manages and enforces the
transaction.&lt;/p&gt;
&lt;p&gt;Formal transactions have
three general patterns for their SoR: one SoR, many SoR's and a badly-chosen
SoR.  When there's one SoR, life is good.  The transaction happens in some
system (SAP, Oracle, QuickBooks, Aptiva, etc.)  It propagates through the
organization through ordinary Enterprise Application Integration (EAI)
techniques.  It winds up in the warehouse through ordinary
Extract-Transform-Load (ETL) processing.&lt;/p&gt;
&lt;p&gt;When there are multiple
SoR's, we have some challenges.  Sometimes, the relationship is &lt;em&gt;horizontal&lt;/em&gt;:
two peer business units have separate sources for similar data.  One unit has
SAP, the other has Aptiva.  This means that there may be common data which must
be conformed into a warehouse dimension.  So far, so good.&lt;/p&gt;
&lt;p&gt;Sometimes the relationship between SoR's is &lt;em&gt;vertical&lt;/em&gt;:
the parent company uses SAP, the subsidiary uses Great Plains.   This means that
there may be contradictions between the views of the common data.  When data is
moved up from the subsidiary, it may be aggregated: business entities are
elided, and the data is difficult (or impossible) to
conform.&lt;/p&gt;
&lt;p&gt;Sometimes the relationship between SoR's is &lt;em&gt;psychotic&lt;/em&gt;.
This often leads to a badly-chosen SoR.  A single organization can have the same
data in two applications and neither can be trusted to be the SoR.  They may
have customer data in Siebel and JDE, and the data is different, and can only be
reconciled manually.  Sigh.  No amount of Data Warehouse ETL can sort this out.
The organization must pick something as the SoR, and revise their business
processes to reflect that.&lt;/p&gt;
&lt;p&gt;In summary,
there are no transactions in the warehouse.  Transactions happen in the SoR, and
the results of those transactions are applied to the warehouse.  You must pick
an SoR.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="change-in-the-warehouse"&gt;
&lt;h2&gt;Change in the Warehouse&lt;/h2&gt;
&lt;p&gt;Sometimes, there is no System of Record.  There are two common cases: the data is maintained manually,
and the data is maintained through a cryptic transaction buried in the legacy
reporting application.  When data is maintained manually, we have a rather
difficult &lt;strong&gt;Master Data Management&lt;/strong&gt;  (MDM) issue because we don't have
an official SoR.  We're often in a bad position, here, because we're forced to
stop data warehouse development work to put a SoR in place.  This extra work can
be hard to justify; managers say &amp;quot;we never needed a system for that before, why now?&amp;quot;&lt;/p&gt;
&lt;p&gt;The answer is simple, but
unpleasant.  &amp;quot;It never worked before, either.&amp;quot;  People put in data warehouses
because their legacy reporting tools are incorrect or inconsistent.  One root
cause of errors is lack of a public, well-understood truth because of manual or
informal changes.&lt;/p&gt;
&lt;p&gt;The cryptic
transaction is the worst thing to ferret out.  Let's say we have two
applications, B and C, which each do parts of a business function.  Further, each
has it's little quirks, and we periodically must reconcile B and C's results
against each other.  How do we do this reconciliation when the two applications
are largely disjoint except where they have to be
reconciled?&lt;/p&gt;
&lt;p&gt;The usual solution is to
merge the data into a kind of data warehouse.  However, when there are
reconciliation problems, we hate to make a change to B or C, and re-run the
complete ETL cycle.  Instead we make the change directly in the warehouse.  Who
wants to duplicate this change in B or C?  No one, so we back-propagate the
change from the warehouse into the SoR's.  In effect, we've made the warehouse
the SoR.&lt;/p&gt;
&lt;p&gt;In summary, change in the
warehouse is limited to a historical snapshot of change in the SoR.  Change
happens in the SoR, and the results of the changes are applied to the warehouse.
You must pick an SoR.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="maintenance-in-the-warehouse"&gt;
&lt;h2&gt;Maintenance in the Warehouse&lt;/h2&gt;
&lt;p&gt;The question of
maintaining data in the warehouse usually stems from a warehouse design which
involves something more complex than simple facts and dimensions.  Generally, a
bridge table (often for a hierarchy) becomes a source of confusion.  Most
business entities (dates, accounts, products, documents, etc.) are pretty clear
in the source applications.  The facts are usually
obvious.&lt;/p&gt;
&lt;p&gt;It's the reporting
relationships that get confusing.  Something like product family can be a very
difficult thing to handle.  Something like a bill of materials (BoM) or
Organization Hierarchy (OH) can be even more
complex.&lt;/p&gt;
&lt;p&gt;In the product family case,
the reporting is an organization fiction.  It doesn't tie back to anything
except how managers chunk information.  In this case, the reporting hierarchy is
entirely a feature of the warehouse itself.  This is the pure Master Data
Management problem, where business entities are grouped in the warehouse
exclusively for the user's
convenience.&lt;/p&gt;
&lt;p&gt;In the BoM or OH case,
however, the reporting hierarchy does have an independent existence.  In the
case of the BoM, it ties to engineering or product configuration.  In the case
of OH, it ties to some project structure or accounting structure.  However,
these hierarchical structures don't often exist in the same simple form that
they do in the warehouse bridge table.  And this leads to confusion on how we
maintain the bridge table.&lt;/p&gt;
&lt;p&gt;In summary,
maintenance in the warehouse is limited to loading a historical snapshot of the
relationships in the SoR.  Maintenance happens in the SoR, and the results of
the maintenance are applied to the warehouse.  You must depend on an SoR.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="bridge-tables-maintenance"&gt;
&lt;h2&gt;Bridge Tables Maintenance&lt;/h2&gt;
&lt;p&gt;There are several
varieties of Bridge Tables.  We'll address hierarchy, since it seems to lead to
the most confusion.  We'll touch on minidimension and outrigger tables, also,
since the same design pattern applies to those.&lt;/p&gt;
&lt;p&gt;The essential worry about
hierarchies stems from the fact that a hierarchy bridge table can have many more
rows than the  dimension it bridges.  Generally, it's an &lt;span class="math"&gt;\(n \log(n)\)&lt;/span&gt;
kind of multiplication, where &lt;span class="math"&gt;\(\log(n)\)&lt;/span&gt; is an estimate of the depth of the hierarchy.&lt;/p&gt;
&lt;p&gt;As a practical matter,
moving one child to another parent is a single row change in the original data.
However, the expansion in the bridge table means that &lt;span class="math"&gt;\(2d\)&lt;/span&gt; rows will change, where &lt;span class="math"&gt;\(d\)&lt;/span&gt; is
the depth of the node in the hierarchy.  For some reason, this is intimidating.&lt;/p&gt;
&lt;p&gt;There are two solutions:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;Reload the entire bridge table with each
source change.  This is easy to implement but slow.  If you use SCD change
tracking, you'll have lots of nearly identical rows that are labeled with change
dates because they were associated with a source node change.&lt;/li&gt;
&lt;li&gt;Recompute just the changed parentage,
updating only those rows of the bridge table.  This is not significantly more
complex.  First, write a &amp;quot;find-all-parents&amp;quot; function, and apply this across
every element of the source data to populate the bridge initially.  Then, you
can use the &amp;quot;find-all-parents&amp;quot; function to compute just the relevant bridge
table changes when a source node changes.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;A similar pattern is appropriate for
minidimensions and outriggers, which are based on subsets of a dimension.  The
lazy approach is to rebuild these each time the dimension changes.  A slightly
more efficient approach is to derive just the changed rows from the changes in
the dimension.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="bottom-line"&gt;
&lt;h2&gt;Bottom Line&lt;/h2&gt;
&lt;p&gt;Change doesn't happen in the warehouse.  Change happens in the SoR.
The warehouse merely captures the effect of that change.&lt;/p&gt;
&lt;/div&gt;
&lt;script type='text/javascript'&gt;if (!document.getElementById('mathjaxscript_pelican_#%@#$@#')) {
    var align = "center",
        indent = "0em",
        linebreak = "false";

    if (false) {
        align = (screen.width &lt; 768) ? "left" : align;
        indent = (screen.width &lt; 768) ? "0em" : indent;
        linebreak = (screen.width &lt; 768) ? 'true' : linebreak;
    }

    var mathjaxscript = document.createElement('script');
    mathjaxscript.id = 'mathjaxscript_pelican_#%@#$@#';
    mathjaxscript.type = 'text/javascript';
    mathjaxscript.src = 'https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.3/latest.js?config=TeX-AMS-MML_HTMLorMML';

    var configscript = document.createElement('script');
    configscript.type = 'text/x-mathjax-config';
    configscript[(window.opera ? "innerHTML" : "text")] =
        "MathJax.Hub.Config({" +
        "    config: ['MMLorHTML.js']," +
        "    TeX: { extensions: ['AMSmath.js','AMSsymbols.js','noErrors.js','noUndefined.js'], equationNumbers: { autoNumber: 'none' } }," +
        "    jax: ['input/TeX','input/MathML','output/HTML-CSS']," +
        "    extensions: ['tex2jax.js','mml2jax.js','MathMenu.js','MathZoom.js']," +
        "    displayAlign: '"+ align +"'," +
        "    displayIndent: '"+ indent +"'," +
        "    showMathMenu: true," +
        "    messageStyle: 'normal'," +
        "    tex2jax: { " +
        "        inlineMath: [ ['\\\\(','\\\\)'] ], " +
        "        displayMath: [ ['$$','$$'] ]," +
        "        processEscapes: true," +
        "        preview: 'TeX'," +
        "    }, " +
        "    'HTML-CSS': { " +
        "        availableFonts: ['STIX', 'TeX']," +
        "        preferredFont: 'STIX'," +
        "        styles: { '.MathJax_Display, .MathJax .mo, .MathJax .mi, .MathJax .mn': {color: 'inherit ! important'} }," +
        "        linebreaks: { automatic: "+ linebreak +", width: '90% container' }," +
        "    }, " +
        "}); " +
        "if ('default' !== 'default') {" +
            "MathJax.Hub.Register.StartupHook('HTML-CSS Jax Ready',function () {" +
                "var VARIANT = MathJax.OutputJax['HTML-CSS'].FONTDATA.VARIANT;" +
                "VARIANT['normal'].fonts.unshift('MathJax_default');" +
                "VARIANT['bold'].fonts.unshift('MathJax_default-bold');" +
                "VARIANT['italic'].fonts.unshift('MathJax_default-italic');" +
                "VARIANT['-tex-mathit'].fonts.unshift('MathJax_default-italic');" +
            "});" +
            "MathJax.Hub.Register.StartupHook('SVG Jax Ready',function () {" +
                "var VARIANT = MathJax.OutputJax.SVG.FONTDATA.VARIANT;" +
                "VARIANT['normal'].fonts.unshift('MathJax_default');" +
                "VARIANT['bold'].fonts.unshift('MathJax_default-bold');" +
                "VARIANT['italic'].fonts.unshift('MathJax_default-italic');" +
                "VARIANT['-tex-mathit'].fonts.unshift('MathJax_default-italic');" +
            "});" +
        "}";

    (document.body || document.getElementsByTagName('head')[0]).appendChild(configscript);
    (document.body || document.getElementsByTagName('head')[0]).appendChild(mathjaxscript);
}
&lt;/script&gt;</content><category term="Python"></category><category term="#python"></category><category term="database"></category></entry><entry><title>Refactoring and Unit Testing</title><link href="https://slott56.github.io/2006_10_11-refactoring_and_unit_testing.html" rel="alternate"></link><published>2006-10-11T00:19:00-04:00</published><updated>2006-10-11T00:19:00-04:00</updated><author><name>S.Lott</name></author><id>tag:slott56.github.io,2006-10-11:/2006_10_11-refactoring_and_unit_testing.html</id><summary type="html">&lt;p&gt;I do a fair amount of manual refactoring.  I've
used WebSphere Studio (Eclipse) to do some automated refactoring, so I have some
experience in using IDE's which exploit Java's static type-checking.&lt;/p&gt;
&lt;p&gt;However, the question of
type checking in a dynamic language is interesting.  I don't use a sophisticated
IDE for …&lt;/p&gt;</summary><content type="html">&lt;p&gt;I do a fair amount of manual refactoring.  I've
used WebSphere Studio (Eclipse) to do some automated refactoring, so I have some
experience in using IDE's which exploit Java's static type-checking.&lt;/p&gt;
&lt;p&gt;However, the question of
type checking in a dynamic language is interesting.  I don't use a sophisticated
IDE for Python development.  So, I have limited experience using an IDE to do
refactoring in a dynamic language.&lt;/p&gt;
&lt;p&gt;However, JB notes&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&amp;quot;I'm having
some heartburn about hierarchical type
systems as a way of determining 1)
conformability and 2) managing commitments of
semantically equivalent behavior.&lt;/p&gt;
&lt;p&gt;...&lt;/p&gt;
&lt;p&gt;Seems a constraining way to do it, to me, not
that I have a better alternative at the
moment.&amp;quot;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;div class="section" id="duck-typing"&gt;
&lt;h2&gt;Duck Typing&lt;/h2&gt;
&lt;p&gt;Since Python relies on
&lt;a class="reference external" href="http://en.wikipedia.org/wiki/Duck_typing"&gt;Duck
Typing&lt;/a&gt; , refactoring takes on an interesting new dimension.  We aren't
constrained to simply shuffle methods up and down the class hierarchy.  We are
now able to -- well -- put a method just about anywhere.&lt;/p&gt;
&lt;p&gt;Further, since Python doesn't have a
simple, single-inheritance model, the &amp;quot;hierarchical&amp;quot; type system doesn't
completely apply.&lt;/p&gt;
&lt;p&gt;For these reasons,
refactoring in Python is one potentially complex problem.&lt;/p&gt;
&lt;p&gt;From what I understand of
Ruby, you can override a class method without creating a subclass, essentially
redefining a base class in some obscure way.  This gives me the willies because
it makes refactoring a problem without any sensible boundaries.  Maybe I'm
misunderstanding Ruby, and have this wrong.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="essential-use-cases"&gt;
&lt;h2&gt;Essential Use Cases&lt;/h2&gt;
&lt;p&gt;The principal refactoring
use case involves moving a common method up the inheritance hierarchy.  As a
practical matter, this does happen once in a while.&lt;/p&gt;
&lt;p&gt;An additional text-book
refactoring use case arises when we're adding or removing whole methods in the
subclass hierarchy.&lt;/p&gt;
&lt;p&gt;The fringe use case is a variation on the theme of &lt;tt class="docutils literal"&gt;SubClass1.methodA&lt;/tt&gt;
looking a lot like &lt;tt class="docutils literal"&gt;Subclass2.methodA&lt;/tt&gt;,
but they're not the same.  There are two interesting cases.&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;tt class="docutils literal"&gt;SC1.mA()&lt;/tt&gt; is a superset of &lt;tt class="docutils literal"&gt;SC2.mA()&lt;/tt&gt;.
All of &lt;tt class="docutils literal"&gt;SC2.mA()&lt;/tt&gt; &lt;cite&gt;gets refactored up, and ``SC1.mA()`&lt;/cite&gt;
overrides it to add features.&lt;/li&gt;
&lt;li&gt;&lt;tt class="docutils literal"&gt;SC1.mA()&lt;/tt&gt; overlaps with &lt;tt class="docutils literal"&gt;SC2.mA()&lt;/tt&gt;.
Some common functionality has to get extracted, moved up the hierarchy;
&lt;tt class="docutils literal"&gt;SC1.mA()&lt;/tt&gt; and &lt;tt class="docutils literal"&gt;SC2.mA()&lt;/tt&gt; are rebuilt around this common kernel.&lt;/li&gt;
&lt;li&gt;&lt;tt class="docutils literal"&gt;SC1.mA()&lt;/tt&gt; has no usable relationship with &lt;tt class="docutils literal"&gt;SC2.mA()&lt;/tt&gt;.
What now?  In some cases, a complete change in design may be called for.  The mere
presence of this situation is diagnostic of the designer having missed
something.&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class="section" id="beyond-the-fringe"&gt;
&lt;h2&gt;Beyond the Fringe&lt;/h2&gt;
&lt;p&gt;Outside the fringe of
ordinary refactoring are the &lt;strong&gt;New Design Pattern&lt;/strong&gt;™ situations.  Mostly, these are
&lt;strong&gt;Strategy&lt;/strong&gt; situations, where what looks -- initially -- like a variant method grows into a
different approach as we learn more about the
solution.&lt;/p&gt;
&lt;p&gt;Consider a pair of ordinary
Entity classes that look like different entities because of different behavior.
However, they have the same attributes, and almost identical methods.  The only
difference is one algorithm.  This can be done through inheritance, but
sometimes that variant algorithm is only the tip of the iceberg, and there is
more variability just below the
surface.&lt;/p&gt;
&lt;p&gt;At this point, we realize we need a
&lt;strong&gt;Strategy&lt;/strong&gt; hierarchy to contain the variant algorithms, not a hierarchy of ordinary
Entities.  How does refactoring work here, where we're moving the functionality
out of a class hierarchy into a different class hierarchy?  Is this even
refactoring, or is it the more general case of redesign?&lt;/p&gt;
&lt;p&gt;It doesn't feel like
refactoring because  we aren't shuffling methods up and down the class
hierarchy.  In Python, the Duck Typing means we don't actually need a proper
hierarchy for the &lt;strong&gt;Strategy&lt;/strong&gt; class definitions.  Consequently, we're free to make significant structural
changes that I don't think an IDE can ever help with.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="across-the-spectrum"&gt;
&lt;h2&gt;Across the Spectrum&lt;/h2&gt;
&lt;p&gt;One potential problem
with this is captured in the comment that &amp;quot;[the compiler] can’t help but
see your code as a pile of text&amp;quot;.  By extension, then, the IDE can't do anything
more than treat source as text, losing precious semantic information.&lt;/p&gt;
&lt;p&gt;However, when doing a fundamental
restructuring (from class methods to Strategy hierarchy), the source code
information available to the compiler (or the IDE) isn't of much value until
you've finished.  Nothing helps you when you're in the middle of this.  Until
the semantic information exists, no IDE can help you manage and maintain the
semantic information.&lt;/p&gt;
&lt;p&gt;There are parts of design (and redesign) that are hard.  I think anyone would agree that the
earliest phases of noodling around about a problem are done without benefit of
an IDE or formal semantics.  When the design is merely conceptual, tools can't
help.&lt;/p&gt;
&lt;p&gt;I think refactoring includes a very broad spectrum.  At one end, things are essentially mechanical; at the
other end things, are completely conceptual.  This isn't really a problem that
needs a solution; it doesn't need tools.  It's part of the game of moving from a
good idea to software.  Some parts of the good idea don't have formal semantics.
Eventually, when formal semantics exist, tools can be
applied.&lt;/p&gt;
&lt;p&gt;In the case of Python, I suspect that IDE support for refactoring could only be feeble at best.  The
mechanical end of the spectrum is so easy that tools aren't required.   At the
conceptual end of the spectrum, tools don't help in the first place.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="the-middle-ground"&gt;
&lt;h2&gt;The Middle Ground&lt;/h2&gt;
&lt;p&gt;One might argue that simple, mechanical refactoring can be aided by the presence of static type
declarations.  However, my experience is that this covers only the most mundane
of the refactoring use cases.  In Python, we just move the method around.&lt;/p&gt;
&lt;p&gt;In Python, there's a double whammy:
checking types can't be done because the language traditionally lacked type
declarations.  Further -- and more important -- type checking doesn't need to be
done because the language is dynamic.  It can't be done, and even if it could,
it didn't matter anyway.&lt;/p&gt;
&lt;p&gt;This is overly
simplistic, however.  There is some type checking which can be done in Python.
The &lt;a class="reference external" href="http://epydoc.sourceforge.net/"&gt;epydoc&lt;/a&gt;  package does considerable analysis of
source as part of writing documentation.  It spots unused arguments, and can
spot certain kinds of obvious mismatches in number of arguments vs. parameters.&lt;/p&gt;
&lt;p&gt;When we look at JB's point on
committing to specific semantics, we see something even more profound.  It goes
way beyond what even Java is capable of checking or
automating.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="the-formal-specification"&gt;
&lt;h2&gt;The Formal Specification&lt;/h2&gt;
&lt;p&gt;JB is asking for a
level beyond syntax, beyond type matching and off into &amp;quot;intent&amp;quot;.  JB appears to
be looking for formal assertions of preconditions and postconditions that he can
use to determine how to redesign methods to make them refactorable, and then how
to refactor the changed design.&lt;/p&gt;
&lt;p&gt;JB's formality would be a nice thing to capture.  If every statement had a proper
precondition and postcondition, then we could prove almost anything about our
software except whether or not the loops actually terminated.  (That can't be
formally proven in a system with the same expressive power as software, it
requires more sophisticated logical tools.)&lt;/p&gt;
&lt;p&gt;Since Java and Python have
added additional markers (annotations and decorators) JB's assertions could be
captured, to an extent.  You'd have to implement a simple &amp;quot;for all&amp;quot; and &amp;quot;there
exists&amp;quot; predicate, but Python has a nice reduce that can be paired with a lambda
that allows you to write a &amp;quot;for all&amp;quot;; from this you can built a &amp;quot;there exists&amp;quot;.&lt;/p&gt;
&lt;p&gt;I'm not sure how helpful formal assertions would be.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="pragmatic-refactoring"&gt;
&lt;h2&gt;Pragmatic Refactoring&lt;/h2&gt;
&lt;p&gt;When working in the
center of the refactoring use cases, IDE aids are helpful.  When working at the
fringe, they're just visual noise.  Indeed, when redesigning something, I have
to be sure not to look at any of the &amp;quot;helpful&amp;quot; messages from Eclipse because
it's checking for errors using obsolete type information.  When I've broken the
whole thing down into a workbench full of parts, the semantic checks aren't even
meaningful.  Once I get it put back together again, automated checking can be
handy to assure a complete job.&lt;/p&gt;
&lt;p&gt;In Python, breaking the whole thing down as part of a redesign is so much simpler.
We don't have the artifice of &amp;quot;interface&amp;quot; to keep to a single inheritance model
with static type checking across multiple aspects of a class.  We just move the
methods around.  We have multiple inheritance, and we don't need formal
interface declarations.&lt;/p&gt;
&lt;p&gt;Indeed, it's
far, far easier to produce a working design in Python, and use that as a formal
specification for a Java program.  I can tweak and tinker, optimizing
performance and simplifying without the rigid formality of Java.  Adding proper
class hierarchies and turning multiple inheritance into single+interface
inheritance is typically a pretty easy transformation.  Since I knew I was
aiming at Java in the first place, I avoided Pythonisms that don't translate.&lt;/p&gt;
&lt;p&gt;While it's true that we
don't need Java's formality in Python, much of that formality is helpful.  I
find it easier to work with a proper inheritance hierarchy, one that has
explicit Not Implemented exceptions to mark the place-holders.  I like to have a
tidy interface definition so that I can document the interface.  This additional
material makes refactoring slightly more complex, but could help an automated
tool do some useful method matching among classes.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="the-final-test"&gt;
&lt;h2&gt;The Final Test&lt;/h2&gt;
&lt;p&gt;Without appropriate unit
tests, refactoring is impossible.  Even in Java, with a swanky IDE that checks
everything, you still have potential problems which are uncheckable.  In
particular, a mis-named subclass method cannot be detected except by &amp;quot;near-miss&amp;quot;
fuzzy-matching rules that will almost always work and will have false-positives.
Only unit testing can locate this situation.&lt;/p&gt;
&lt;p&gt;Unit testing absolutely is a
stand-in for things the compiler can't check.  You can portray the heavy use of
unit testing as a negative (&amp;quot;the compiler can't be trusted&amp;quot;) or as a pragmatic
approach to verifying the things you can't formally state.  All of the
assertions in the world won't find a spelling mistake.&lt;/p&gt;
&lt;p&gt;Worse, your formal
declarations (post-condition assertions or type definitions) could just as
easily be wrong.  A tidy formal proof with a wrong piece of logic will derive an
incorrect program.  A misspelled class name may compile, but still fail a suite
of tests.&lt;/p&gt;
&lt;p&gt;Since the IDE can't register intent very well, it isn't a complete solution.
In the case of redesign, it isn't even very helpful.&lt;/p&gt;
&lt;/div&gt;
</content><category term="Python"></category><category term="#python"></category><category term="unit testing"></category></entry><entry><title>Python OODB (Revised)</title><link href="https://slott56.github.io/2006_06_20-python_oodb_revised.html" rel="alternate"></link><published>2006-06-20T10:35:00-04:00</published><updated>2006-06-20T10:35:00-04:00</updated><author><name>S.Lott</name></author><id>tag:slott56.github.io,2006-06-20:/2006_06_20-python_oodb_revised.html</id><summary type="html">&lt;p&gt;Simple object persistence (i.e., serialization to
a file system) is what pickle, marshal and shelve
do.&lt;/p&gt;
&lt;p&gt;However, here's the next thing of
some interest OODB's.&lt;/p&gt;
&lt;p&gt;Zope's &lt;a class="reference external" href="http://www.zope.org/Wikis/ZODB/FrontPage"&gt;ZODB&lt;/a&gt; .  The original OODB for Python, the
backbone of Zope.&lt;/p&gt;
&lt;p&gt;&lt;a class="reference external" href="http://buzhug.sourceforge.net/"&gt;buzhug&lt;/a&gt; :
&amp;quot;a fast, pure-Python database engine, using a syntax that Python programmers
should …&lt;/p&gt;</summary><content type="html">&lt;p&gt;Simple object persistence (i.e., serialization to
a file system) is what pickle, marshal and shelve
do.&lt;/p&gt;
&lt;p&gt;However, here's the next thing of
some interest OODB's.&lt;/p&gt;
&lt;p&gt;Zope's &lt;a class="reference external" href="http://www.zope.org/Wikis/ZODB/FrontPage"&gt;ZODB&lt;/a&gt; .  The original OODB for Python, the
backbone of Zope.&lt;/p&gt;
&lt;p&gt;&lt;a class="reference external" href="http://buzhug.sourceforge.net/"&gt;buzhug&lt;/a&gt; :
&amp;quot;a fast, pure-Python database engine, using a syntax that Python programmers
should find very intuitive.&amp;quot;&lt;/p&gt;
&lt;p&gt;&lt;a class="reference external" href="http://pypersyst.org/"&gt;PyPerSyst&lt;/a&gt;  &amp;quot;fast,
reliable, and flexible object persistence with a small footprint, suitable for
embedding in other Python
applications.&amp;quot;&lt;/p&gt;
&lt;p&gt;&lt;a class="reference external" href="http://couchdb.infogami.com/"&gt;CouchDB&lt;/a&gt;  &amp;quot;A
stand-alone document store, [which] most closely resembles the Lotus
Notes/Domino storage engine.&amp;quot;&lt;/p&gt;
&lt;p&gt;&lt;a class="reference external" href="http://www.objectivity.com/blogs/insider/expert_opinion/languages/python/"&gt;ObjectivityDB/Python&lt;/a&gt;  &amp;quot;a high performance and
robust Object-Oriented Database Management System [ODBMS].&amp;quot;&lt;/p&gt;
&lt;p&gt;&lt;a class="reference external" href="http://divmod.org/trac/wiki/DivmodAxiom"&gt;Axiom&lt;/a&gt;  &amp;quot;Axiom is an object database, or
alternatively, an object-relational mapper.&amp;quot;  The pleasant thing is that you
don't really care which.&lt;/p&gt;
&lt;p&gt;&lt;a class="reference external" href="https://www.mems-exchange.org/software/durus/"&gt;Durus&lt;/a&gt;  &amp;quot;a persistent object system for
applications written in the Python programming
language.&amp;quot;&lt;/p&gt;
&lt;p&gt;&lt;a class="reference external" href="http://www.itamarst.org/software/cog/"&gt;http://www.itamarst.org/software/cog/&lt;/a&gt;
(not &lt;a class="reference external" href="http://wiki.cogkit.org/index.php/Main_Page"&gt;CoG&lt;/a&gt; , the Commodity Grid) is the Checkpointed
Object Graph, which provides &amp;quot;semi-transparent persistence for large sets of
interrelated Python objects.&amp;quot;&lt;/p&gt;
</content><category term="Python"></category><category term="#python"></category><category term="database"></category></entry><entry><title>Doctest beyond Python</title><link href="https://slott56.github.io/2006_04_17-doctest_beyond_python.html" rel="alternate"></link><published>2006-04-17T14:53:00-04:00</published><updated>2006-04-17T14:53:00-04:00</updated><author><name>S.Lott</name></author><id>tag:slott56.github.io,2006-04-17:/2006_04_17-doctest_beyond_python.html</id><summary type="html">&lt;p&gt;This is something that elevates Doctest into the
realm of Pattern.  Perhaps even above
that.&lt;/p&gt;
&lt;p&gt;The idea is so elegant: the
document is the test, and the test procedure is the
document.&lt;/p&gt;
&lt;p&gt;There's a &lt;a class="reference external" href="http://en.wikipedia.org/wiki/Don't_repeat_yourself"&gt;DRY&lt;/a&gt;  clarity to the whole thing that is rather
exciting.  It is an elegant application of …&lt;/p&gt;</summary><content type="html">&lt;p&gt;This is something that elevates Doctest into the
realm of Pattern.  Perhaps even above
that.&lt;/p&gt;
&lt;p&gt;The idea is so elegant: the
document is the test, and the test procedure is the
document.&lt;/p&gt;
&lt;p&gt;There's a &lt;a class="reference external" href="http://en.wikipedia.org/wiki/Don't_repeat_yourself"&gt;DRY&lt;/a&gt;  clarity to the whole thing that is rather
exciting.  It is an elegant application of basic &lt;a class="reference external" href="http://en.wikipedia.org/wiki/Literate_programming"&gt;Literate Programming&lt;/a&gt;
principles.&lt;/p&gt;
&lt;p&gt;The best part is that it is
difficult to do in other languages.  Ruby and Perl have the necessary
interactive execution modes.  But in Java, it would be a nightmare to define all
the required overheads to have a standardized exercise framework that paralleled
the Python interactive execution
mode.&lt;/p&gt;
&lt;p&gt;This makes the &amp;quot;Doctest&amp;quot; pattern
a key value proposition for any new language or environment.  If you can
implement the Doctest pattern, you have something that creates value by binding
testing and documentation into one tidy package.  If you can't implement the
Doctest pattern, perhaps you should rethink your implementation because you
can't easily compete against Python.&lt;/p&gt;
</content><category term="Python"></category><category term="#python"></category><category term="unit testing"></category></entry><entry><title>Python Object-Relational Mapping (Revised)</title><link href="https://slott56.github.io/2006_04_13-python_object_relational_mapping_revised.html" rel="alternate"></link><published>2006-04-13T02:37:00-04:00</published><updated>2006-04-13T02:37:00-04:00</updated><author><name>S.Lott</name></author><id>tag:slott56.github.io,2006-04-13:/2006_04_13-python_object_relational_mapping_revised.html</id><summary type="html">&lt;p&gt;Ian Bicking: A Blog &lt;a class="reference external" href="http://blog.ianbicking.org/"&gt;http://blog.ianbicking.org/&lt;/a&gt;,
provided some info on Py3K and Python Introspection &lt;a class="reference external" href="http://blog.ianbicking.org/introspecting-expressions-in-py3k.html"&gt;http://blog.ianbicking.org/introspecting-expressions-in-py3k.html&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;For
me, the interesting part was his summary of Object-Relational Mapping.  Mr.
Bicking identifies two broad approaches: lambda introspection and operator
overloading.&lt;/p&gt;
&lt;div class="section" id="lambda-introspection"&gt;
&lt;h2&gt;Lambda Introspection&lt;/h2&gt;
&lt;p&gt;&lt;a class="reference external" href="http://projects.amor.org/dejavu"&gt;Dejavu&lt;/a&gt;
It primarily uses …&lt;/p&gt;&lt;/div&gt;</summary><content type="html">&lt;p&gt;Ian Bicking: A Blog &lt;a class="reference external" href="http://blog.ianbicking.org/"&gt;http://blog.ianbicking.org/&lt;/a&gt;,
provided some info on Py3K and Python Introspection &lt;a class="reference external" href="http://blog.ianbicking.org/introspecting-expressions-in-py3k.html"&gt;http://blog.ianbicking.org/introspecting-expressions-in-py3k.html&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;For
me, the interesting part was his summary of Object-Relational Mapping.  Mr.
Bicking identifies two broad approaches: lambda introspection and operator
overloading.&lt;/p&gt;
&lt;div class="section" id="lambda-introspection"&gt;
&lt;h2&gt;Lambda Introspection&lt;/h2&gt;
&lt;p&gt;&lt;a class="reference external" href="http://projects.amor.org/dejavu"&gt;Dejavu&lt;/a&gt;
It primarily uses a generic &lt;a class="reference external" href="http://www.martinfowler.com/eaaCatalog/dataMapper.html"&gt;Data Mapper&lt;/a&gt;  architecture.  It is more of an OODB
backed by a relational store.&lt;/p&gt;
&lt;p&gt;&lt;a class="reference external" href="http://subway.python-hosting.com/wiki/SQLComp"&gt;SQLComp&lt;/a&gt;  The make_query method examines a lambda
containing a list comprehension to create SQL.  This is only queries, and isn't
a complete ORM.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="operator-overloading"&gt;
&lt;h2&gt;Operator Overloading&lt;/h2&gt;
&lt;p&gt;&lt;a class="reference external" href="http://sqlobject.org/"&gt;SQLObject&lt;/a&gt;  This is
a very complete ORM, cast in the some mold as
Django.&lt;/p&gt;
&lt;p&gt;&lt;a class="reference external" href="http://sqlalchemy.org/"&gt;SQLAlchemy&lt;/a&gt; This provides a Pythonic definition
of SQL metadata and a mapping from the SQL metadata to Python class definitions.
This is a very, very rich approach, allowing you to straddle the SQL and Object
worlds explicitly.&lt;/p&gt;
&lt;p&gt;&lt;a class="reference external" href="http://pyorq.sourceforge.net/"&gt;PyORQ&lt;/a&gt;   This
is an older ORM with a few data types but a very &amp;quot;naked&amp;quot; use of overloaded
operators to perform queries.  Unlike the lambda overloading, the class provides
operators that are set operations for
queries.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="non-introspective-approaches"&gt;
&lt;h2&gt;Non-Introspective Approaches&lt;/h2&gt;
&lt;p&gt;&lt;a class="reference external" href="http://www.djangoproject.com/"&gt;Django&lt;/a&gt; , for example, encodes attributes and
operators as keyword parameters to methods.  It doesn't look inside the Python
code, but parses the keywords.&lt;/p&gt;
&lt;p&gt;&lt;a class="reference external" href="http://skunkweb.sourceforge.net/"&gt;PyDO2&lt;/a&gt;
encodes the query explicitly, using functions that mirror SQL operators or
tuples that contain string names for the
functions.&lt;/p&gt;
&lt;p&gt;&lt;a class="reference external" href="http://www.qlime.org/"&gt;QLime&lt;/a&gt;   is an ORM with functional notation,
similar to PyDO2.&lt;/p&gt;
&lt;p&gt;&lt;a class="reference external" href="http://www.tux4web.de/computer/software/orm/"&gt;ORM&lt;/a&gt;  (the Object-Relational Membrane) mostly
captures SQL metadata in Python.&lt;/p&gt;
&lt;p&gt;&lt;a class="reference external" href="http://projects.almad.net/dbclass"&gt;DBClass&lt;/a&gt;
is focused on an easy way to hack around with SQL queries (to get data from
procedures and so on).&lt;/p&gt;
&lt;p&gt;&lt;a class="reference external" href="http://divmod.org/trac/wiki/DivmodAxiom"&gt;Axiom&lt;/a&gt;  is an object database, or alternatively,
an object-relational mapper.  It depends on &lt;a class="reference external" href="http://divmod.org/trac/wiki/DivmodEpsilon"&gt;Epsilon&lt;/a&gt; .&lt;/p&gt;
&lt;p&gt;The
Python wiki page on &lt;a class="reference external" href="http://wiki.python.org/moin/HigherLevelDatabaseProgramming"&gt;Higher Level Database Programming&lt;/a&gt;   has
additional notes and products that are high
level.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="garden-variety-relational-access"&gt;
&lt;h2&gt;Garden-Variety Relational Access&lt;/h2&gt;
&lt;p&gt;All of these modules
provide the standard &lt;a class="reference external" href="http://www.python.org/dev/peps/pep-0249/"&gt;DB-API&lt;/a&gt;  (PEP 249) interface to a SQL database.&lt;/p&gt;
&lt;p&gt;The most visible access layer product
is &lt;a class="reference external" href="http://www.egenix.com/files/python/eGenix-mx-Extensions.html"&gt;mx.ODBC&lt;/a&gt;  for bare ODBC connectivity.  This has
the advantage of wide portability, and the disadvantage of the narrow ODBC
interface.&lt;/p&gt;
&lt;p&gt;&lt;a class="reference external" href="http://pdo.neurokode.com/"&gt;PDO&lt;/a&gt;  wraps a
variety of other access methods into a single, combined package.  I'm not
precisely sure why it adds another interface layer, but it appears to simply do
away with Cursor objects.  However, it does provide a nice list of DB-API 2.0
modules for direct SQL access.&lt;/p&gt;
&lt;p&gt;&lt;a class="reference external" href="http://sourceforge.net/projects/mysql-python"&gt;MySQLdb&lt;/a&gt;  for
MySQL&lt;/p&gt;
&lt;p&gt;&lt;a class="reference external" href="http://initd.org/tracker/pysqlite"&gt;PySQLite&lt;/a&gt;  and &lt;a class="reference external" href="http://www.rogerbinns.com/apsw.html"&gt;APSW&lt;/a&gt;
are for the ultra-lightweight SQLite
RDBMS.&lt;/p&gt;
&lt;p&gt;The &lt;a class="reference external" href="http://python.projects.postgresql.org/"&gt;PostgresPy&lt;/a&gt;   project will address many PostgreSQL
topics.  &lt;a class="reference external" href="http://www.pygresql.org/"&gt;PyGreSql&lt;/a&gt;  (aka pgdb), &lt;a class="reference external" href="http://www.initd.org/projects/psycopg1"&gt;psycopg&lt;/a&gt; , &lt;a class="reference external" href="http://www.zope.org/Members/tm/PoPy"&gt;PoPy&lt;/a&gt; ,
&lt;a class="reference external" href="http://barryp.org/software/bpgsql"&gt;bpgsql&lt;/a&gt; .&lt;/p&gt;
&lt;p&gt;&lt;a class="reference external" href="http://kinterbasdb.sourceforge.net/"&gt;kinterbasdb&lt;/a&gt;  Firebird and Borland's
Interbase&lt;/p&gt;
&lt;p&gt;&lt;a class="reference external" href="http://sourceforge.net/projects/pydb2/"&gt;pyDB2&lt;/a&gt;
DB/2&lt;/p&gt;
&lt;p&gt;&lt;a class="reference external" href="http://www.cxtools.net/default.aspx?nav=cxorlb%22%20target=%22NewWindow"&gt;cx_Oracle&lt;/a&gt;
Oracle&lt;/p&gt;
&lt;p&gt;&lt;a class="reference external" href="http://adodbapi.sourceforge.net/"&gt;adodbapi&lt;/a&gt;
Python access to the MS Windows ADO
interface&lt;/p&gt;
&lt;p&gt;The Python wiki page on &lt;a class="reference external" href="http://wiki.python.org/moin/DatabaseInterfaces"&gt;Database Interfaces&lt;/a&gt;  also has a list of these
product-specific access
modules.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="recommendations"&gt;
&lt;h2&gt;Recommendations&lt;/h2&gt;
&lt;p&gt;Rule 1.  Do development with SQLite.  Why? Eschew Features.  Focus on RDBMS for
relational store, focus on Python for processing.  Stored Procedures and
Triggers are a product-specific mine-field.  Once the model passes unit tests,
move to another RDBMS that supports concurrent
users.&lt;/p&gt;
&lt;p&gt;Rule 2.  For OLTP, use an
OR-Mapping and stay away from naked SQL.  However (and this is a big however)
you will likely be supporting ad-hoc reporting through SQL-based report writers.
There are two extemes.  At one end is Deja-Vu, which may be too far from the
underlying SQL.   The other end begins with SQLAlchemy, which may expose too
much SQL; ORM and DBClass may be too light on object
features.&lt;/p&gt;
&lt;p&gt;Rule 3.  For OLAP, you have
two kinds of applications.  Some parts (like dimension conformance) can use an
OR-Mapping because they are OLAP-like.  For some loading, aggregation and
extraction, use direct SQL drivers for the chosen product.  For the large-volume
fact-oriented loads, use the vendor-supplied bulk loader.  Portability is not
your concern.&lt;/p&gt;
&lt;/div&gt;
</content><category term="Python"></category><category term="#python"></category><category term="database"></category></entry><entry><title>Agile Testing Goodies from PyCon 2006</title><link href="https://slott56.github.io/2006_02_27-agile_testing_goodies_from_pycon_2006.html" rel="alternate"></link><published>2006-02-27T11:26:00-05:00</published><updated>2006-02-27T11:26:00-05:00</updated><author><name>S.Lott</name></author><id>tag:slott56.github.io,2006-02-27:/2006_02_27-agile_testing_goodies_from_pycon_2006.html</id><summary type="html">&lt;p&gt;A number of testing frameworks were used.  The
Agile Testing tutorial provides a path through the toolsets, showing what you
can do, and how you should do it.&lt;/p&gt;
&lt;p&gt;Unit
Testing:  [&lt;a class="reference external" href="http://somethingaboutorange.com/mrl/projects/nose/"&gt;Nose&lt;/a&gt; ], &amp;lt;{filename}/blog/2005/11/2005_11_09-compare_and_contrast_round_3_revised.rst&amp;gt;&lt;/p&gt;
&lt;p&gt;Acceptance
Testing:  [&lt;a class="reference external" href="http://fitnesse.org/FrontPage"&gt;FitNesse&lt;/a&gt; ]&lt;/p&gt;
&lt;p&gt;Regression
Testing:  [&lt;a class="reference external" href="http://texttest.carmen.se/index.html"&gt;TextTest&lt;/a&gt; ]&lt;/p&gt;
&lt;p&gt;Functional
Testing:  [&lt;a class="reference external" href="http://www.idyll.org/~t/www-tools/twill/"&gt;twill&lt;/a&gt; ].  A thorough analysis is …&lt;/p&gt;</summary><content type="html">&lt;p&gt;A number of testing frameworks were used.  The
Agile Testing tutorial provides a path through the toolsets, showing what you
can do, and how you should do it.&lt;/p&gt;
&lt;p&gt;Unit
Testing:  [&lt;a class="reference external" href="http://somethingaboutorange.com/mrl/projects/nose/"&gt;Nose&lt;/a&gt; ], &amp;lt;{filename}/blog/2005/11/2005_11_09-compare_and_contrast_round_3_revised.rst&amp;gt;&lt;/p&gt;
&lt;p&gt;Acceptance
Testing:  [&lt;a class="reference external" href="http://fitnesse.org/FrontPage"&gt;FitNesse&lt;/a&gt; ]&lt;/p&gt;
&lt;p&gt;Regression
Testing:  [&lt;a class="reference external" href="http://texttest.carmen.se/index.html"&gt;TextTest&lt;/a&gt; ]&lt;/p&gt;
&lt;p&gt;Functional
Testing:  [&lt;a class="reference external" href="http://www.idyll.org/~t/www-tools/twill/"&gt;twill&lt;/a&gt; ].  A thorough analysis is at &lt;a class="reference external" href="http://www.advogato.org/article/874.html"&gt;http://www.advogato.org/article/874.html&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Ajax
Interaction Testing:  [&lt;a class="reference external" href="http://www.openqa.org/selenium/"&gt;Selenium&lt;/a&gt; ] and PAMIE &lt;a class="reference external" href="http://pamie.sourceforge.net/"&gt;http://pamie.sourceforge.net/&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Literate
Testing:  [&lt;a class="reference external" href="http://www.python.org/doc/lib/module-doctest.html"&gt;doctest&lt;/a&gt; ] and epydoc &lt;a class="reference external" href="http://epydoc.sourceforge.net/"&gt;http://epydoc.sourceforge.net/&lt;/a&gt;.&lt;/p&gt;
</content><category term="Python"></category><category term="#python"></category><category term="unit testing"></category></entry><entry><title>testresources</title><link href="https://slott56.github.io/2005_12_26-testresources.html" rel="alternate"></link><published>2005-12-26T13:57:00-05:00</published><updated>2005-12-26T13:57:00-05:00</updated><author><name>S.Lott</name></author><id>tag:slott56.github.io,2005-12-26:/2005_12_26-testresources.html</id><summary type="html">&lt;p&gt;&lt;tt class="docutils literal"&gt;testresources&lt;/tt&gt; &lt;a class="reference external" href="http://www.robertcollins.net/unittest/testresources/"&gt;http://www.robertcollins.net/unittest/testresources/&lt;/a&gt;
purpose appears to be to manage the resources used by a test suite.&lt;/p&gt;
&lt;p&gt;Adding this resource management
context extends the &lt;strong&gt;Test Suite&lt;/strong&gt;  to optimize tests around the resources.
This can reshuffle the TestCases to minimize SetUp's.  This can be useful in
contexts where …&lt;/p&gt;</summary><content type="html">&lt;p&gt;&lt;tt class="docutils literal"&gt;testresources&lt;/tt&gt; &lt;a class="reference external" href="http://www.robertcollins.net/unittest/testresources/"&gt;http://www.robertcollins.net/unittest/testresources/&lt;/a&gt;
purpose appears to be to manage the resources used by a test suite.&lt;/p&gt;
&lt;p&gt;Adding this resource management
context extends the &lt;strong&gt;Test Suite&lt;/strong&gt;  to optimize tests around the resources.
This can reshuffle the TestCases to minimize SetUp's.  This can be useful in
contexts where the &lt;strong&gt;Fixture&lt;/strong&gt; includes &lt;strong&gt;Singletons&lt;/strong&gt; or expensive resources.&lt;/p&gt;
&lt;p&gt;While interesting, this package bends one of the common definitions of Unit Testing.
If there is a complex resource dependency, the &lt;strong&gt;Fixture&lt;/strong&gt;
being tested isn't really isolated.  This pushes beyond isolated unit testing
with &lt;strong&gt;Mock&lt;/strong&gt; objects into integration testing with real objects and real
interfaces.&lt;/p&gt;
&lt;p&gt;One can make the case that
&amp;quot;unit&amp;quot; is intentionally vague; the Beck definitions refer to a &lt;strong&gt;Fixture&lt;/strong&gt;
as the design pattern.  This could be a class, module or package, depending on
your willingness to abstract.  I agree that &amp;quot;unit&amp;quot; does not necessarily mean
class.  However, I do think that &amp;quot;unit&amp;quot; means isolated from other
components.&lt;/p&gt;
&lt;p&gt;&lt;tt class="docutils literal"&gt;testresources&lt;/tt&gt; seems
specifically designed for integration test, not unit test.  I think it is
miscategorized, and belongs to an unidentified species of products: integration
testing frameworks.&lt;/p&gt;
</content><category term="Python"></category><category term="#python"></category><category term="unit testing"></category></entry><entry><title>SubUnit</title><link href="https://slott56.github.io/2005_12_20-subunit.html" rel="alternate"></link><published>2005-12-20T18:36:00-05:00</published><updated>2005-12-20T18:36:00-05:00</updated><author><name>S.Lott</name></author><id>tag:slott56.github.io,2005-12-20:/2005_12_20-subunit.html</id><summary type="html">&lt;p&gt;SubUnit's &lt;a class="reference external" href="http://www.robertcollins.net/unittest/subunit/"&gt;http://www.robertcollins.net/unittest/subunit/&lt;/a&gt; purpose appears to be to manage testing
via subprocesses.&lt;/p&gt;
&lt;p&gt;Consequently, it can run external tests not in Python, it can fork a subprocess to manage the Fixture
in an isolated process.&lt;/p&gt;
&lt;p&gt;Adding this subprocess execution context extends the &lt;strong&gt;Test Runner&lt;/strong&gt;  implementation of the …&lt;/p&gt;</summary><content type="html">&lt;p&gt;SubUnit's &lt;a class="reference external" href="http://www.robertcollins.net/unittest/subunit/"&gt;http://www.robertcollins.net/unittest/subunit/&lt;/a&gt; purpose appears to be to manage testing
via subprocesses.&lt;/p&gt;
&lt;p&gt;Consequently, it can run external tests not in Python, it can fork a subprocess to manage the Fixture
in an isolated process.&lt;/p&gt;
&lt;p&gt;Adding this subprocess execution context extends the &lt;strong&gt;Test Runner&lt;/strong&gt;  implementation of the built-in
&lt;tt class="docutils literal"&gt;unittest&lt;/tt&gt; module.  This can be useful in contexts where the Fixture includes &lt;strong&gt;Singletons&lt;/strong&gt;
or connection pools or other per-process design features.&lt;/p&gt;
</content><category term="Python"></category><category term="#python"></category><category term="unit testing"></category></entry><entry><title>Twisted Trial</title><link href="https://slott56.github.io/2005_12_15-twisted_trial.html" rel="alternate"></link><published>2005-12-15T17:10:00-05:00</published><updated>2005-12-15T17:10:00-05:00</updated><author><name>S.Lott</name></author><id>tag:slott56.github.io,2005-12-15:/2005_12_15-twisted_trial.html</id><summary type="html">&lt;p&gt;Trial is not really a stand-alone unit test
framework.  It is an extension to unittest focused on the testing needs for the
Twisted framework.&lt;/p&gt;
&lt;p&gt;The Trial how-to
&lt;a class="reference external" href="http://twistedmatrix.com/projects/core/documentation/howto/testing.html"&gt;http://twistedmatrix.com/projects/core/documentation/howto/testing.html&lt;/a&gt; has some information.  More valuable,
perhaps are the API documents &lt;a class="reference external" href="http://twistedmatrix.com/documents/current/api/twisted.trial.html"&gt;http://twistedmatrix.com/documents …&lt;/a&gt;&lt;/p&gt;</summary><content type="html">&lt;p&gt;Trial is not really a stand-alone unit test
framework.  It is an extension to unittest focused on the testing needs for the
Twisted framework.&lt;/p&gt;
&lt;p&gt;The Trial how-to
&lt;a class="reference external" href="http://twistedmatrix.com/projects/core/documentation/howto/testing.html"&gt;http://twistedmatrix.com/projects/core/documentation/howto/testing.html&lt;/a&gt; has some information.  More valuable,
perhaps are the API documents &lt;a class="reference external" href="http://twistedmatrix.com/documents/current/api/twisted.trial.html"&gt;http://twistedmatrix.com/documents/current/api/twisted.trial.html&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Because of the asynchronous nature of
Twisted, the Fixture pattern has a rather complex relationship with the Twisted
Reactor.  Also, since Twisted is a framework, not a single application,
components are optional, and the TestSuite pattern is implemented with
considerably more flexibility.&lt;/p&gt;
</content><category term="Python"></category><category term="#python"></category><category term="unit testing"></category></entry><entry><title>More Frameworks! (rev. 3)</title><link href="https://slott56.github.io/2005_12_13-more_frameworks_rev_3.html" rel="alternate"></link><published>2005-12-13T18:40:00-05:00</published><updated>2005-12-13T18:40:00-05:00</updated><author><name>S.Lott</name></author><id>tag:slott56.github.io,2005-12-13:/2005_12_13-more_frameworks_rev_3.html</id><summary type="html">&lt;p&gt;A wiki page on Python testing tools &lt;a class="reference external" href="http://pycheesecake.org/wiki/PythonTestingToolsTaxonomy"&gt;http://pycheesecake.org/wiki/PythonTestingToolsTaxonomy&lt;/a&gt; identifies a number of additional unit
testing tools.  The wiki page provides a handy summary.  I'll examine these in
light of the Beck Unit Test design patterns to provide a little more detail on
what they really do …&lt;/p&gt;</summary><content type="html">&lt;p&gt;A wiki page on Python testing tools &lt;a class="reference external" href="http://pycheesecake.org/wiki/PythonTestingToolsTaxonomy"&gt;http://pycheesecake.org/wiki/PythonTestingToolsTaxonomy&lt;/a&gt; identifies a number of additional unit
testing tools.  The wiki page provides a handy summary.  I'll examine these in
light of the Beck Unit Test design patterns to provide a little more detail on
what they really do.&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;Twisted Trial&lt;/li&gt;
&lt;li&gt;SubUnit&lt;/li&gt;
&lt;li&gt;Testresources&lt;/li&gt;
&lt;li&gt;PyUnitPerf&lt;/li&gt;
&lt;li&gt;PeckCheck&lt;/li&gt;
&lt;li&gt;PythonMock&lt;/li&gt;
&lt;li&gt;pMock&lt;/li&gt;
&lt;/ul&gt;
</content><category term="Python"></category><category term="#python"></category><category term="unit testing"></category></entry><entry><title>Compare and Contrast (round 3, revised)</title><link href="https://slott56.github.io/2005_11_09-compare_and_contrast_round_3_revised.html" rel="alternate"></link><published>2005-11-09T19:38:00-05:00</published><updated>2005-11-09T19:38:00-05:00</updated><author><name>S.Lott</name></author><id>tag:slott56.github.io,2005-11-09:/2005_11_09-compare_and_contrast_round_3_revised.html</id><summary type="html">&lt;p&gt;The object-oriented unit testing framework began
as Smalltalk's Beck Test framework &lt;a class="reference external" href="http://www.xprogramming.com/testfram.htm"&gt;http://www.xprogramming.com/testfram.htm&lt;/a&gt;.  It evolved to the JUnit &lt;a class="reference external" href="http://www.junit.org/index.htm"&gt;http://www.junit.org/index.htm&lt;/a&gt;&amp;gt;`_ `  &amp;lt;&lt;a class="reference external" href="http://www.junit.org/index.htm%22%20target=%22NewWindow"&gt;http://www.junit.org/index.htm%22%20target=%22NewWindow&lt;/a&gt;
framework for Java.  Beck defined four repeated patterns of unit testing
software …&lt;/p&gt;</summary><content type="html">&lt;p&gt;The object-oriented unit testing framework began
as Smalltalk's Beck Test framework &lt;a class="reference external" href="http://www.xprogramming.com/testfram.htm"&gt;http://www.xprogramming.com/testfram.htm&lt;/a&gt;.  It evolved to the JUnit &lt;a class="reference external" href="http://www.junit.org/index.htm"&gt;http://www.junit.org/index.htm&lt;/a&gt;&amp;gt;`_ `  &amp;lt;&lt;a class="reference external" href="http://www.junit.org/index.htm%22%20target=%22NewWindow"&gt;http://www.junit.org/index.htm%22%20target=%22NewWindow&lt;/a&gt;
framework for Java.  Beck defined four repeated patterns of unit testing
software, covered in a previous posting &amp;lt;{filename}/blog/2005/11/2005_11_05-compare_and_contrast_round_1.rst&amp;gt;.&lt;/p&gt;
&lt;p&gt;&lt;tt class="docutils literal"&gt;Nose&lt;/tt&gt;, &lt;tt class="docutils literal"&gt;TestOOB&lt;/tt&gt;, &lt;tt class="docutils literal"&gt;test.py&lt;/tt&gt;
(and &lt;tt class="docutils literal"&gt;TestGears&lt;/tt&gt;) are significant revisions to the
&lt;strong&gt;Test Suite&lt;/strong&gt;  and &lt;strong&gt;Test Runner&lt;/strong&gt;  parts of the unit test patterns.
Additionally, these tools make efforts to implement the &lt;strong&gt;Diagnostics&lt;/strong&gt; pattern, also.&lt;/p&gt;
&lt;p&gt;The &lt;strong&gt;Fixture&lt;/strong&gt;
is implied by the context in which the tests are discovered.  Nose can locate
package, module and function tests; it uses TestCase class definitions, also.
TestOOB and test.py sit more squarely on unittest, with the resulting focus on
module-level testing.&lt;/p&gt;
&lt;p&gt;In &lt;tt class="docutils literal"&gt;TestOOB&lt;/tt&gt; and &lt;tt class="docutils literal"&gt;test.py&lt;/tt&gt;, the &lt;strong&gt;TestCase&lt;/strong&gt;
class plus a flexible regular expressions or glob expression defines the test
cases.  test.py looks for packages of tests, using the path name of the package,
as well as the module name.  In &lt;tt class="docutils literal"&gt;Nose&lt;/tt&gt;, the &lt;strong&gt;TestCase&lt;/strong&gt;
class can be used for compatibility, but this is not required; Nose will match a
regular expression to locate tests.&lt;/p&gt;
&lt;p&gt;The &lt;strong&gt;Results Check&lt;/strong&gt;  in &lt;tt class="docutils literal"&gt;nose&lt;/tt&gt; can be done via the existing
&lt;tt class="docutils literal"&gt;assert&lt;/tt&gt; statement.  &lt;tt class="docutils literal"&gt;Nose&lt;/tt&gt;, pleasantly handles the &amp;quot;test which throws an exception&amp;quot;
case: a test function that exits normally is a &amp;quot;pass&amp;quot;.  An &lt;tt class="docutils literal"&gt;AssertionError&lt;/tt&gt;
exception is a test failure; any other exception is an error.  Since &lt;tt class="docutils literal"&gt;TestOOB&lt;/tt&gt; and
&lt;tt class="docutils literal"&gt;test.py&lt;/tt&gt; sit on &lt;tt class="docutils literal"&gt;unittest&lt;/tt&gt;, they
depend on the complex set of assert methods, and the &lt;tt class="docutils literal"&gt;fail()&lt;/tt&gt; method.&lt;/p&gt;
&lt;p&gt;The &lt;strong&gt;Test Suite&lt;/strong&gt;  is implied by the collection of &lt;tt class="docutils literal"&gt;TestCase&lt;/tt&gt;
instances with the expected name forms in &lt;tt class="docutils literal"&gt;TestOOB&lt;/tt&gt;.  In &lt;tt class="docutils literal"&gt;Nose&lt;/tt&gt; and &lt;tt class="docutils literal"&gt;test.py&lt;/tt&gt;, it is
the collection of modules, functions and methods with names that have the
expected forms.  Both cases make powerful use of Python introspection to track
down the tests.&lt;/p&gt;
&lt;p&gt;The &lt;strong&gt;Test Runner&lt;/strong&gt;  in nose can be a stand-alone &lt;tt class="docutils literal"&gt;nosetests&lt;/tt&gt;
program, or you can &lt;tt class="docutils literal"&gt;import nose; nose.main()&lt;/tt&gt;.
In the case of &lt;tt class="docutils literal"&gt;TestOOB&lt;/tt&gt;, we have a &lt;tt class="docutils literal"&gt;testoob&lt;/tt&gt;
program, or we can &lt;cite&gt;import testoob; testoob.main()`&lt;/cite&gt;.
&lt;tt class="docutils literal"&gt;Nose&lt;/tt&gt; has an interesting
integration with Python &lt;tt class="docutils literal"&gt;distutils/setuptools&lt;/tt&gt;.  It adds a new &amp;quot;test&amp;quot; verb to
&lt;tt class="docutils literal"&gt;setup.py&lt;/tt&gt;.  The &lt;tt class="docutils literal"&gt;test.py&lt;/tt&gt; main program has a large number of options to fine-tune
which tests are run&lt;/p&gt;
&lt;p&gt;&lt;tt class="docutils literal"&gt;Nose&lt;/tt&gt; supports the &lt;strong&gt;Diagnostics&lt;/strong&gt;
with output capture and a simple flag for producing additional details.
&lt;tt class="docutils literal"&gt;TestOOB&lt;/tt&gt;, in certain environments, will produce color output; it produces an XML
test report as well as HTML test reports.  &lt;tt class="docutils literal"&gt;TestOOB&lt;/tt&gt; can launch the Python
debugger as well as log failing assertions in detail.  &lt;tt class="docutils literal"&gt;test.py&lt;/tt&gt; can run
&lt;tt class="docutils literal"&gt;pychecker&lt;/tt&gt;, do tracing and refcount checking as part of the diagnostics.&lt;/p&gt;
&lt;p&gt;&lt;tt class="docutils literal"&gt;TestOOB&lt;/tt&gt; has some additional features for repeating and controlling the timing of the tests.
While this is not sufficient to prove that an application lacks the kind of race
condition that makes it behave poorly; it can help to provide some confidence
for load testing.  Similarly, &lt;tt class="docutils literal"&gt;test.py&lt;/tt&gt; includes features for looping tests to
look for memory leaks and race conditions.&lt;/p&gt;
</content><category term="Python"></category><category term="#python"></category><category term="unit testing"></category></entry><entry><title>Compare and Contrast (round 2)</title><link href="https://slott56.github.io/2005_11_07-compare_and_contrast_round_2.html" rel="alternate"></link><published>2005-11-07T17:50:00-05:00</published><updated>2005-11-07T17:50:00-05:00</updated><author><name>S.Lott</name></author><id>tag:slott56.github.io,2005-11-07:/2005_11_07-compare_and_contrast_round_2.html</id><summary type="html">&lt;p&gt;The object-oriented unit testing framework began
as Smalltalk's Beck Test framework &lt;a class="reference external" href="http://www.xprogramming.com/testfram.htm%22%20target=%22NewWindow"&gt;http://www.xprogramming.com/testfram.htm%22%20target=%22NewWindow&lt;/a&gt;.
It evolved to the JUnit &lt;a class="reference external" href="http://www.junit.org/index.htm%22%20target=%22NewWindow"&gt;http://www.junit.org/index.htm%22%20target=%22NewWindow&lt;/a&gt;
framework for Java.  Beck defined four repeated patterns of unit testing
software, covered in a previous …&lt;/p&gt;</summary><content type="html">&lt;p&gt;The object-oriented unit testing framework began
as Smalltalk's Beck Test framework &lt;a class="reference external" href="http://www.xprogramming.com/testfram.htm%22%20target=%22NewWindow"&gt;http://www.xprogramming.com/testfram.htm%22%20target=%22NewWindow&lt;/a&gt;.
It evolved to the JUnit &lt;a class="reference external" href="http://www.junit.org/index.htm%22%20target=%22NewWindow"&gt;http://www.junit.org/index.htm%22%20target=%22NewWindow&lt;/a&gt;
framework for Java.  Beck defined four repeated patterns of unit testing
software, covered in a previous posting &amp;lt;{filename}/blog/2005/11/2005_11_05-compare_and_contrast_round_1.rst&amp;gt;.&lt;/p&gt;
&lt;p&gt;An additional pattern that py.test introduces is the &lt;strong&gt;Diagnostics&lt;/strong&gt;
pattern.  This is a useful traceback or cached output.  To make it useful, it is
presented only for failing tests, and elides repetition in the event of
recursions that lead to stack overflows.&lt;/p&gt;
&lt;p&gt;&lt;tt class="docutils literal"&gt;py.test&lt;/tt&gt; seems to deliver most of the Beck-defined features.&lt;/p&gt;
&lt;p&gt;The Fixture is created by offering a
number of setup/teardown functions, either at the module level (for a module or
class) or within a class.&lt;/p&gt;
&lt;p&gt;The Test Case is a module, class or function with an appropriate name.  Either
&lt;tt class="docutils literal"&gt;test_&lt;/tt&gt; or &lt;tt class="docutils literal"&gt;Test_&lt;/tt&gt; as a
prefix is sufficient to define a test case.&lt;/p&gt;
&lt;p&gt;The Results Check uses ordinary
asserts and a special
&lt;tt class="docutils literal"&gt;py.test.raises()&lt;/tt&gt;
function to cover all the bases.  Personally, I prefer the JUnit approach to
catching the expected exception and calling the
&lt;tt class="docutils literal"&gt;fail()&lt;/tt&gt; method for everything else.&lt;/p&gt;
&lt;p&gt;The Suite is
developed by implication through Python's powerful introspection: everything
that looks like a test -- at the package, module and class level -- is a
candidate.  A regular expression can pick names, plus other global conditions
can be examined to further refine the test protocols.&lt;/p&gt;
&lt;p&gt;The Runner is a stand-alone &lt;tt class="docutils literal"&gt;py.test&lt;/tt&gt; program
that locates the tests, executes them and produces a log.  Further, it produces
Diagnostics focused on the failing tests.&lt;/p&gt;
</content><category term="Python"></category><category term="#python"></category><category term="unit testing"></category></entry><entry><title>Compare and Contrast (round 1)</title><link href="https://slott56.github.io/2005_11_05-compare_and_contrast_round_1.html" rel="alternate"></link><published>2005-11-05T20:21:00-05:00</published><updated>2005-11-05T20:21:00-05:00</updated><author><name>S.Lott</name></author><id>tag:slott56.github.io,2005-11-05:/2005_11_05-compare_and_contrast_round_1.html</id><summary type="html">&lt;div class="section" id="some-basis-for-comparison"&gt;
&lt;h2&gt;Some Basis for Comparison&lt;/h2&gt;
&lt;p&gt;The object-oriented unit
testing framework began as Smalltalk's Beck Test framework &lt;a class="reference external" href="http://www.xprogramming.com/testfram.htm"&gt;http://www.xprogramming.com/testfram.htm&lt;/a&gt;.  It evolved to the JUnit &lt;a class="reference external" href="http://www.junit.org/index.htm"&gt;http://www.junit.org/index.htm&lt;/a&gt;
framework for Java.  Beck defined four repeated patterns of unit testing
software:&lt;/p&gt;
&lt;p&gt;The &lt;strong&gt;Fixture&lt;/strong&gt;.
The thing we are …&lt;/p&gt;&lt;/div&gt;</summary><content type="html">&lt;div class="section" id="some-basis-for-comparison"&gt;
&lt;h2&gt;Some Basis for Comparison&lt;/h2&gt;
&lt;p&gt;The object-oriented unit
testing framework began as Smalltalk's Beck Test framework &lt;a class="reference external" href="http://www.xprogramming.com/testfram.htm"&gt;http://www.xprogramming.com/testfram.htm&lt;/a&gt;.  It evolved to the JUnit &lt;a class="reference external" href="http://www.junit.org/index.htm"&gt;http://www.junit.org/index.htm&lt;/a&gt;
framework for Java.  Beck defined four repeated patterns of unit testing
software:&lt;/p&gt;
&lt;p&gt;The &lt;strong&gt;Fixture&lt;/strong&gt;.
The thing we are testing; a class or possibly a set of instances of a given
class, or possibly something even larger.  If we are testing more than one class
at a time, we aren't really &amp;quot;unit&amp;quot; testing.  So the fixture often includes stubs
for missing classes.&lt;/p&gt;
&lt;p&gt;The &lt;strong&gt;Test Case&lt;/strong&gt;.  A predictable reaction of the fixture.
This should either work or fail.  It can, of course also raise one of those
egregious, unchecked-for errors that indicate fairly serious problems in a
preliminary piece of software.  Or, it may indicate something that was badly
damaged during maintenance and is now raising errors instead of simply failing
the regression test suite.&lt;/p&gt;
&lt;p&gt;The &lt;strong&gt;Results Check&lt;/strong&gt;.  A specific assertion about the fixture's results.&lt;/p&gt;
&lt;p&gt;The &lt;strong&gt;Test Suite&lt;/strong&gt;. A collection of TestCases.&lt;/p&gt;
&lt;p&gt;JUnit and unittest add a
&lt;strong&gt;Test Runner&lt;/strong&gt;  pattern, also.  This the top-level
component that uses a Test Suite to create test results by executing each Test
Case, assuring that each Results Check worked.  The Test Runner can also assure
that any Fixture Setup and Teardown is done
correctly.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="legacy-frameworks"&gt;
&lt;h2&gt;Legacy Frameworks&lt;/h2&gt;
&lt;p&gt;&lt;tt class="docutils literal"&gt;unittest&lt;/tt&gt; delivers all
the Beck-defined features.  It should, it is the indirect descendant of the
original framework.  Having JUnit as an ancestor, however, leads to some clunky
non-Pythonic features.  In particular, Python features that Java lacks are
ignored, including modules and free-standing
functions.&lt;/p&gt;
&lt;p&gt;&lt;tt class="docutils literal"&gt;doctest&lt;/tt&gt; has an odd fit with
the Beck framework.  The fixture isn't well defined; since doctest has a
module-centric view, a shallow copy of the module globals are given to each
test, making the module globals the fixture.  Each Case and Results Check is
encoded in a docstring, usually by a cut and paste from an interactive testing
session.  The test suite is implied by the module
structure.&lt;/p&gt;
&lt;p&gt;&lt;tt class="docutils literal"&gt;unittest&lt;/tt&gt; isn't terribly
Pythonic.  Doctest is module-focused, not class focused, and doesn't treat the
notion of fixture very well.&lt;/p&gt;
&lt;p&gt;IMO, module-based testing is a more useful level of unit testing.  Individual
classes, while important, rarely make sense in a vacuum.  All of the test
harness and stub classes required to test just one class seems like too much
unproductive work.  When the architecture changes, I may have to change a class
definition as well as the test harness classes that stand in for this class in
the unit testing framework.&lt;/p&gt;
&lt;p&gt;Next Up,
&lt;tt class="docutils literal"&gt;py.test&lt;/tt&gt;, &lt;tt class="docutils literal"&gt;nose&lt;/tt&gt; and &lt;tt class="docutils literal"&gt;testgears&lt;/tt&gt;.  Later, &lt;tt class="docutils literal"&gt;TestOOB&lt;/tt&gt; and &lt;tt class="docutils literal"&gt;Sancho&lt;/tt&gt;.&lt;/p&gt;
&lt;/div&gt;
</content><category term="Python"></category><category term="#python"></category><category term="unit testing"></category></entry><entry><title>Python Unit Testing Frameworks (v3)</title><link href="https://slott56.github.io/2005_11_02-python_unit_testing_frameworks_v3.html" rel="alternate"></link><published>2005-11-02T00:12:00-05:00</published><updated>2005-11-02T00:12:00-05:00</updated><author><name>S.Lott</name></author><id>tag:slott56.github.io,2005-11-02:/2005_11_02-python_unit_testing_frameworks_v3.html</id><summary type="html">&lt;p&gt;Ned Batchelder : Blog [&lt;a class="reference external" href="http://www.nedbatchelder.com/blog/index.html"&gt;http://www.nedbatchelder.com/blog/index.html&lt;/a&gt; ] identifies no less than 6 unit testing
frameworks for Python [&lt;a class="reference external" href="http://www.nedbatchelder.com/blog/200510.html#e20051025T070731"&gt;http://www.nedbatchelder.com/blog/200510.html#e20051025T070731&lt;/a&gt; ] and [&lt;a class="reference external" href="http://www.nedbatchelder.com/blog/200411.html#e20041120T185622"&gt;http://www.nedbatchelder.com/blog/200411.html#e20041120T185622&lt;/a&gt; ].&lt;/p&gt;
&lt;p&gt;TestGears
[&lt;a class="reference external" href="http://www.turbogears.com/testgears/"&gt;http://www.turbogears.com/testgears/&lt;/a&gt; ] is part of the TurboGears web
uber-framework …&lt;/p&gt;</summary><content type="html">&lt;p&gt;Ned Batchelder : Blog [&lt;a class="reference external" href="http://www.nedbatchelder.com/blog/index.html"&gt;http://www.nedbatchelder.com/blog/index.html&lt;/a&gt; ] identifies no less than 6 unit testing
frameworks for Python [&lt;a class="reference external" href="http://www.nedbatchelder.com/blog/200510.html#e20051025T070731"&gt;http://www.nedbatchelder.com/blog/200510.html#e20051025T070731&lt;/a&gt; ] and [&lt;a class="reference external" href="http://www.nedbatchelder.com/blog/200411.html#e20041120T185622"&gt;http://www.nedbatchelder.com/blog/200411.html#e20041120T185622&lt;/a&gt; ].&lt;/p&gt;
&lt;p&gt;TestGears
[&lt;a class="reference external" href="http://www.turbogears.com/testgears/"&gt;http://www.turbogears.com/testgears/&lt;/a&gt; ] is part of the TurboGears web
uber-framework. It provides automatic discovery of test functions, simplifies
suite development, and makes it easy to run tests zero configuration.  Kevin
Dangoor [&lt;a class="reference external" href="http://www.blueskyonmars.com/"&gt;http://www.blueskyonmars.com/&lt;/a&gt; ] says he will deprecate this in favor of
Nose.  David Warnock [&lt;a class="reference external" href="http://42.blogs.warnock.me.uk/2005/10/turbogears_cont.html"&gt;http://42.blogs.warnock.me.uk/2005/10/turbogears_cont.html&lt;/a&gt; ] says something
similar.&lt;/p&gt;
&lt;p&gt;TestOOB [&lt;a class="reference external" href="http://testoob.sourceforge.net/"&gt;http://testoob.sourceforge.net/&lt;/a&gt; ]
(Testing Out Of [the] Box) provides for new styles of output (HTML and color
terminal), debugger launching, verbose asserts, parallel execution, and
command-line utility testing&lt;/p&gt;
&lt;p&gt;nose [&lt;a class="reference external" href="http://somethingaboutorange.com/mrl/projects/nose/"&gt;http://somethingaboutorange.com/mrl/projects/nose/&lt;/a&gt; ] provides an alternate test discovery and
execution engine for unittest&lt;/p&gt;
&lt;p&gt;unittest [&lt;a class="reference external" href="http://docs.python.org/lib/module-unittest.html"&gt;http://docs.python.org/lib/module-unittest.html&lt;/a&gt; ], formerly known as PyUnit [&lt;a class="reference external" href="http://pyunit.sourceforge.net/"&gt;http://pyunit.sourceforge.net/&lt;/a&gt; ]
(Thanks for the heads up, Tony [&lt;a class="reference external" href="http://www.haloscan.com/comments/slott/E20051105152154/#29209"&gt;http://www.haloscan.com/comments/slott/E20051105152154/#29209&lt;/a&gt; ])&lt;/p&gt;
&lt;p&gt;doctest
[&lt;a class="reference external" href="http://docs.python.org/lib/module-doctest.html"&gt;http://docs.python.org/lib/module-doctest.html&lt;/a&gt; ]&lt;/p&gt;
&lt;p&gt;py.test [&lt;a class="reference external" href="http://codespeak.net/py/current/doc/test.html"&gt;http://codespeak.net/py/current/doc/test.html&lt;/a&gt; ]&lt;/p&gt;
&lt;p&gt;Michal
Watkins [&lt;a class="reference external" href="http://mikewatkins.net/"&gt;http://mikewatkins.net/&lt;/a&gt; ] adds  Sancho, a unit testing framework
[&lt;a class="reference external" href="http://www.mems-exchange.org/software/sancho/"&gt;http://www.mems-exchange.org/software/sancho/&lt;/a&gt; ].&lt;/p&gt;
&lt;p&gt;Also,
ZOPE has test.py [&lt;a class="reference external" href="http://zopewiki.org/HowToRunZopeUnitTests"&gt;http://zopewiki.org/HowToRunZopeUnitTests&lt;/a&gt; ].  There is a derivative product, also, the
SchoolTool Test Runner [&lt;a class="reference external" href="http://svn.nuxeo.org/trac/pub/file/CalCore/trunk/test.py"&gt;http://svn.nuxeo.org/trac/pub/file/CalCore/trunk/test.py&lt;/a&gt; ].&lt;/p&gt;
&lt;p&gt;Jeremy
Hylton's blog has some notes [&lt;a class="reference external" href="http://www.python.org/~jeremy/weblog/031014.html"&gt;http://www.python.org/~jeremy/weblog/031014.html&lt;/a&gt; ] on unit testing, describing
test.py.&lt;/p&gt;
&lt;p&gt;Ian Bicking also has a list of
complaints about the basic unittest interface [&lt;a class="reference external" href="http://blog.colorstudy.com/ianb/weblog/2003/10/10.html#P11"&gt;http://blog.colorstudy.com/ianb/weblog/2003/10/10.html#P11&lt;/a&gt; ], many of which are answered by the
add-ons.&lt;/p&gt;
</content><category term="Python"></category><category term="unit testing"></category></entry><entry><title>One More Cool Thing About Python Is...</title><link href="https://slott56.github.io/2005_10_22-one_more_cool_thing_about_python_is.html" rel="alternate"></link><published>2005-10-22T15:25:00-04:00</published><updated>2005-10-22T15:25:00-04:00</updated><author><name>S.Lott</name></author><id>tag:slott56.github.io,2005-10-22:/2005_10_22-one_more_cool_thing_about_python_is.html</id><summary type="html">&lt;p&gt;So, confronted with 1.4M records of questionable
data, what do we do?&lt;/p&gt;
&lt;ol class="arabic simple"&gt;
&lt;li&gt;We'll need a production program that can run
daily from now until the end of time to cleanse the data and reject the truly
evil records.&lt;/li&gt;
&lt;li&gt;We don't have a good fix on what the data is …&lt;/li&gt;&lt;/ol&gt;</summary><content type="html">&lt;p&gt;So, confronted with 1.4M records of questionable
data, what do we do?&lt;/p&gt;
&lt;ol class="arabic simple"&gt;
&lt;li&gt;We'll need a production program that can run
daily from now until the end of time to cleanse the data and reject the truly
evil records.&lt;/li&gt;
&lt;li&gt;We don't have a good fix on what the data is.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;We have some &amp;quot;specifications&amp;quot; but
they're little more than a wish list of cleansing
suggestions.&lt;/p&gt;
&lt;p&gt;What to do?&lt;/p&gt;
&lt;p&gt;Well, step 1 is obviously analysis to
fully define the problem.  What data do we have?  What is the platonic ideal?
What forms of badness are represented where data does not meet the
ideal?&lt;/p&gt;
&lt;p&gt;So, how do we analyze?  We can't
throw it into Excel.  We can barely open the file in Textpad.&lt;/p&gt;
&lt;div class="section" id="python-to-the-rescue"&gt;
&lt;h2&gt;Python to the rescue&lt;/h2&gt;
&lt;p&gt;While the final application
must be delivered in Java, Java's a terrible way to analyze unknown data.  We
would have a cycle of write, debug, build, test, run and scratch head over a
fairly complex piece of Java code just to do simple frequency
histograms.&lt;/p&gt;
&lt;p&gt;The Python program we used
was something on the order of&lt;/p&gt;
&lt;pre class="literal-block"&gt;
fq= {}
for line in file( 'sample.csv', 'r'):
    fields= line.split(',')
    aSuspect= fields[4].split()
    fq.setdefault( aSuspect[3], 0 )
    fq[ aSuspect[3] ] += 1
print fq
&lt;/pre&gt;
&lt;p&gt;Given this template we can move
around, analyzing fields individually or in groups, looking for the domain of
badness and formulating a cleansing
strategy.&lt;/p&gt;
&lt;p&gt;Python reduces the
development cycle to type-run-think analysis cycle which goes very quickly.  And
we can easily produce things we don't mind throwing away.  We haven't invested
much, we don't have the Urge to
Preserve.&lt;/p&gt;
&lt;p&gt;We can then dry-run our
cleansing approach in a type-run-think design cycle.  Again, the Urge to
Preserve is negligible when there's such a tiny
investment.&lt;/p&gt;
&lt;p&gt;Compare the 8 lines of code
above with the equivalent in Java.  It would be, perhaps twice the size.  The
strong type checking slows development, and causes you to marry a class
hierarchy that isn't really correct because it's too expensive to
change.&lt;/p&gt;
&lt;p&gt;And when we're done, we can
simply translate to Java.  I like this Python thing, it works.&lt;/p&gt;
&lt;/div&gt;
</content><category term="Python"></category><category term="data analysis"></category><category term="application example"></category></entry></feed>