<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom"><title>Vitaly Samigullin's blog</title><link href="https://blog.pilosus.org/" rel="alternate"></link><link href="https://blog.pilosus.org/feeds/all.atom.xml" rel="self"></link><id>https://blog.pilosus.org/</id><updated>2024-01-07T17:39:00+01:00</updated><entry><title>emacs tramp: ssh + sudo</title><link href="https://blog.pilosus.org/posts/2024/01/06/emacs-tramp-ssh-sudo/" rel="alternate"></link><published>2024-01-06T18:58:00+01:00</published><updated>2024-01-06T18:58:00+01:00</updated><author><name>Vitaly Samigullin</name></author><id>tag:blog.pilosus.org,2024-01-06:/posts/2024/01/06/emacs-tramp-ssh-sudo/</id><summary type="html">&lt;p&gt;&lt;span class="caps"&gt;TIL&lt;/span&gt; I can use &lt;a class="reference external" href="https://www.gnu.org/software/tramp/#Combining-ssh-or-plink-with-su_002c-sudo-or-doas"&gt;Emacs Tramp&lt;/a&gt; not only to use &lt;tt class="docutils literal"&gt;sudo&lt;/tt&gt; on localhost
with the Emacs running under unprivilidged&amp;nbsp;user:&lt;/p&gt;
&lt;pre class="literal-block"&gt;
C-x C-f /sudo:localhost:/path/to/file RET
&lt;/pre&gt;
&lt;p&gt;or that I can &lt;tt class="docutils literal"&gt;ssh&lt;/tt&gt; with&amp;nbsp;Emacs:&lt;/p&gt;
&lt;pre class="literal-block"&gt;
C-x C-f /ssh:pilosus&amp;#64;raspberrypi:/path/to/file RET
&lt;/pre&gt;
&lt;p&gt;but I also can pipeline &lt;tt class="docutils literal"&gt;ssh …&lt;/tt&gt;&lt;/p&gt;</summary><content type="html">&lt;p&gt;&lt;span class="caps"&gt;TIL&lt;/span&gt; I can use &lt;a class="reference external" href="https://www.gnu.org/software/tramp/#Combining-ssh-or-plink-with-su_002c-sudo-or-doas"&gt;Emacs Tramp&lt;/a&gt; not only to use &lt;tt class="docutils literal"&gt;sudo&lt;/tt&gt; on localhost
with the Emacs running under unprivilidged&amp;nbsp;user:&lt;/p&gt;
&lt;pre class="literal-block"&gt;
C-x C-f /sudo:localhost:/path/to/file RET
&lt;/pre&gt;
&lt;p&gt;or that I can &lt;tt class="docutils literal"&gt;ssh&lt;/tt&gt; with&amp;nbsp;Emacs:&lt;/p&gt;
&lt;pre class="literal-block"&gt;
C-x C-f /ssh:pilosus&amp;#64;raspberrypi:/path/to/file RET
&lt;/pre&gt;
&lt;p&gt;but I also can pipeline &lt;tt class="docutils literal"&gt;ssh&lt;/tt&gt; session with &lt;tt class="docutils literal"&gt;sudo&lt;/tt&gt;:&lt;/p&gt;
&lt;pre class="literal-block"&gt;
C-x C-f /ssh:pilosus&amp;#64;raspberrypi|sudo:raspberrypi:/path/to/file RET
&lt;/pre&gt;
&lt;p&gt;which is a shortcut&amp;nbsp;for:&lt;/p&gt;
&lt;pre class="literal-block"&gt;
C-x C-f /ssh:pilosus&amp;#64;raspberrypi|sudo:root&amp;#64;raspberrypi:/path/to/file RET
&lt;/pre&gt;
</content><category term="TIL"></category><category term="emacs"></category></entry><entry><title>Unmaintained technical documentation</title><link href="https://blog.pilosus.org/posts/2024/01/06/unmaintained-technical-documentation/" rel="alternate"></link><published>2024-01-06T18:04:00+01:00</published><updated>2024-01-06T18:04:00+01:00</updated><author><name>Vitaly Samigullin</name></author><id>tag:blog.pilosus.org,2024-01-06:/posts/2024/01/06/unmaintained-technical-documentation/</id><summary type="html">&lt;p&gt;I think that the problem of unmaintained technical documentation has
nothing to do with the &amp;#8220;wrong&amp;#8221; tools. It&amp;#8217;s not about what&amp;#8217;s better:
Notion, Wiki or a bunch of version-controlled Markdown files. Rather,
the problem boils down to the following&amp;nbsp;problems:&lt;/p&gt;
&lt;p&gt;1. Wrong ownership of the documentation (who really …&lt;/p&gt;</summary><content type="html">&lt;p&gt;I think that the problem of unmaintained technical documentation has
nothing to do with the &amp;#8220;wrong&amp;#8221; tools. It&amp;#8217;s not about what&amp;#8217;s better:
Notion, Wiki or a bunch of version-controlled Markdown files. Rather,
the problem boils down to the following&amp;nbsp;problems:&lt;/p&gt;
&lt;p&gt;1. Wrong ownership of the documentation (who really cares: a
developer, a tech lead, a product manager,&amp;nbsp;etc.)&lt;/p&gt;
&lt;p&gt;2. Wrong documentation form (&lt;a class="reference external" href="https://diataxis.fr/"&gt;Diátaxis&lt;/a&gt; approach with tutorial,
how-to, reference, explanation formats of&amp;nbsp;docs)&lt;/p&gt;
&lt;p&gt;3. An attempt to solve a problem with the docs where code or
automation can be written or a work process&amp;nbsp;introduced&lt;/p&gt;
&lt;div class="section" id="ownership-docs-form"&gt;
&lt;h2&gt;Ownership &lt;span class="amp"&gt;&amp;amp;&lt;/span&gt; docs&amp;nbsp;form&lt;/h2&gt;
&lt;p&gt;These two points are better explained with the&amp;nbsp;examples.&lt;/p&gt;
&lt;p&gt;Example 1: as a developer of a closed-source piece of software with no
public APIs, I probably don&amp;#8217;t care about writing the docs in the
format of reference. This is because I am responsible for the code, I
work with the code on a daily basis and hence I try to write my code
to be self-documenting. On the other hand, if today I am an on-duty
engineer, I would thank my colleagues for writing a short how-to guide
that will help me to quickly fix a rare problem that occurs, say, once
a month or a quarter. I&amp;#8217;ll likely to write or update such a how-to
guide myself until we have capacities or increased problem occurrence
to automate the how-to guide, so that it&amp;#8217;s the code again and docs are
no longer needed. I don&amp;#8217;t expect that how-to guides are written by a
team lead or a technical writer; it&amp;#8217;s developers who need&amp;nbsp;them.&lt;/p&gt;
&lt;p&gt;Example 2: as a team lead or a product owner, it&amp;#8217;s very likely that I
want to have some higher-level understanding-oriented form of docs
maintained. So that it&amp;#8217;s easier for me to onboard a new developer or
explain a a complex concept with its context to someone, etc. I don&amp;#8217;t
expect devs to write these docs for me, it&amp;#8217;s my responsibility and
ownership. I am interested in maintaining it and I&amp;#8217;ll suffer if the
docs are unmaintained when I need to answer the same questions over
and over&amp;nbsp;again.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="docs-instead-of-automation-or-a-process"&gt;
&lt;h2&gt;Docs instead of automation or a&amp;nbsp;process&lt;/h2&gt;
&lt;p&gt;It&amp;#8217;s nice to come up with a checklist document for a new employee&amp;nbsp;onboarding:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;add the GitHub account to the&amp;nbsp;organisation&lt;/li&gt;
&lt;li&gt;add relevant permissions on &lt;span class="caps"&gt;GCP&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;send a &lt;span class="caps"&gt;VPN&lt;/span&gt;&amp;nbsp;certificate&lt;/li&gt;
&lt;li&gt;&amp;#8230;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;It&amp;#8217;s much better than not having it at all. But it&amp;#8217;s even better if
you implement the checklist as a automated process with your
Infrastructure-as-a-Code approach. It will take more time to implement
first, but once done it&amp;#8217;s guaranteed to be&amp;nbsp;maintained.&lt;/p&gt;
&lt;p&gt;Same with the managerial work processes. It&amp;#8217;s nice to outline it with
a document first, but once implemented, keep only relevant forms of
the docs maintained by the people who are interested to have these
docs updated. They still may need help organising the process of the
docs maintenance: e.g. every time when someone disturbs a colleague
asking how to do something, they are instructed, but also asked to
update the docs with the answer they just got. They can also have a
task to update the docs during their on-duty&amp;nbsp;rotation.&lt;/p&gt;
&lt;/div&gt;
</content><category term="Blog"></category><category term="docs"></category></entry><entry><title>Multiple WiFi network connections on Rapspberry Pi 5</title><link href="https://blog.pilosus.org/posts/2024/01/06/multiple-wifi-network-connections-on-rapspberry-pi-5/" rel="alternate"></link><published>2024-01-06T16:10:00+01:00</published><updated>2024-01-06T16:10:00+01:00</updated><author><name>Vitaly Samigullin</name></author><id>tag:blog.pilosus.org,2024-01-06:/posts/2024/01/06/multiple-wifi-network-connections-on-rapspberry-pi-5/</id><summary type="html">&lt;p&gt;This post was intended to be an answer to &lt;a class="reference external" href="https://raspberrypi.stackexchange.com/questions/11631/how-to-setup-multiple-wifi-networks"&gt;this question&lt;/a&gt;. But given
that I&amp;#8217;m a new Raspberry Pi&amp;#8217;s StackExchange user and I don&amp;#8217;t have
enough karma, I cannot post my answers yet, hence a post in my own&amp;nbsp;blog.&lt;/p&gt;
&lt;p&gt;Problem statement: I want to have multiple …&lt;/p&gt;</summary><content type="html">&lt;p&gt;This post was intended to be an answer to &lt;a class="reference external" href="https://raspberrypi.stackexchange.com/questions/11631/how-to-setup-multiple-wifi-networks"&gt;this question&lt;/a&gt;. But given
that I&amp;#8217;m a new Raspberry Pi&amp;#8217;s StackExchange user and I don&amp;#8217;t have
enough karma, I cannot post my answers yet, hence a post in my own&amp;nbsp;blog.&lt;/p&gt;
&lt;p&gt;Problem statement: I want to have multiple networks to connect my
Raspberry Pi 5 device to, e.g. a WiFi connection at home and another
WiFi connection at work. Previously, all the WiFI network
configuration used to be done with the &lt;tt class="docutils literal"&gt;wpa_applicant&lt;/tt&gt; alongs with
many other network-related tools. Now, it seems, &lt;tt class="docutils literal"&gt;NetworkManager&lt;/tt&gt;
and its plugins took over many Linux distributions, including
Raspberry Pi &lt;span class="caps"&gt;OS&lt;/span&gt;.&lt;/p&gt;
&lt;p&gt;According to the &lt;a class="reference external" href="https://downloads.raspberrypi.com/raspios_full_armhf/release_notes.txt"&gt;changelog&lt;/a&gt;, in September 2022 and later in December
2023 &lt;tt class="docutils literal"&gt;NetworkManager&lt;/tt&gt; started to be used as a high-level interface
for all the network configuration on Raspberry Pi &lt;span class="caps"&gt;OS&lt;/span&gt;. So, as of
January 2024 for Raspberry Pi 5 under Debian 12.4 Bookwork (Raspberry
Pi &lt;span class="caps"&gt;OS&lt;/span&gt;) this is how one can have multiple WiFi connection profiles set&amp;nbsp;up.&lt;/p&gt;
&lt;div class="section" id="headless-set-up"&gt;
&lt;h2&gt;Headless&amp;nbsp;set-up&lt;/h2&gt;
&lt;ol class="arabic simple"&gt;
&lt;li&gt;Connect to your device and become a &lt;tt class="docutils literal"&gt;root&lt;/tt&gt; user&lt;/li&gt;
&lt;/ol&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;$&lt;span class="w"&gt; &lt;/span&gt;ssh&lt;span class="w"&gt; &lt;/span&gt;user@raspberrypi.local
$&lt;span class="w"&gt; &lt;/span&gt;sudo&lt;span class="w"&gt; &lt;/span&gt;-s
&lt;/pre&gt;&lt;/div&gt;
&lt;ol class="arabic simple" start="2"&gt;
&lt;li&gt;Find all the WiFi networks available and network&amp;nbsp;devices&lt;/li&gt;
&lt;/ol&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;$&lt;span class="w"&gt; &lt;/span&gt;nmcli&lt;span class="w"&gt; &lt;/span&gt;dev&lt;span class="w"&gt; &lt;/span&gt;wifi
$&lt;span class="w"&gt; &lt;/span&gt;nmcli&lt;span class="w"&gt; &lt;/span&gt;dev
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;note the &lt;tt class="docutils literal"&gt;&lt;span class="caps"&gt;SSID&lt;/span&gt;&lt;/tt&gt; (your network &lt;span class="caps"&gt;ID&lt;/span&gt;) and the &lt;tt class="docutils literal"&gt;Device&lt;/tt&gt; name
(e.g. &lt;tt class="docutils literal"&gt;wlan0&lt;/tt&gt; of type &lt;tt class="docutils literal"&gt;wifi&lt;/tt&gt;).&lt;/p&gt;
&lt;ol class="arabic simple" start="3"&gt;
&lt;li&gt;Run a &lt;tt class="docutils literal"&gt;NetworkManager&lt;/tt&gt; config tool with the text-based&amp;nbsp;interface&lt;/li&gt;
&lt;/ol&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;$&lt;span class="w"&gt; &lt;/span&gt;nmtui
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Edit connection -&amp;gt; Add -&amp;gt;&amp;nbsp;WiFi.&lt;/p&gt;
&lt;ol class="arabic simple" start="4"&gt;
&lt;li&gt;Add a new connection&amp;nbsp;profile:&lt;/li&gt;
&lt;/ol&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;profile&amp;nbsp;name&lt;/li&gt;
&lt;li&gt;&lt;tt class="docutils literal"&gt;&lt;span class="caps"&gt;SSID&lt;/span&gt;&lt;/tt&gt; from the step&amp;nbsp;2&lt;/li&gt;
&lt;li&gt;Security (usually &lt;tt class="docutils literal"&gt;&lt;span class="caps"&gt;WPA&lt;/span&gt; &lt;span class="amp"&gt;&amp;amp;&lt;/span&gt; &lt;span class="caps"&gt;WPA2&lt;/span&gt; Personal&lt;/tt&gt; for the home WiFi routers +&amp;nbsp;password)&lt;/li&gt;
&lt;li&gt;Other settings if needed (e.g. if no &lt;tt class="docutils literal"&gt;&lt;span class="caps"&gt;DHCP&lt;/span&gt;&lt;/tt&gt; is&amp;nbsp;used)&lt;/li&gt;
&lt;li&gt;&lt;tt class="docutils literal"&gt;&amp;lt;&lt;span class="caps"&gt;OK&lt;/span&gt;&amp;gt;&lt;/tt&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;ol class="arabic simple" start="6"&gt;
&lt;li&gt;Restart the &lt;tt class="docutils literal"&gt;NetworkManager&lt;/tt&gt; service&lt;/li&gt;
&lt;/ol&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;$&lt;span class="w"&gt; &lt;/span&gt;systemctl&lt;span class="w"&gt; &lt;/span&gt;restart&lt;span class="w"&gt; &lt;/span&gt;NetworkManager
&lt;/pre&gt;&lt;/div&gt;
&lt;ol class="arabic simple" start="7"&gt;
&lt;li&gt;To switch between the connection profiles&amp;nbsp;manually&lt;/li&gt;
&lt;/ol&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;$&lt;span class="w"&gt; &lt;/span&gt;nmtui
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Activate a connection -&amp;gt; Choose a profile&amp;nbsp;needed&lt;/p&gt;
&lt;p&gt;The same can be done via the &lt;span class="caps"&gt;CLI&lt;/span&gt;&amp;nbsp;tool&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;$&lt;span class="w"&gt; &lt;/span&gt;nmcli&lt;span class="w"&gt; &lt;/span&gt;connection&lt;span class="w"&gt; &lt;/span&gt;down&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;profile1&amp;#39;&lt;/span&gt;
$&lt;span class="w"&gt; &lt;/span&gt;nmcli&lt;span class="w"&gt; &lt;/span&gt;connection&lt;span class="w"&gt; &lt;/span&gt;up&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;profile2&amp;#39;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Beware not to deactivate your only active connection! Otherwise you
may found your &lt;cite&gt;ssh&lt;/cite&gt; session hang. You&amp;#8217;ll need to restart the device
(if no other connections exist) and reconnect in this&amp;nbsp;case.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="desktop"&gt;
&lt;h2&gt;Desktop&lt;/h2&gt;
&lt;p&gt;I haven&amp;#8217;t used Debian Bookworm with the &lt;span class="caps"&gt;GNOME&lt;/span&gt; desktop environment, but
I guess it uses &lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;nm-connection-manager&lt;/span&gt;&lt;/tt&gt; as a &lt;span class="caps"&gt;GUI&lt;/span&gt; alternative to
&lt;tt class="docutils literal"&gt;nmcli&lt;/tt&gt; and &lt;tt class="docutils literal"&gt;nmtui&lt;/tt&gt; and is a part of the &lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;network-manager-gnome&lt;/span&gt;&lt;/tt&gt;
package.&lt;/p&gt;
&lt;p&gt;More details on&amp;nbsp;NetworkManager:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;a class="reference external" href="https://wiki.debian.org/WiFi/HowToUse#NetworkManager"&gt;WiFi on Debian&amp;nbsp;Wiki&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="reference external" href="https://wiki.debian.org/NetworkManager"&gt;NetworkManager on Debian&amp;nbsp;Wiki&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="reference external" href="https://wiki.archlinux.org/title/NetworkManager"&gt;NetworkManager on ArchLinux&amp;nbsp;Wiki&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
</content><category term="Blog"></category><category term="raspberrypi"></category><category term="linux"></category></entry><entry><title>DNS sinkhole with dnsmasq on local Linux machine</title><link href="https://blog.pilosus.org/posts/2024/01/01/dns-sinkhole-with-dnsmasq-on-local-linux-machine/" rel="alternate"></link><published>2024-01-01T21:38:00+01:00</published><updated>2024-01-01T21:38:00+01:00</updated><author><name>Vitaly Samigullin</name></author><id>tag:blog.pilosus.org,2024-01-01:/posts/2024/01/01/dns-sinkhole-with-dnsmasq-on-local-linux-machine/</id><summary type="html">&lt;p&gt;Recently I&amp;#8217;ve posted a wrap-up on my new tiny &lt;span class="caps"&gt;DNS&lt;/span&gt; analyzer tool called
&lt;a class="reference external" href="https://blog.pilosus.org/posts/2023/12/29/dnseen-simple-dns-queries-analyzer/"&gt;dnseen&lt;/a&gt;. One of the development goals was generating a top-list of
websites that I visit along with the frequencies, so that I can
blacklist websites that I think I spend too much time on without
long-term …&lt;/p&gt;</summary><content type="html">&lt;p&gt;Recently I&amp;#8217;ve posted a wrap-up on my new tiny &lt;span class="caps"&gt;DNS&lt;/span&gt; analyzer tool called
&lt;a class="reference external" href="https://blog.pilosus.org/posts/2023/12/29/dnseen-simple-dns-queries-analyzer/"&gt;dnseen&lt;/a&gt;. One of the development goals was generating a top-list of
websites that I visit along with the frequencies, so that I can
blacklist websites that I think I spend too much time on without
long-term benefits. These are mostly news and social media that use
manipulative algorithms to make you a &amp;#8220;dophamine addict&amp;#8221;. Another
thing that I wanted to implement is blacklisting for ads/tracking
services that works on the &lt;span class="caps"&gt;DNS&lt;/span&gt; level, so that I don&amp;#8217;t need to worry
about browser extensions in case I want to try a new&amp;nbsp;browser.&lt;/p&gt;
&lt;p&gt;I used to have a list of blacklisted domains in my &lt;tt class="docutils literal"&gt;/etc/hosts&lt;/tt&gt; file
for a while. One obvious drawback for me is the need to lump
everything in one file. Instead, in a perfect solution, I&amp;#8217;d like to
split blacklisted domain into multiple files, so that I can easily
break them down by topic and/or automate some hosts file generation
from third-party curated lists, like hosts files from &lt;a class="reference external" href="https://github.com/StevenBlack/hosts"&gt;Steven Black&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;One of the popular options is to set up a &lt;a class="reference external" href="https://pi-hole.net/"&gt;Pi-hole&lt;/a&gt; on the local
network and use it as the local &lt;span class="caps"&gt;DNS&lt;/span&gt; server. I do like this option too,
but before picking up ready to use products, I&amp;#8217;d like to &amp;#8220;reinvent the
wheel&amp;#8221; and build by own prototype first. This is how I came up with
the idea of building my own &lt;a class="reference external" href="https://en.wikipedia.org/wiki/DNS_sinkhole"&gt;&lt;span class="caps"&gt;DNS&lt;/span&gt; sinkhole&lt;/a&gt; with the help of
&lt;a class="reference external" href="https://dnsmasq.org/"&gt;dnsmasq&lt;/a&gt; as a &lt;span class="caps"&gt;DNS&lt;/span&gt; server on my local &lt;a class="reference external" href="https://blog.pilosus.org/posts/2023/12/10/my-linux-journey-so-far-status-update-2023/"&gt;Linux&lt;/a&gt;&amp;nbsp;machine.&lt;/p&gt;
&lt;div class="section" id="linux-networkmanager-dnsmasq"&gt;
&lt;h2&gt;Linux + NetworkManager +&amp;nbsp;dnsmasq&lt;/h2&gt;
&lt;p&gt;After installing &lt;a class="reference external" href="https://dnsmasq.org/"&gt;dnsmasq&lt;/a&gt; on the Fedora 39 Linux&amp;nbsp;machine:&lt;/p&gt;
&lt;pre class="literal-block"&gt;
sudo dnf -y install dnsmasq
&lt;/pre&gt;
&lt;p&gt;we need to make sure it&amp;#8217;s configured as a &lt;a class="reference external" href="https://networkmanager.dev/docs/admins/"&gt;NetworkManager&lt;/a&gt; plugin
rather than &lt;tt class="docutils literal"&gt;systemd&lt;/tt&gt;, so that &lt;tt class="docutils literal"&gt;dnsmasq&lt;/tt&gt; daemon is run correctly
and redefined the &lt;tt class="docutils literal"&gt;/etc/resolv.conf&lt;/tt&gt;, the resolver configuration
file used for &lt;span class="caps"&gt;DNS&lt;/span&gt;&amp;nbsp;lookups.&lt;/p&gt;
&lt;p&gt;First of all, we need to activate the &lt;tt class="docutils literal"&gt;NetworkManager&lt;/tt&gt; plugin:&lt;/p&gt;
&lt;pre class="literal-block"&gt;
$ cat /etc/NetworkManager/conf.d/00-use-dnsmasq.conf
[main]
dns=dnsmasq
&lt;/pre&gt;
&lt;p&gt;Now, we can configure the &lt;tt class="docutils literal"&gt;dnsmasq&lt;/tt&gt; itself:&lt;/p&gt;
&lt;pre class="literal-block"&gt;
$ cat /etc/NetworkManager/dnsmasq.d/00-addn-hosts.conf
# NB! When making changes, don't forget restart dnsmasq &amp;amp; NetworkManager.service:
# $ killall dnsmasq
# $ systemctl restart NetworkManager

# No need to read /etc/resolv.conf as it contains the localhost
# addresses of dnsmasq itself
no-resolv

# Upstream DNS servers
# My router, Google, CloudFlare
server=192.168.0.1
server=8.8.8.8
server=1.1.1.1

# Add debug logging
# $ journalctl --follow -u NetworkManager
log-queries

# Ignore /etc/hosts
no-hosts

# Add custom hosts files;
# If path is a directory, all files from it are loaded
addn-hosts=/etc/hosts.d
&lt;/pre&gt;
&lt;p&gt;So, basically we want to keep our multiple &lt;tt class="docutils literal"&gt;hosts&lt;/tt&gt; files under
&lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;addn-hosts=/etc/hosts.d&lt;/span&gt;&lt;/tt&gt; directory and make sure that if a hostname
cannot be resolved using these files we fallback to the upstream &lt;span class="caps"&gt;DNS&lt;/span&gt;
servers that are the one my local network&amp;#8217;s router provides, as well
as public ones from Google and&amp;nbsp;CloudFlare.&lt;/p&gt;
&lt;p&gt;Now, given that my Fedora machine also runs &lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;systemd-resolved&lt;/span&gt;&lt;/tt&gt;, a
system service that provides network name resolution to local
applications, we want to prevent both &lt;tt class="docutils literal"&gt;dnsmasq&lt;/tt&gt; and
&lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;systemd-resolved&lt;/span&gt;&lt;/tt&gt; to mess up with the &lt;tt class="docutils literal"&gt;/etc/resolv.conf&lt;/tt&gt; file. So
we stop and disable &lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;systemd-resolved&lt;/span&gt;&lt;/tt&gt;:&lt;/p&gt;
&lt;pre class="literal-block"&gt;
$ sudo systemctl stop systemd-resolved.service
$ sudo systemctl disable systemd-resolved.service
&lt;/pre&gt;
&lt;p&gt;We can add a hosts file for testing&amp;nbsp;purposes:&lt;/p&gt;
&lt;pre class="literal-block"&gt;
$ cat /etc/hosts.d/news
0.0.0.0 example.com
0.0.0.0 news.ycombinator.com
&lt;/pre&gt;
&lt;p&gt;Next, let&amp;#8217;s validate &lt;tt class="docutils literal"&gt;dnqmasq&lt;/tt&gt; config and restart the relevant&amp;nbsp;services:&lt;/p&gt;
&lt;pre class="literal-block"&gt;
$ dnsmasq --test
$ sudo killall dnsmasq
$ sudo systemctl restart NetworkManager
&lt;/pre&gt;
&lt;/div&gt;
&lt;div class="section" id="check-if-local-dns-works-as-expected"&gt;
&lt;h2&gt;Check if local &lt;span class="caps"&gt;DNS&lt;/span&gt; works as&amp;nbsp;expected&lt;/h2&gt;
&lt;p&gt;First of all, let&amp;#8217;s look at the &lt;tt class="docutils literal"&gt;dnsmasq&lt;/tt&gt; logs:&lt;/p&gt;
&lt;pre class="literal-block"&gt;
$ journalctl --follow -u NetworkManager
...
Jan 01 21:03:16 fedora dnsmasq[16321]: using nameserver 192.168.0.1#53
Jan 01 21:03:16 fedora dnsmasq[16321]: using nameserver 8.8.8.8#53
Jan 01 21:03:16 fedora dnsmasq[16321]: using nameserver 1.1.1.1#53
Jan 01 21:03:16 fedora dnsmasq[16321]: read /etc/hosts.d/news - 2 names
&lt;/pre&gt;
&lt;p&gt;Let&amp;#8217;s also make sure &lt;tt class="docutils literal"&gt;/etc/resolv.conf&lt;/tt&gt; is controlled by
&lt;tt class="docutils literal"&gt;NetworkManager&lt;/tt&gt;:&lt;/p&gt;
&lt;pre class="literal-block"&gt;
$ cat /etc/resolv.conf
# Generated by NetworkManager
nameserver 127.0.0.1
options edns0 trust-ad
&lt;/pre&gt;
&lt;p&gt;Now, let&amp;#8217;s see if &lt;span class="caps"&gt;DNS&lt;/span&gt; lookups work&amp;nbsp;properly:&lt;/p&gt;
&lt;pre class="literal-block"&gt;
$ dig news.ycombinator.com

; &amp;lt;&amp;lt;&amp;gt;&amp;gt; DiG 9.18.20 &amp;lt;&amp;lt;&amp;gt;&amp;gt; news.ycombinator.com
;; global options: +cmd
;; Got answer:
;; -&amp;gt;&amp;gt;HEADER&amp;lt;&amp;lt;- opcode: QUERY, status: NOERROR, id: 15461
;; flags: qr aa rd ra; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 1

;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags:; udp: 1232
;; QUESTION SECTION:
;news.ycombinator.com.                IN      A

;; ANSWER SECTION:
news.ycombinator.com. 0       IN      A       0.0.0.0

;; Query time: 0 msec
;; SERVER: 127.0.0.1#53(127.0.0.1) (UDP)
;; WHEN: Mon Jan 01 21:18:57 CET 2024
;; MSG SIZE  rcvd: 65
&lt;/pre&gt;
&lt;p&gt;So, yes, &lt;tt class="docutils literal"&gt;&lt;span class="caps"&gt;SERVER&lt;/span&gt;: 127.0.0.1#53(127.0.0.1) (&lt;span class="caps"&gt;UDP&lt;/span&gt;)&lt;/tt&gt; is telling us a
localhost &lt;span class="caps"&gt;DNS&lt;/span&gt; is being used, and &lt;tt class="docutils literal"&gt;A 0.0.0.0&lt;/tt&gt; from the &lt;tt class="docutils literal"&gt;&lt;span class="caps"&gt;ANSWER&lt;/span&gt;
&lt;span class="caps"&gt;SECTION&lt;/span&gt;&lt;/tt&gt; shows us it&amp;#8217;s resolved to the &lt;tt class="docutils literal"&gt;0.0.0.0&lt;/tt&gt; &lt;span class="caps"&gt;IP&lt;/span&gt; address from
our hosts&amp;nbsp;file.&lt;/p&gt;
&lt;p&gt;One the same time, if you try to access the same domain in a web
browser or using &lt;tt class="docutils literal"&gt;curl&lt;/tt&gt;, you will see it&amp;#8217;s still accessible. The
thing is &lt;tt class="docutils literal"&gt;dig&lt;/tt&gt; tool is using &lt;tt class="docutils literal"&gt;/etc/resolv.conf&lt;/tt&gt; settings for &lt;span class="caps"&gt;DNS&lt;/span&gt;,
while other applications rely on the &lt;span class="caps"&gt;DNS&lt;/span&gt; settings from the network
connection. Let&amp;#8217;s see what are these &lt;span class="caps"&gt;DNS&lt;/span&gt; setings using
&lt;tt class="docutils literal"&gt;NetworkManager&lt;/tt&gt; &lt;span class="caps"&gt;CLI&lt;/span&gt;&amp;nbsp;tool:&lt;/p&gt;
&lt;pre class="literal-block"&gt;
$ nmcli device show | grep -i dns
IP4.DNS[1]:                             192.168.0.1
IP6.DNS[1]:                             2a02:8383:d:c::1000
IP6.DNS[2]:                             2a02:8383:d:c::1
&lt;/pre&gt;
&lt;p&gt;These are actually &lt;span class="caps"&gt;DNS&lt;/span&gt; addresses from my WiFi router. Given that my
machine receives &lt;span class="caps"&gt;IP&lt;/span&gt;-address from the router&amp;#8217;s &lt;span class="caps"&gt;DHCP&lt;/span&gt; server, it also
gets the &lt;span class="caps"&gt;DNS&lt;/span&gt;&amp;nbsp;settings.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="configuring-connection-s-settings"&gt;
&lt;h2&gt;Configuring connection&amp;#8217;s&amp;nbsp;settings&lt;/h2&gt;
&lt;p&gt;We can override the settings in &lt;tt class="docutils literal"&gt;NetworkManager&lt;/tt&gt; &lt;span class="caps"&gt;GUI&lt;/span&gt;. Open up
&lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;nm-connection-editor&lt;/span&gt;&lt;/tt&gt; (or Settings -&amp;gt; WiFi -&amp;gt; Network you&amp;#8217;re
connected to Settings), then&amp;nbsp;IPv4:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;Method -&amp;gt; Automatic (&lt;span class="caps"&gt;DHCP&lt;/span&gt;) addresses&amp;nbsp;only&lt;/li&gt;
&lt;li&gt;&lt;span class="caps"&gt;DNS&lt;/span&gt; Server -&amp;gt; 127.0.0.1 (instead of automatic&amp;nbsp;one)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;then for&amp;nbsp;IPv6:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;Method -&amp;gt; Automatic, &lt;span class="caps"&gt;DHCP&lt;/span&gt; only (watch out, it&amp;#8217;s different than for&amp;nbsp;IPv4)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This corresponds to the changes in
&lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;/etc/NetworkManager/system-connections/&lt;span class="caps"&gt;YOUR&lt;/span&gt;-&lt;span class="caps"&gt;CONNECTION&lt;/span&gt;-&lt;span class="caps"&gt;NAME&lt;/span&gt;.nmconnection&lt;/span&gt;&lt;/tt&gt;:&lt;/p&gt;
&lt;pre class="literal-block"&gt;
[ipv4]
dns=127.0.0.1;
ignore-auto-dns=true
method=auto

[ipv6]
addr-gen-mode=stable-privacy
dns=::1;
ignore-auto-dns=true
method=dhcp
&lt;/pre&gt;
&lt;p&gt;Now restart the &lt;tt class="docutils literal"&gt;NetworkManager&lt;/tt&gt; and check &lt;span class="caps"&gt;DNS&lt;/span&gt;&amp;nbsp;settings:&lt;/p&gt;
&lt;pre class="literal-block"&gt;
$ sudo systemctl restart NetworkManager
$ nmcli device show | grep -i dns
IP4.DNS[1]:                             127.0.0.1
IP6.DNS[1]:                             ::1
&lt;/pre&gt;
&lt;p&gt;All good! Local &lt;span class="caps"&gt;DNS&lt;/span&gt; sinkhole works. Now hosts files under
&lt;tt class="docutils literal"&gt;/etc/hosts.d&lt;/tt&gt; can be orginized easily and will be used for domains
resolution in all the requests coming from the configured&amp;nbsp;connection.&lt;/p&gt;
&lt;p&gt;&lt;span class="caps"&gt;P.S.&lt;/span&gt; I&amp;#8217;m deeply grateful to the Stackexchange community for the help
with the &lt;tt class="docutils literal"&gt;dnsmasq&lt;/tt&gt; config &lt;a class="reference external" href="https://serverfault.com/questions/1150701/dnsmasq-reads-addn-hosts-config-but-ignores-it-falls-back-to-systems-etc-host"&gt;troubleshooting&lt;/a&gt;.&lt;/p&gt;
&lt;/div&gt;
</content><category term="Blog"></category><category term="linux"></category></entry><entry><title>dnseen: simple DNS queries analyzer</title><link href="https://blog.pilosus.org/posts/2023/12/29/dnseen-simple-dns-queries-analyzer/" rel="alternate"></link><published>2023-12-29T20:00:00+01:00</published><updated>2024-01-07T17:39:00+01:00</updated><author><name>Vitaly Samigullin</name></author><id>tag:blog.pilosus.org,2023-12-29:/posts/2023/12/29/dnseen-simple-dns-queries-analyzer/</id><summary type="html">&lt;p&gt;&lt;a class="reference external" href="https://github.com/pilosus/dnseen/"&gt;dnseen&lt;/a&gt; is my new tiny open source project to ease collection of &lt;span class="caps"&gt;DNS&lt;/span&gt;
requests coming from the local machine and aggregating statistics over
given period of time. &lt;a class="reference external" href="https://github.com/pilosus/dnseen/"&gt;dnseen&lt;/a&gt; is written in Clojure programming
language (with &lt;a class="reference external" href="https://babashka.org/"&gt;babashka&lt;/a&gt;) and works as a wrapper over &lt;a class="reference external" href="https://www.tcpdump.org/"&gt;tcpdump&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;It&amp;#8217;s written with the following goals …&lt;/p&gt;</summary><content type="html">&lt;p&gt;&lt;a class="reference external" href="https://github.com/pilosus/dnseen/"&gt;dnseen&lt;/a&gt; is my new tiny open source project to ease collection of &lt;span class="caps"&gt;DNS&lt;/span&gt;
requests coming from the local machine and aggregating statistics over
given period of time. &lt;a class="reference external" href="https://github.com/pilosus/dnseen/"&gt;dnseen&lt;/a&gt; is written in Clojure programming
language (with &lt;a class="reference external" href="https://babashka.org/"&gt;babashka&lt;/a&gt;) and works as a wrapper over &lt;a class="reference external" href="https://www.tcpdump.org/"&gt;tcpdump&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;It&amp;#8217;s written with the following goals in&amp;nbsp;mind:&lt;/p&gt;
&lt;div class="section" id="goals"&gt;
&lt;h2&gt;Goals&lt;/h2&gt;
&lt;ol class="arabic simple"&gt;
&lt;li&gt;Get a top-list of websites that I visit&amp;nbsp;intentionally&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;This is mostly to reduce media consumption and remove distracting
websites that make you a dophamine addict and bring so little (yes,
HackerNews is one of them!). For now I simply add domains I want to
block to my &lt;tt class="docutils literal"&gt;/etc/hosts&lt;/tt&gt; like&amp;nbsp;this:&lt;/p&gt;
&lt;pre class="literal-block"&gt;
0.0.0.0 domain
&lt;/pre&gt;
&lt;p&gt;But as my next step I&amp;#8217;d like to use &lt;tt class="docutils literal"&gt;dnsmasq&lt;/tt&gt; and maintain an
extended collection of &lt;tt class="docutils literal"&gt;hosts&lt;/tt&gt; files, possibly using Steven Blacks
&lt;a class="reference external" href="https://github.com/StevenBlack/hosts"&gt;hosts files collection&lt;/a&gt; (upd. see my &lt;a class="reference external" href="https://blog.pilosus.org/posts/2024/01/01/dns-sinkhole-with-dnsmasq-on-local-linux-machine/"&gt;dnsmasq guide&lt;/a&gt;).&lt;/p&gt;
&lt;ol class="arabic simple" start="2"&gt;
&lt;li&gt;Find anomalies related to tracking&amp;nbsp;activities&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Even the software that claims to protect user&amp;#8217;s privacy may track &lt;a class="reference external" href="https://www.kuketz-blog.de/mozilla-firefox-datensendeverhalten-desktop-version-browser-check-teil20/"&gt;too
much&lt;/a&gt;. So I want to collect &lt;span class="caps"&gt;DNS&lt;/span&gt; requests over longer period of time
to see if I can find any suspicious&amp;nbsp;activity.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="alternatives"&gt;
&lt;h2&gt;Alternatives&lt;/h2&gt;
&lt;p&gt;Sure enough, one can use &lt;a class="reference external" href="https://www.tcpdump.org/"&gt;tcpdump&lt;/a&gt;, &lt;a class="reference external" href="https://www.wireshark.org/"&gt;wireshark&lt;/a&gt; or other packet
analyzer to do the job. But they are relatively complex and designed
for troubleshooting rather than long-term analytics based on
aggregated&amp;nbsp;data.&lt;/p&gt;
&lt;p&gt;Or one can use tools developed to track &lt;span class="caps"&gt;DNS&lt;/span&gt; queries specifically, like
&lt;a class="reference external" href="https://github.com/measurement-factory/dnstop"&gt;dnstop&lt;/a&gt; does. It&amp;#8217;s nice, but again, works more like &lt;tt class="docutils literal"&gt;top&lt;/tt&gt; tool
showing queries happening at the moment rather than&amp;nbsp;trends.&lt;/p&gt;
&lt;p&gt;I needed something that allows me to collect data over longer time
windows, apply filters and get some analysis done on the aggregated&amp;nbsp;results.&lt;/p&gt;
&lt;p&gt;Update 2024-01-07: the closest alternative is probably
&lt;a class="reference external" href="https://pi-hole.net/"&gt;Pi-hole&lt;/a&gt;. It&amp;#8217;s rich in features and visualisations, but probably a
bit too &amp;#8220;intrusive&amp;#8221; to be run on my local machine: too much code that
I don&amp;#8217;t have time to investigate, webserver with php website for the
stats dashboard. I&amp;#8217;d prefer to use it on a dedicated machine (like a
RaspberryPi device) if&amp;nbsp;needed.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="dnseen"&gt;
&lt;h2&gt;dnseen&lt;/h2&gt;
&lt;p&gt;&lt;a class="reference external" href="https://github.com/pilosus/dnseen/"&gt;dnseen&lt;/a&gt; is a simple &lt;span class="caps"&gt;DNS&lt;/span&gt; queries analyzer that works on top of the
&lt;tt class="docutils literal"&gt;tcpdump&lt;/tt&gt; logs. For now it works on Linux only (tested with Fedora
39), and get use of the &lt;tt class="docutils literal"&gt;systemd&lt;/tt&gt;. Under the hood it&amp;#8217;s just a
&lt;tt class="docutils literal"&gt;systemd&lt;/tt&gt; service running &lt;tt class="docutils literal"&gt;tcpdump&lt;/tt&gt; and writing simple plain-text
log to the file system. The analyzer program parses logs, applies
filters from command line options, and prints the statistics report to
the terminal. Reports can be printed for humans in pretty tabular
format or as a plain text to be further used in the Unix
pipelines. The program also comes with the installer script to make
the installation quick and&amp;nbsp;easy.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="findings"&gt;
&lt;h2&gt;Findings&lt;/h2&gt;
&lt;p&gt;So far &lt;a class="reference external" href="https://github.com/pilosus/dnseen/"&gt;dnseen&lt;/a&gt; seems to be handy. When it comes to finding sources
of excessive media consumption, it allowed me to block some news and
social websites that I don&amp;#8217;t really want to spend my time&amp;nbsp;with.&lt;/p&gt;
&lt;p&gt;Regarding detecting tracking activities, Fedora&amp;#8217;s &lt;tt class="docutils literal"&gt;NetworkManager&lt;/tt&gt;
turned out to send regular requests to help with the &lt;a class="reference external" href="https://www.reddit.com/r/Fedora/comments/15a0m2y/question_how_to_stop_periodic_dns_queries_for/"&gt;captive portal
detection&lt;/a&gt; that I don&amp;#8217;t need on my home desktop at all. I also
blocked quite a few Mozilla&amp;#8217;s domains related to the services I never
allowed Firefox to use, like Mozilla Location&amp;nbsp;Service.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="conclusion"&gt;
&lt;h2&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;All in all, the project seems to be useful. &lt;tt class="docutils literal"&gt;tcpdump&lt;/tt&gt; logs that
&lt;tt class="docutils literal"&gt;systemd&lt;/tt&gt; service&amp;#8217;s collecting can also be used for further analysis
with other tools, e.g. to determine &lt;span class="caps"&gt;DNS&lt;/span&gt; queries per domain with the
breakdown by&amp;nbsp;hours.&lt;/p&gt;
&lt;p&gt;Also, doing data analysis with Clojure is such a pleasure! &lt;a class="reference external" href="https://clojure.org/guides/threading_macros"&gt;Threading
macros&lt;/a&gt; is a game changer as it brings the power of Unix pipelines to
the functional programming&amp;nbsp;language.&lt;/p&gt;
&lt;p&gt;Using &lt;a class="reference external" href="https://babashka.org/"&gt;babashka&lt;/a&gt; feels good too. It&amp;#8217;s not my first babashka project,
but the first one using &lt;a class="reference external" href="https://github.com/babashka/cli"&gt;babashka.cli&lt;/a&gt; for command-line options
parsing. It&amp;#8217;s simple, yet&amp;nbsp;powerful!&lt;/p&gt;
&lt;p&gt;Developing with Clojure and Babashka allow me to work in a flow, with
almost no distractions to external world, as the language and its
ecosystem are very well designed, self-sufficient, and come with nice
tooling like &lt;a class="reference external" href="https://docs.cider.mx/cider/index.html"&gt;Emacs &lt;span class="caps"&gt;CIDER&lt;/span&gt;&lt;/a&gt;. It feels like tinkering in the garage in
my&amp;nbsp;childhood!&lt;/p&gt;
&lt;/div&gt;
</content><category term="Blog"></category><category term="clojure"></category><category term="foss"></category><category term="security"></category></entry><entry><title>Recording a screencast on Linux</title><link href="https://blog.pilosus.org/posts/2023/12/21/recording-a-screencast-on-linux/" rel="alternate"></link><published>2023-12-21T23:55:00+01:00</published><updated>2023-12-21T23:55:00+01:00</updated><author><name>Vitaly Samigullin</name></author><id>tag:blog.pilosus.org,2023-12-21:/posts/2023/12/21/recording-a-screencast-on-linux/</id><summary type="html">&lt;p&gt;&lt;span class="caps"&gt;TIL&lt;/span&gt; I can record a screencast using &lt;span class="caps"&gt;GNOME&lt;/span&gt;&amp;#8217;s &lt;a class="reference external" href="https://help.gnome.org/users/gnome-help/stable/screen-shot-record.html.en"&gt;tool&lt;/a&gt;,
trim it with &lt;tt class="docutils literal"&gt;ffmpeg&lt;/tt&gt; like&amp;nbsp;this:&lt;/p&gt;
&lt;pre class="literal-block"&gt;
cd ~/Screencasts
ffmpeg -i screencast.webm -ss 00:00:03.120 -t 00:00:11.500 output.webm
&lt;/pre&gt;
&lt;p&gt;then resize with&amp;nbsp;ImageMagick:&lt;/p&gt;
&lt;pre class="literal-block"&gt;
magick output.webm -resize 50% -layers coalesce output.webp
&lt;/pre&gt;
&lt;p&gt;and simply use …&lt;/p&gt;</summary><content type="html">&lt;p&gt;&lt;span class="caps"&gt;TIL&lt;/span&gt; I can record a screencast using &lt;span class="caps"&gt;GNOME&lt;/span&gt;&amp;#8217;s &lt;a class="reference external" href="https://help.gnome.org/users/gnome-help/stable/screen-shot-record.html.en"&gt;tool&lt;/a&gt;,
trim it with &lt;tt class="docutils literal"&gt;ffmpeg&lt;/tt&gt; like&amp;nbsp;this:&lt;/p&gt;
&lt;pre class="literal-block"&gt;
cd ~/Screencasts
ffmpeg -i screencast.webm -ss 00:00:03.120 -t 00:00:11.500 output.webm
&lt;/pre&gt;
&lt;p&gt;then resize with&amp;nbsp;ImageMagick:&lt;/p&gt;
&lt;pre class="literal-block"&gt;
magick output.webm -resize 50% -layers coalesce output.webp
&lt;/pre&gt;
&lt;p&gt;and simply use &lt;a class="reference external" href="https://en.wikipedia.org/wiki/WebP"&gt;webp&lt;/a&gt; as is instead of converting it to &lt;tt class="docutils literal"&gt;gif&lt;/tt&gt;, as
&lt;a class="reference external" href="https://caniuse.com/webp"&gt;browser support&lt;/a&gt; for &lt;tt class="docutils literal"&gt;webp&lt;/tt&gt; is pretty decent&amp;nbsp;nowadays.&lt;/p&gt;
</content><category term="TIL"></category><category term="linux"></category><category term="screencast"></category></entry><entry><title>My Linux journey so far: status update 2023</title><link href="https://blog.pilosus.org/posts/2023/12/10/my-linux-journey-so-far-status-update-2023/" rel="alternate"></link><published>2023-12-10T13:13:00+01:00</published><updated>2023-12-10T13:13:00+01:00</updated><author><name>Vitaly Samigullin</name></author><id>tag:blog.pilosus.org,2023-12-10:/posts/2023/12/10/my-linux-journey-so-far-status-update-2023/</id><summary type="html">&lt;p&gt;I started using Linux at the high school in 2002. 16 years old at that
time, I didn&amp;#8217;t have any contacts with the other Linux users in the
town where I lived. I did have a slow occassional dial-up internet
connection, a few books available in the local book …&lt;/p&gt;</summary><content type="html">&lt;p&gt;I started using Linux at the high school in 2002. 16 years old at that
time, I didn&amp;#8217;t have any contacts with the other Linux users in the
town where I lived. I did have a slow occassional dial-up internet
connection, a few books available in the local book store and a mail
order shop to purchase CDs with various Linux or &lt;span class="caps"&gt;BSD&lt;/span&gt; distributions. It
all started with the &lt;em&gt;Red Hat Linux 7.3 Valhalla&lt;/em&gt; back then. I also
tried Slackware, Debian, FreeBSD 4.5 and whatnot on my&amp;nbsp;desktop.&lt;/p&gt;
&lt;p&gt;During my time at the university (2003-2008), Linux was a no-go: the
whole education process was Windows-centric. After trying some
workarounds with Wine, I realised I was wasting more time on the
&amp;#8220;motorcycle maintenance&amp;#8221; than on the ride itself, so I gave&amp;nbsp;up.&lt;/p&gt;
&lt;p&gt;In 2011 I got Arch Linux distro installed on my pretty low-performant
machine I used for pet projects and other non-work-related things. It
was all the way the hacker machine, for sure! Tiling windows manager
(&lt;a class="reference external" href="https://wiki.archlinux.org/title/i3"&gt;i3-wm&lt;/a&gt; and even &lt;a class="reference external" href="https://wiki.archlinux.org/title/Ratpoison"&gt;ratpoison&lt;/a&gt;), even more never-ending &amp;#8220;motorcycle
maintenance&amp;#8221; with Emacs and &lt;span class="caps"&gt;OS&lt;/span&gt;&amp;nbsp;customizing!&lt;/p&gt;
&lt;p&gt;I also had Gentoo Linux on my second, even less performant &lt;a class="reference external" href="https://en.wikipedia.org/wiki/Sony_Vaio_P_series"&gt;tiny
machine&lt;/a&gt;. You can imaging building the Firefox browser on it from the
source code, let alone upgrading the whole system with &lt;tt class="docutils literal"&gt;emerge
world&lt;/tt&gt;.&lt;/p&gt;
&lt;p&gt;In early 2017 I realised that my current job leaves me almost no spare
time for a home-grown zoo of Linux distros. So I switched to Ubuntu
(16.04 Xenial back then) and had been using Ubuntu until July 2023. In
the lighthning talks session during the the &lt;a class="reference external" href="https://ep2023.europython.eu/"&gt;EuroPython 2023&lt;/a&gt;
conferece I attended this year, someone mentioned Fedora as a nice
alternative to Ubuntu for Python developers who need simplicity with
the up-to-date versions of the upstream software. Given that I wasn&amp;#8217;t
happy with how Ubuntu upgrade to 22.04 went, the mess with snap
packages, and ocassional performance issues I didn&amp;#8217;t have time to
troubleshoot properly, I decided to give Fedora 38 a&amp;nbsp;try.&lt;/p&gt;
&lt;p&gt;Now, 6 months and 1 system upgrade later, I can say that I am pretty
happy with what Fedora Desktop provides. It&amp;#8217;s simple, with the sound
defaults like &lt;span class="caps"&gt;GNOME&lt;/span&gt; Desktop and Firefox browser. &lt;span class="caps"&gt;DNF&lt;/span&gt; package manager
feels like at home without even looking up the man pages/help. The
software is not the bleeding edge, but pretty up to date. &lt;a class="reference external" href="https://developer.fedoraproject.org/tech/languages/python/multiple-pythons.html"&gt;Python
support&lt;/a&gt; is excellent indeed: you get the latest interpeter version
by default, but can install all the predecessors with ease, without
even bothering with &lt;a class="reference external" href="https://github.com/pyenv/pyenv"&gt;pyenv&lt;/a&gt;. Same with &lt;a class="reference external" href="https://docs.fedoraproject.org/en-US/quick-docs/installing-java/"&gt;Java&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Fedora release cycle is 13 month. I have to admit, that my &lt;a class="reference external" href="https://docs.fedoraproject.org/en-US/quick-docs/upgrading-fedora-offline/"&gt;upgrade&lt;/a&gt;
from Fedora 38 to 39 wasn&amp;#8217;t fully seamless. I had to switch SELinux
temporarily to the permissive mode to let the upgrade process update
the boot loader image on my &lt;span class="caps"&gt;UEFI&lt;/span&gt; machine. But Fedora&amp;#8217;s &lt;a class="reference external" href="https://discussion.fedoraproject.org/t/no-upgrade-executed-when-doing-system-upgrade-reboot/78163/4"&gt;discussion
forums&lt;/a&gt; are helpful and allowed me to fix the issue&amp;nbsp;quickly.&lt;/p&gt;
&lt;p&gt;All in all, Fedora feels like a right balance between the extremes:
the very stable distributions with outdated software like Debian and
the bleeding edge systems that are likely to break something during
the upgrades like Arch Linux; between the blows-and-whistles-heavy
popular distributions that seem to neglect the quality like late
Ubuntu and the spartan, distros with survivalist-flavor like&amp;nbsp;Gentoo.&lt;/p&gt;
&lt;p&gt;So far so good! Let&amp;#8217;s see how the upgrade to Fedora 40 will&amp;nbsp;go.&lt;/p&gt;
</content><category term="Blog"></category><category term="linux"></category></entry><entry><title>GNU Stow repo with git submodules</title><link href="https://blog.pilosus.org/posts/2023/09/11/gnu-stow-repo-with-git-submodules/" rel="alternate"></link><published>2023-09-11T15:00:00+02:00</published><updated>2023-09-11T15:00:00+02:00</updated><author><name>Vitaly Samigullin</name></author><id>tag:blog.pilosus.org,2023-09-11:/posts/2023/09/11/gnu-stow-repo-with-git-submodules/</id><summary type="html">&lt;p&gt;&lt;span class="caps"&gt;GNU&lt;/span&gt; &lt;a class="reference external" href="https://www.gnu.org/software/stow/manual/stow.html"&gt;Stow&lt;/a&gt; is a tool to manage symlinks from a single directory. That
makes the tool a perfect candidte to keep configuration files (or
dotfiles) in a &lt;span class="caps"&gt;VCS&lt;/span&gt; repository, such as a Git repo. This is where &lt;a class="reference external" href="https://git-scm.com/book/en/v2/Git-Tools-Submodules"&gt;git
submodules&lt;/a&gt; come in handy: oftentimes people maintain configurations
as standalone repositories, e …&lt;/p&gt;</summary><content type="html">&lt;p&gt;&lt;span class="caps"&gt;GNU&lt;/span&gt; &lt;a class="reference external" href="https://www.gnu.org/software/stow/manual/stow.html"&gt;Stow&lt;/a&gt; is a tool to manage symlinks from a single directory. That
makes the tool a perfect candidte to keep configuration files (or
dotfiles) in a &lt;span class="caps"&gt;VCS&lt;/span&gt; repository, such as a Git repo. This is where &lt;a class="reference external" href="https://git-scm.com/book/en/v2/Git-Tools-Submodules"&gt;git
submodules&lt;/a&gt; come in handy: oftentimes people maintain configurations
as standalone repositories, e.g. configuration for &lt;a class="reference external" href="https://github.com/pilosus/dot-emacs/"&gt;Emacs&lt;/a&gt;. They can
be plugged into a stow directory as a&amp;nbsp;submodule.&lt;/p&gt;
&lt;div class="section" id="example-stow-directory"&gt;
&lt;h2&gt;Example stow&amp;nbsp;directory&lt;/h2&gt;
&lt;p&gt;Let&amp;#8217;s create a stow directory and initialise a git&amp;nbsp;repository:&lt;/p&gt;
&lt;pre class="literal-block"&gt;
$ mkdir stow-example
$ cd stow-example/
$ git init
Initialized empty Git repository in /home/pilosus/git/stow-example/.git/
&lt;/pre&gt;
&lt;p&gt;Now, let&amp;#8217;s create a stow package for Emacs - a directory in a stow
directory containing all the necessary files and directories to be
installed into a target directory (which is &lt;tt class="docutils literal"&gt;$&lt;span class="caps"&gt;HOME&lt;/span&gt;&lt;/tt&gt; or &lt;tt class="docutils literal"&gt;~/&lt;/tt&gt;). The
package resides under &lt;tt class="docutils literal"&gt;emacs&lt;/tt&gt; directory and will contain a
&lt;tt class="docutils literal"&gt;.emacs.d&lt;/tt&gt; directory as a git&amp;nbsp;submodule:&lt;/p&gt;
&lt;pre class="literal-block"&gt;
$ mkdir emacs
$ cd emacs/
$ git submodule add git&amp;#64;github.com:pilosus/dot-emacs.git .emacs.d
Cloning into '/home/pilosus/git/stow-example/emacs/.emacs.d'...
&lt;/pre&gt;
&lt;p&gt;Let&amp;#8217;s add one more stow package as a git submodule, a &lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;~/.clojure&lt;/span&gt;&lt;/tt&gt;
directory that contains tools and basic aliases for Clojure&amp;nbsp;language:&lt;/p&gt;
&lt;pre class="literal-block"&gt;
$ cd ..
$ mkdir clojure
$ cd clojure/
$ git submodule add git&amp;#64;github.com:pilosus/dot-clojure.git .clojure
Cloning into '/home/pilosus/git/stow-example/clojure/.clojure'...
&lt;/pre&gt;
&lt;p&gt;So now we have two stow packages, &lt;cite&gt;emacs&lt;/cite&gt; and &lt;cite&gt;clojure&lt;/cite&gt; installed as git&amp;nbsp;submodules:&lt;/p&gt;
&lt;pre class="literal-block"&gt;
$ tree -R -a -L 2 -I '.git'
.
├── clojure
│&amp;nbsp;&amp;nbsp; └── .clojure
├── emacs
│&amp;nbsp;&amp;nbsp; └── .emacs.d
└── .gitmodules
&lt;/pre&gt;
&lt;p&gt;Installing packages with Stow is as easy as&amp;nbsp;this:&lt;/p&gt;
&lt;pre class="literal-block"&gt;
$ stow --target=$HOME emacs
$ stow --target=$HOME clojure
&lt;/pre&gt;
&lt;p&gt;For bulk install &lt;tt class="docutils literal"&gt;*/&lt;/tt&gt; can be&amp;nbsp;used:&lt;/p&gt;
&lt;pre class="literal-block"&gt;
$ stow --target=$HOME --stow */
&lt;/pre&gt;
&lt;p&gt;Stowing a package will create symlinks in the target directory
(&lt;tt class="docutils literal"&gt;$&lt;span class="caps"&gt;HOME&lt;/span&gt;&lt;/tt&gt; in this case) that reflect the hierarchy of files and
directories in the package. E.g. for &lt;tt class="docutils literal"&gt;clojure&lt;/tt&gt; package the hierarcy
is the&amp;nbsp;following:&lt;/p&gt;
&lt;pre class="literal-block"&gt;
$ cd clojure
$ tree -R -a
.
└── .clojure
    ├── deps.edn
    ├── .git
    ├── .gitignore
    ├── LICENSE
    ├── README.md
    └── tools
        ├── antq.edn
        ├── clj-watson.edn
        ├── new.edn
        └── tools.edn
&lt;/pre&gt;
&lt;p&gt;It&amp;#8217;s symlinked to a home directory that we used as a target directory when&amp;nbsp;stowing:&lt;/p&gt;
&lt;pre class="literal-block"&gt;
$ ls -lth ~/.clojure
lrwxrwxrwx. 1 pilosus pilosus 29 Sep  5 15:51 /home/pilosus/.clojure -&amp;gt; git/stow-example/clojure/.clojure
&lt;/pre&gt;
&lt;p&gt;Using git modules allows to support a standalone repository for
configuration files so that they can be kept separately from the stow
repo itself. This is a good solution given that a stow repo can
contain sensitive configuration files such as inventory for Ansible or
other authentication&amp;nbsp;information.&lt;/p&gt;
&lt;p&gt;Git submodules also allow to do all the normal git things such as
checking out a certain commit or tag, pushing changes upstream,
etc. That makes the package management easy and&amp;nbsp;flexible.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="my-personal-dotfiles"&gt;
&lt;h2&gt;My personal&amp;nbsp;dotfiles&lt;/h2&gt;
&lt;p&gt;Here&amp;#8217;s &lt;a class="reference external" href="https://github.com/pilosus/dotfiles"&gt;dotfiles&lt;/a&gt;, a repo with my personal config files organised
with &lt;span class="caps"&gt;GNU&lt;/span&gt;/Stow.&lt;/p&gt;
&lt;/div&gt;
</content><category term="blog"></category><category term="dotfiles"></category><category term="shell"></category></entry><entry><title>Clojure: decision fatigue</title><link href="https://blog.pilosus.org/posts/2023/07/16/clojure-decision-fatigue/" rel="alternate"></link><published>2023-07-16T17:00:00+02:00</published><updated>2023-07-16T17:00:00+02:00</updated><author><name>Vitaly Samigullin</name></author><id>tag:blog.pilosus.org,2023-07-16:/posts/2023/07/16/clojure-decision-fatigue/</id><summary type="html">&lt;p&gt;As someone who uses Clojure mostly for solo projects I often find
myself in a situation when a decision on what libraries to use is
hard. Clojure and its community seems to follow &amp;#8220;There Is More Than
One Way To Do It&amp;#8221; path. Possibly often unintentiallally, because the
core team …&lt;/p&gt;</summary><content type="html">&lt;p&gt;As someone who uses Clojure mostly for solo projects I often find
myself in a situation when a decision on what libraries to use is
hard. Clojure and its community seems to follow &amp;#8220;There Is More Than
One Way To Do It&amp;#8221; path. Possibly often unintentiallally, because the
core team is too small to ship some features before the community
comes up with the missing parts. According to &lt;a class="reference external" href="https://www.surveymonkey.com/stories/SM-_2BH3b49f_2FXEkUlrb_2BJSThxg_3D_3D/"&gt;State of Clojure 2023&lt;/a&gt;
survey, Clojure is mostly used in smaller teams (of, presumably,
experienced developers), which is probably the reason, why Clojure
ecosystem also lacks &lt;a class="reference external" href="https://pkg.go.dev/cmd/gofmt"&gt;opinionated tools&lt;/a&gt; that may save you some
mental energy. All in all, these result in a decision fatigue when
deciding on third-party libraries and&amp;nbsp;tools.&lt;/p&gt;
&lt;div class="section" id="possible-decisions"&gt;
&lt;h2&gt;Possible&amp;nbsp;decisions&lt;/h2&gt;
&lt;div class="section" id="enriched-version-of-clojure-toolbox-com"&gt;
&lt;h3&gt;Enriched version of&amp;nbsp;clojure-toolbox.com&lt;/h3&gt;
&lt;p&gt;When the quality of the popular search engines seems to be
&lt;a class="reference external" href="https://hn.algolia.com/?dateRange=all&amp;amp;page=0&amp;amp;prefix=false&amp;amp;query=google%20search%20quality&amp;amp;sort=byPopularity&amp;amp;type=story"&gt;declining&lt;/a&gt;, a hand-picked collection may be a &lt;a class="reference external" href="https://wiki.archlinux.org/title/list_of_applications"&gt;good solution&lt;/a&gt;. For
Clojure, &lt;a class="reference external" href="https://www.clojure-toolbox.com/"&gt;clojure-toolbox.com&lt;/a&gt; is a high quality categorised
directory of libraries and tools for the language. And it seems to be
well &lt;a class="reference external" href="https://github.com/weavejester/clojure-toolbox.com/pulls?q=is%3Apr+is%3Amerged+"&gt;maintained&lt;/a&gt;. It has a few drawbacks&amp;nbsp;though:&lt;/p&gt;
&lt;ol class="arabic simple"&gt;
&lt;li&gt;Projects are not&amp;nbsp;rated&lt;/li&gt;
&lt;li&gt;No hints on projects maintenance&amp;nbsp;status&lt;/li&gt;
&lt;li&gt;Projects description is hidden in&amp;nbsp;tooltips&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;These are hard things. Not only it&amp;#8217;s hard to compare dependencies that
differ in their feature sets even under the same category. But also
there could be no single source of statistics about number of
downloads (Maven Central, Clojars, GitHub), the last release date,
etc. Let alone the popularity isn&amp;#8217;t necessary a sign of quality and
the recent last release timestamp isn&amp;#8217;t necessary a sign of good
maintenance in a community with the strong commitment to backwards
compatibility. Yet something simple would still be a huge relief to
decision&amp;nbsp;fatigue:&lt;/p&gt;
&lt;ol class="arabic simple"&gt;
&lt;li&gt;Number of stars on GitHub is a project uses it for&amp;nbsp;hosting&lt;/li&gt;
&lt;li&gt;The timestamp of the last commit on&amp;nbsp;GitHub&lt;/li&gt;
&lt;li&gt;Showing project&amp;#8217;s description&amp;nbsp;openly&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;It turned out, that 970 out of 1001 projects at the moment of writing
had GitHub repo &lt;span class="caps"&gt;URL&lt;/span&gt; as a project link. That makes the approach
mentioned above good&amp;nbsp;enough!&lt;/p&gt;
&lt;p&gt;Here&amp;#8217;s how I enrich clojure-toolbox.com&amp;#8217;s data with the GitHub&amp;nbsp;stats:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;Pull down clojure-toolbox.com &lt;span class="caps"&gt;DB&lt;/span&gt; (a &lt;span class="caps"&gt;YAML&lt;/span&gt; file stored in GitHub&amp;nbsp;repo)&lt;/li&gt;
&lt;li&gt;If a project has a GitHub link, get information about number of
stargazers and the last commit push timestamp via GitHub &lt;span class="caps"&gt;API&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;Use &lt;cite&gt;clojure.core.async&lt;/cite&gt; and GitHub access token to run operations
in&amp;nbsp;bulk&lt;/li&gt;
&lt;li&gt;Format results in&amp;nbsp;Markdown&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Here&amp;#8217;s an &lt;a class="reference external" href="https://github.com/pilosus/github.api.async/blob/main/src/org/pilosus/github/api/async.clj#L240"&gt;example code&lt;/a&gt;. And this is how the &lt;a class="reference external" href="https://gist.github.com/pilosus/8d1ed78c2851e6f82b54357ac4b5e220#file-clojure-toolbox-md"&gt;enriched catalogue&lt;/a&gt;
looks like. Much more&amp;nbsp;informative!&lt;/p&gt;
&lt;p&gt;The same could possibly be done for &lt;a class="reference external" href="https://babashka.org/toolbox/"&gt;The Babashka toolbox&lt;/a&gt; as it&amp;#8217;s
&lt;a class="reference external" href="https://github.com/babashka/toolbox"&gt;derived&lt;/a&gt; from the &lt;a class="reference external" href="https://github.com/weavejester/clojure-toolbox.com"&gt;The Clojure Toolbox&lt;/a&gt; source&amp;nbsp;code.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="clojurians-slack"&gt;
&lt;h3&gt;Clojurians&amp;nbsp;Slack&lt;/h3&gt;
&lt;p&gt;&lt;a class="reference external" href="http://clojurians.net"&gt;Clojurians&lt;/a&gt; Slack is another great source of knowledge. Not only you
can ask questions to some experienced Clojure programmers, but also
search the Slack&amp;#8217;s&amp;nbsp;history.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
</content><category term="Blog"></category><category term="clojure"></category></entry><entry><title>Debugging and patching Clojure code running in production using socket server and REPL</title><link href="https://blog.pilosus.org/posts/2023/07/07/debugging-and-patching-clojure-code-running-in-production-using-socket-server-and-repl/" rel="alternate"></link><published>2023-07-07T20:00:00+02:00</published><updated>2023-07-07T20:00:00+02:00</updated><author><name>Vitaly Samigullin</name></author><id>tag:blog.pilosus.org,2023-07-07:/posts/2023/07/07/debugging-and-patching-clojure-code-running-in-production-using-socket-server-and-repl/</id><summary type="html">&lt;p&gt;Common Lisp is well known for its interactivity. The story of &lt;span class="caps"&gt;NASA&lt;/span&gt;
engineers who used remote &lt;span class="caps"&gt;REPL&lt;/span&gt; to patch Lisp code running 60 million
miles away inside the interplanetary spacecraft is &lt;a class="reference external" href="https://youtu.be/_gZK0tW8EhQ"&gt;well known&lt;/a&gt;
too. You can debug and patch Clojure code running in production using
&lt;span class="caps"&gt;REPL&lt;/span&gt; too. This is where …&lt;/p&gt;</summary><content type="html">&lt;p&gt;Common Lisp is well known for its interactivity. The story of &lt;span class="caps"&gt;NASA&lt;/span&gt;
engineers who used remote &lt;span class="caps"&gt;REPL&lt;/span&gt; to patch Lisp code running 60 million
miles away inside the interplanetary spacecraft is &lt;a class="reference external" href="https://youtu.be/_gZK0tW8EhQ"&gt;well known&lt;/a&gt;
too. You can debug and patch Clojure code running in production using
&lt;span class="caps"&gt;REPL&lt;/span&gt; too. This is where Clojure &lt;a class="reference external" href="https://clojure.org/reference/repl_and_main#_launching_a_socket_server"&gt;socket server&lt;/a&gt; comes in&amp;nbsp;handy.&lt;/p&gt;
&lt;div class="section" id="practicle-example"&gt;
&lt;h2&gt;Practicle&amp;nbsp;example&lt;/h2&gt;
&lt;p&gt;Let&amp;#8217;s interact with the live web service written in Clojure and try to
change the running program&amp;#8217;s state using &lt;span class="caps"&gt;REPL&lt;/span&gt; and Clojure&amp;#8217;s socket
server. I assume you have Java 17 or later (earlier versions might
work but not tested), &lt;a class="reference external" href="https://clojure.org/guides/install_clojure"&gt;Clojure 1.11 or later&lt;/a&gt;, &lt;tt class="docutils literal"&gt;git&lt;/tt&gt; and &lt;tt class="docutils literal"&gt;curl&lt;/tt&gt;
installed on you computer to work through this&amp;nbsp;tutorial.&lt;/p&gt;
&lt;div class="section" id="get-sample-code"&gt;
&lt;h3&gt;Get sample&amp;nbsp;code&lt;/h3&gt;
&lt;p&gt;Get &lt;a class="reference external" href="https://github.com/pilosus/dienstplan"&gt;dienstplan&lt;/a&gt; app (it&amp;#8217;s&amp;nbsp;opensourced):&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;$&lt;span class="w"&gt; &lt;/span&gt;git&lt;span class="w"&gt; &lt;/span&gt;clone&lt;span class="w"&gt; &lt;/span&gt;git@github.com:pilosus/dienstplan.git
$&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;cd&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;dienstplan
$&lt;span class="w"&gt; &lt;/span&gt;git&lt;span class="w"&gt; &lt;/span&gt;checkout&lt;span class="w"&gt; &lt;/span&gt;ba1aa1f846bff8f690e14a6fbb8989ae73a10b53
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;div class="section" id="compile-the-code"&gt;
&lt;h3&gt;Compile the&amp;nbsp;code&lt;/h3&gt;
&lt;p&gt;Compile an &lt;tt class="docutils literal"&gt;uberjar&lt;/tt&gt;. It produces &lt;tt class="docutils literal"&gt;app.jar&lt;/tt&gt; in your working&amp;nbsp;directory:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;$&lt;span class="w"&gt; &lt;/span&gt;clojure&lt;span class="w"&gt; &lt;/span&gt;-T:build&lt;span class="w"&gt; &lt;/span&gt;uberjar&lt;span class="w"&gt; &lt;/span&gt;:uber-file&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;&amp;quot;app.jar&amp;quot;&amp;#39;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;div class="section" id="run-the-app-with-socket-server"&gt;
&lt;h3&gt;Run the app with socket&amp;nbsp;server&lt;/h3&gt;
&lt;p&gt;Let&amp;#8217;s run the app with a Java option string
&lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;-Dclojure.server.repl=&amp;quot;{:port&lt;/span&gt; 5555 :accept clojure.core.server/repl}&amp;quot;&lt;/tt&gt;
needed to start a socket server with &lt;span class="caps"&gt;REPL&lt;/span&gt;
on localhost with the port 5555 open. Environment variables are needed
to start the&amp;nbsp;app:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;$&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;APP__DEBUG&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;false&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nv"&gt;SLACK__TOKEN&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;xoxb-Your-Bot-User-OAuth-Token&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nv"&gt;SLACK__SIGN&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Your-Signing-Secret&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nv"&gt;ALERTS__SENTRY_DSN&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;https://public:private@localhost/1&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nv"&gt;SERVER__PORT&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;8080&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nv"&gt;SERVER__LOGLEVEL&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;INFO&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nv"&gt;DB__SERVER_NAME&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;localhost&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nv"&gt;DB__PORT_NUMBER&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;15432&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nv"&gt;DB__DATABASE_NAME&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;dienstplan&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nv"&gt;DB__USERNAME&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;dienstplan&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nv"&gt;DB__PASSWORD&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;dienstplan&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;
java&lt;span class="w"&gt; &lt;/span&gt;-Dclojure.server.repl&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;{:port 5555 :accept clojure.core.server/repl}&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;-jar&lt;span class="w"&gt; &lt;/span&gt;app.jar
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;div class="section" id="check-if-app-is-running-properly"&gt;
&lt;h3&gt;Check if app is running&amp;nbsp;properly&lt;/h3&gt;
&lt;p&gt;Let&amp;#8217;s check if the app is working properly by making a &lt;span class="caps"&gt;HTTP&lt;/span&gt; &lt;span class="caps"&gt;GET&lt;/span&gt;
request to its healthcheck &lt;span class="caps"&gt;API&lt;/span&gt;&amp;nbsp;endpoint:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;$&lt;span class="w"&gt; &lt;/span&gt;curl&lt;span class="w"&gt; &lt;/span&gt;http://localhost:8080/api/healthcheck
&lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;status&amp;quot;&lt;/span&gt;:&lt;span class="s2"&gt;&amp;quot;ok&amp;quot;&lt;/span&gt;&lt;span class="o"&gt;}&lt;/span&gt;

$&lt;span class="w"&gt; &lt;/span&gt;curl&lt;span class="w"&gt; &lt;/span&gt;-SsL&lt;span class="w"&gt; &lt;/span&gt;-I&lt;span class="w"&gt; &lt;/span&gt;http://localhost:8080/api/healthcheck&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;head&lt;span class="w"&gt; &lt;/span&gt;-n&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;
HTTP/1.1&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;200&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;OK
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;div class="section" id="connect-to-repl"&gt;
&lt;h3&gt;Connect to &lt;span class="caps"&gt;REPL&lt;/span&gt;&lt;/h3&gt;
&lt;p&gt;Now, let&amp;#8217;s connect to the &lt;span class="caps"&gt;REPL&lt;/span&gt; socket server running alongside the
app. Remember, it&amp;#8217;s a pure &lt;span class="caps"&gt;REPL&lt;/span&gt; accessible via &lt;span class="caps"&gt;UDP&lt;/span&gt; connection, so your
emacs &lt;span class="caps"&gt;CIDER&lt;/span&gt;&amp;#8217;s &lt;a class="reference external" href="https://nrepl.org/nrepl/1.0/alternatives.html#comparison"&gt;nrepl&lt;/a&gt;, implementing more complex tooling protocol and
working over &lt;span class="caps"&gt;TCP&lt;/span&gt;, won&amp;#8217;t be able to connect to it! We can use simpler
things like &lt;tt class="docutils literal"&gt;telnet&lt;/tt&gt; or &lt;tt class="docutils literal"&gt;netcat&lt;/tt&gt; (or &lt;tt class="docutils literal"&gt;nc&lt;/tt&gt;) instead. For better
line editing experience, like moving the cursor back and forth, using
backspace, and history support, you may use &lt;tt class="docutils literal"&gt;readline&lt;/tt&gt; library with
&lt;tt class="docutils literal"&gt;rlwrap&lt;/tt&gt; (readline wrap) &lt;span class="caps"&gt;CLI&lt;/span&gt;&amp;nbsp;tool:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;$&lt;span class="w"&gt; &lt;/span&gt;rlwrap&lt;span class="w"&gt; &lt;/span&gt;nc&lt;span class="w"&gt; &lt;/span&gt;localhost&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;5555&lt;/span&gt;
&lt;span class="nv"&gt;user&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&amp;gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;div class="section" id="access-state-of-the-running-app"&gt;
&lt;h3&gt;Access state of the running&amp;nbsp;app&lt;/h3&gt;
&lt;p&gt;Now let&amp;#8217;s tinker with our running app&amp;#8217;s state from within the &lt;span class="caps"&gt;REPL&lt;/span&gt;!
First, let&amp;#8217;s take a look at the &lt;a class="reference external" href="https://github.com/pilosus/dienstplan/blob/1.0.0/src/dienstplan/config.clj#L39"&gt;config&lt;/a&gt; initialized with the
envorinment variables a few steps above. To do it we need to load
app&amp;#8217;s namespace &lt;tt class="docutils literal"&gt;dienstplan.config&lt;/tt&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="nv"&gt;user=&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;dienstplan.config&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;:as&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;config&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;span class="nv"&gt;nil&lt;/span&gt;

&lt;span class="nv"&gt;user=&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;config/config&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="ss"&gt;:application&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="ss"&gt;:name&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;dienstplan&amp;quot;&lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;:version&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;latest&amp;quot;&lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;:env&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;production&amp;quot;&lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;:debug&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;false&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;:server&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="ss"&gt;:port&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;8080&lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;:loglevel&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;INFO&amp;quot;&lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;:access-log&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;true&lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;:block-thread&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;true&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;:slack&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="ss"&gt;:token&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;xoxb-Your-Bot-User-OAuth-Token&amp;quot;&lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;:sign&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;Your-Signing-Secret&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;:alerts&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="ss"&gt;:sentry&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;https://public:private@localhost/1&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;:db&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="ss"&gt;:dbtype&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;postgres&amp;quot;&lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;:password&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;dienstplan&amp;quot;&lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;:minimumIdle&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;20&lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;:username&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;dienstplan&amp;quot;&lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;:maxLifetime&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1800000&lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;:port&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;15432&lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;:dbname&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;dienstplan&amp;quot;&lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;:connectionTimeout&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;10000&lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;:host&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;localhost&amp;quot;&lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;:keepaliveTime&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;:maximumPoolSize&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;}}&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;div class="section" id="patch-the-code-of-the-runnning-app"&gt;
&lt;h3&gt;Patch the code of the runnning&amp;nbsp;app&lt;/h3&gt;
&lt;p&gt;And now for something completely different! Let&amp;#8217;s override some code
in the running app! Let&amp;#8217;s update the &lt;span class="caps"&gt;API&lt;/span&gt; handler &lt;a class="reference external" href="https://github.com/pilosus/dienstplan/blob/1.0.0/src/dienstplan/endpoints.clj#L34"&gt;/api/healthcheck&lt;/a&gt;
that requested above with&amp;nbsp;this:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;defmethod &lt;/span&gt;&lt;span class="nv"&gt;multi-handler&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;:healthcheck&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;_&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;log/warn&lt;/span&gt;
&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;If I were to suggest that between the Earth and Mars there is a&lt;/span&gt;
&lt;span class="s"&gt;   china teapot revolving about the sun in an elliptical orbit...&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="ss"&gt;:status&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;418&lt;/span&gt;
&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="ss"&gt;:body&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="ss"&gt;:status&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;I&amp;#39;m a teapot&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;}})&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;To do it, let&amp;#8217;s use &lt;a class="reference external" href="https://clojure.org/guides/repl/navigating_namespaces#_switching_to_an_existing_namespace_with_in_ns"&gt;in-ns&lt;/a&gt; function to switch the current &lt;span class="caps"&gt;REPL&lt;/span&gt;&amp;#8217;s
namespace to &lt;tt class="docutils literal"&gt;dienstplan.endpoints&lt;/tt&gt; where we want to override the&amp;nbsp;method:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="nv"&gt;user=&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;in-ns &lt;/span&gt;&lt;span class="ss"&gt;&amp;#39;dienstplan.endpoints&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="o"&gt;#&lt;/span&gt;&lt;span class="nv"&gt;object&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;clojure.lang.Namespace&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="nv"&gt;x50c83f27&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;dienstplan.endpoints&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

&lt;span class="nv"&gt;dienstplan.endpoints=&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;defmethod &lt;/span&gt;&lt;span class="nv"&gt;multi-handler&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;:healthcheck&lt;/span&gt;
&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;_&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;log/warn&lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;If I were to suggest that between the Earth and Mars there is a&lt;/span&gt;
&lt;span class="s"&gt;china teapot revolving about the sun in an elliptical orbit...&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="ss"&gt;:status&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;418&lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;:body&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="ss"&gt;:status&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;I&amp;#39;m a teapot&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;}})&lt;/span&gt;
&lt;span class="o"&gt;#&lt;/span&gt;&lt;span class="nv"&gt;object&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;clojure.lang.MultiFn&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="nv"&gt;x5d8ea5bb&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;clojure.lang.MultiFn@5d8ea5bb&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;div class="section" id="check-if-changes-applied"&gt;
&lt;h3&gt;Check if changes&amp;nbsp;applied&lt;/h3&gt;
&lt;p&gt;Let&amp;#8217;s make another &lt;span class="caps"&gt;HTTP&lt;/span&gt; &lt;span class="caps"&gt;GET&lt;/span&gt; request to the &lt;tt class="docutils literal"&gt;/api/healthcheck&lt;/tt&gt;
handler:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;$&lt;span class="w"&gt; &lt;/span&gt;curl&lt;span class="w"&gt; &lt;/span&gt;http://localhost:8080/api/healthcheck
&lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;status&amp;quot;&lt;/span&gt;:&lt;span class="s2"&gt;&amp;quot;I&amp;#39;m a teapot&amp;quot;&lt;/span&gt;&lt;span class="o"&gt;}&lt;/span&gt;

$&lt;span class="w"&gt; &lt;/span&gt;curl&lt;span class="w"&gt; &lt;/span&gt;-SsL&lt;span class="w"&gt; &lt;/span&gt;-I&lt;span class="w"&gt; &lt;/span&gt;http://localhost:8080/api/healthcheck&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;head&lt;span class="w"&gt; &lt;/span&gt;-n&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;
HTTP/1.1&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;418&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;I&lt;span class="err"&gt;&amp;#39;&lt;/span&gt;m&lt;span class="w"&gt; &lt;/span&gt;a&lt;span class="w"&gt; &lt;/span&gt;Teapot
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Response body has changed, so did the &lt;span class="caps"&gt;HTTP&lt;/span&gt; status code. In the app
logs we also&amp;nbsp;see:&lt;/p&gt;
&lt;pre class="literal-block"&gt;
2023-07-07 18:52:00,665 [qtp1116648405-23] WARN  dienstplan.endpoints - If I were to suggest that between the Earth and Mars there is a
  china teapot revolving about the sun in an elliptical orbit...
&lt;/pre&gt;
&lt;p&gt;Method has been sucessfully&amp;nbsp;overriden!&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="revert-changes-by-restarting-the-process"&gt;
&lt;h3&gt;Revert changes by restarting the&amp;nbsp;process&lt;/h3&gt;
&lt;p&gt;Tinkering with the running code doesn&amp;#8217;t change the code itself
though. Restart the app process and fire another &lt;span class="caps"&gt;HTTP&lt;/span&gt; request to make
sure all is back to&amp;nbsp;normal:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;$&lt;span class="w"&gt; &lt;/span&gt;curl&lt;span class="w"&gt; &lt;/span&gt;http://localhost:8080/api/healthcheck
&lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;status&amp;quot;&lt;/span&gt;:&lt;span class="s2"&gt;&amp;quot;ok&amp;quot;&lt;/span&gt;&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;div class="section" id="conclusion"&gt;
&lt;h3&gt;Conclusion&lt;/h3&gt;
&lt;p&gt;Socket server with &lt;span class="caps"&gt;REPL&lt;/span&gt; running along with the main app is a nice way
to use various &lt;a class="reference external" href="https://clojure.org/guides/repl/enhancing_your_repl_workflow#debugging-tools-and-techniques"&gt;debugging tools and techniques&lt;/a&gt; the Clojure and
third-parties provide. Code override won&amp;#8217;t work if you use
&lt;a class="reference external" href="https://clojure.org/reference/compilation"&gt;ahead-of-time&lt;/a&gt; compilation (doube-check your &lt;a class="reference external" href="https://leiningen.org/tutorial.html#uberjar"&gt;project settings&lt;/a&gt;,
especially if you build &lt;tt class="docutils literal"&gt;uberjar&lt;/tt&gt; with &lt;tt class="docutils literal"&gt;lein&lt;/tt&gt;). But for on-the-fly
compilation to &lt;span class="caps"&gt;JVM&lt;/span&gt; bytecode you can debug the running code without
touching the code itself, right in the &lt;span class="caps"&gt;REPL&lt;/span&gt;, and revert the changes by
simply restarting the app&amp;#8217;s&amp;nbsp;process.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="precautions"&gt;
&lt;h3&gt;Precautions&lt;/h3&gt;
&lt;p&gt;Running a socker server allows an intruder to connect to it over the
network if an exposed port isn&amp;#8217;t protected with a firewall, &lt;span class="caps"&gt;VPN&lt;/span&gt; and/or
other access control tool. It may impose huge security risks on your
app in production&amp;nbsp;environment.&lt;/p&gt;
&lt;p&gt;Patching code directly in the running app maybe the only available
solution when your production environment is &lt;a class="reference external" href="https://corecursive.com/lisp-in-space-with-ron-garret/"&gt;millions miles away&lt;/a&gt;
and costs $150M. While patches themselves are easily reverted by
restarting the process, your app&amp;#8217;s state may be damaged&amp;nbsp;irreversably.&lt;/p&gt;
&lt;p&gt;Touching code in production is a great power, but also is a huge
responsibility. Oftentimes risks of the live patching are higher than
longer time needed to prepare and deploy the hotfix. You should know
what you are&amp;nbsp;doing.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
</content><category term="Blog"></category><category term="clojure"></category><category term="repl"></category></entry><entry><title>kairos: crontab parsing library for Clojure</title><link href="https://blog.pilosus.org/posts/2023/07/07/kairos-crontab-parsing-library-for-clojure/" rel="alternate"></link><published>2023-07-07T12:00:00+02:00</published><updated>2023-07-07T12:00:00+02:00</updated><author><name>Vitaly Samigullin</name></author><id>tag:blog.pilosus.org,2023-07-07:/posts/2023/07/07/kairos-crontab-parsing-library-for-clojure/</id><summary type="html">&lt;p&gt;I have been tinkering with &lt;a class="reference external" href="https://blog.pilosus.org/tag/clojure/"&gt;Clojure&lt;/a&gt; since the late 2020. When I
publish an open-source project in Clojure, it&amp;#8217;s mostly about two
goals: solving a real-world problem and learning some new concepts or
tooling in Clojure ecosystem. &lt;a class="reference external" href="https://github.com/pilosus/kairos"&gt;kairos&lt;/a&gt; is a Clojure library for
parsing crontab is no exception of …&lt;/p&gt;</summary><content type="html">&lt;p&gt;I have been tinkering with &lt;a class="reference external" href="https://blog.pilosus.org/tag/clojure/"&gt;Clojure&lt;/a&gt; since the late 2020. When I
publish an open-source project in Clojure, it&amp;#8217;s mostly about two
goals: solving a real-world problem and learning some new concepts or
tooling in Clojure ecosystem. &lt;a class="reference external" href="https://github.com/pilosus/kairos"&gt;kairos&lt;/a&gt; is a Clojure library for
parsing crontab is no exception of this&amp;nbsp;rule.&lt;/p&gt;
&lt;p&gt;&lt;cite&gt;kairos&lt;/cite&gt; parses &lt;a class="reference external" href="https://man7.org/linux/man-pages/man5/crontab.5.html"&gt;vixie&lt;/a&gt; crontab format and produces a lazy sequence
of &lt;cite&gt;java.time.ZonedDateTime&lt;/cite&gt; objects for &lt;cite&gt;&lt;span class="caps"&gt;UTC&lt;/span&gt;&lt;/cite&gt; timezone that are in
the&amp;nbsp;future.&lt;/p&gt;
&lt;div class="section" id="java-interop"&gt;
&lt;h2&gt;Java&amp;nbsp;interop&lt;/h2&gt;
&lt;p&gt;&lt;a class="reference external" href="https://github.com/pilosus/kairos"&gt;kairos&lt;/a&gt; doesn&amp;#8217;t use any Clojure wrapper libraries to work with
&lt;cite&gt;java.time&lt;/cite&gt; classes, like &lt;a class="reference external" href="https://github.com/henryw374/cljc.java-time"&gt;cljc.java-time&lt;/a&gt; or
&lt;a class="reference external" href="https://github.com/dm3/clojure.java-time"&gt;clojure.java-time&lt;/a&gt;. Instead it uses Java interop directly, so that
no dependencies are needed for the&amp;nbsp;library.&lt;/p&gt;
&lt;p&gt;The main lesson learned here: using &lt;a class="reference external" href="https://clojure.org/reference/java_interop#typehints"&gt;type hints&lt;/a&gt; and enabling
warnings on reflection can boost code performance to 6-8&amp;nbsp;times!&lt;/p&gt;
&lt;p&gt;Plain &lt;cite&gt;(set! *warn-on-reflection* true)&lt;/cite&gt; works well for reflection
detection. But &lt;a class="reference external" href="https://github.com/jonase/eastwood"&gt;eastwood&lt;/a&gt; linter can do the job too, in addition to
many other&amp;nbsp;things.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="clojure-cli-and-deps-edn"&gt;
&lt;h2&gt;Clojure &lt;span class="caps"&gt;CLI&lt;/span&gt; and&amp;nbsp;deps.edn&lt;/h2&gt;
&lt;p&gt;&lt;a class="reference external" href="https://github.com/pilosus/kairos"&gt;kairos&lt;/a&gt; is my first project with &lt;cite&gt;deps.edn&lt;/cite&gt; and Clojure &lt;span class="caps"&gt;CLI&lt;/span&gt; used
from the day 1. In my previous projects I used &lt;a class="reference external" href="https://leiningen.org/"&gt;lein&lt;/a&gt;. Leiningen is a
fantastic integrated tool for all important project-related tasks from
running tests to building an &lt;cite&gt;uberjar&lt;/cite&gt;. I still think that &lt;cite&gt;lein&lt;/cite&gt; is
the best in class for Clojure beginners, who want to focus on Clojure
code and not on the tooling. On the other hand, Clojure &lt;span class="caps"&gt;CLI&lt;/span&gt; allows you
to be much more flexible when writing tasks for &lt;span class="caps"&gt;CI&lt;/span&gt;, e.g. create custom
entrypoints in Clojure code for &lt;a class="reference external" href="https://github.com/pilosus/kairos/blob/v0.1.14/build.clj#L43"&gt;testing&lt;/a&gt; or &lt;a class="reference external" href="https://github.com/pilosus/kairos/blob/v0.1.14/build.clj#L65"&gt;deploying jars&lt;/a&gt; to
Clojars. It also seems to be a first-class citizen when it comes to
Clojure core team &lt;a class="reference external" href="https://github.com/nubank/morse/issues/5"&gt;support&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Apart from official docs, Sean Corfield&amp;#8217;s &lt;a class="reference external" href="https://github.com/seancorfield/dot-clojure"&gt;dot-clojure&lt;/a&gt; repo and
Practicalli&amp;#8217;s &lt;a class="reference external" href="https://github.com/practicalli/clojure-cli-config"&gt;clojure-cli-config&lt;/a&gt; are both great sources of&amp;nbsp;inspiration.&lt;/p&gt;
&lt;/div&gt;
</content><category term="Blog"></category><category term="clojure"></category></entry><entry><title>dienstplan: Slack bot for duty rotations</title><link href="https://blog.pilosus.org/posts/2022/01/16/dienstplan/" rel="alternate"></link><published>2022-01-16T00:59:00+01:00</published><updated>2022-01-16T00:59:00+01:00</updated><author><name>Vitaly Samigullin</name></author><id>tag:blog.pilosus.org,2022-01-16:/posts/2022/01/16/dienstplan/</id><summary type="html">&lt;p class="first last"&gt;A simple bot app for on-call duty rotations in your team&amp;#8217;s Slack&amp;nbsp;channels&lt;/p&gt;
</summary><content type="html">&lt;p&gt;&lt;a class="reference external" href="https://github.com/pilosus/dienstplan"&gt;dienstplan&lt;/a&gt; is a Slack bot for duty rotations. It&amp;#8217;s dead simple:
just a few commands to manage on-call duty rotations in your team&amp;#8217;s
Slack channels. The bot application follows the rule &amp;#8220;Do One Thing and
Do It Well&amp;#8221;. It plays nicely with Slack &lt;a class="reference external" href="https://slack.com/resources/using-slack/how-to-use-reminders-in-slack"&gt;reminders&lt;/a&gt; and &lt;a class="reference external" href="https://slack.com/features/workflow-automation"&gt;workflows&lt;/a&gt;.&lt;/p&gt;
&lt;div class="section" id="usage-example"&gt;
&lt;h2&gt;Usage&amp;nbsp;example&lt;/h2&gt;
&lt;a class="reference external image-reference" href="https://youtu.be/pZWJYpsT1_w"&gt;&lt;img alt="Dienstplan Slack bot" class="responsive" src="https://blog.pilosus.org/images/dienstplan.gif" /&gt;&lt;/a&gt;
&lt;p&gt;Let&amp;#8217;s create a rotation using &lt;a class="reference external" href="https://github.com/pilosus/dienstplan"&gt;dienstplan&lt;/a&gt;. Just pass in a &lt;tt class="docutils literal"&gt;create&lt;/tt&gt;
command followed by a rotation name, a list of the channel users in a
rotation, and a rotation&amp;nbsp;description:&lt;/p&gt;
&lt;pre class="literal-block"&gt;
&amp;#64;dienstplan create my-rota &amp;#64;user1 &amp;#64;user2 &amp;#64;user3
On-call engineer's duties:
- Process support team questions queue
- Resolve service alerts
- Check service health metrics
- Casual code refactoring-
 Follow the boy scout rule: always leave the campground cleaner than you found it
&lt;/pre&gt;
&lt;p&gt;Once the rota is set up, the first user in the list becomes a current
on-call person. Check it with a &lt;tt class="docutils literal"&gt;who&lt;/tt&gt; command:&lt;/p&gt;
&lt;pre class="literal-block"&gt;
&amp;#64;dienstplan who my-rota
&lt;/pre&gt;
&lt;p&gt;To change the current on-call person to the next one use &lt;tt class="docutils literal"&gt;rotate&lt;/tt&gt;
command:&lt;/p&gt;
&lt;pre class="literal-block"&gt;
&amp;#64;dienstplan rotate my-rota
&lt;/pre&gt;
&lt;p&gt;The bot iterates over the users in the list&amp;nbsp;order:&lt;/p&gt;
&lt;pre class="literal-block"&gt;
&amp;#64;user1 -&amp;gt; &amp;#64;user2 -&amp;gt;  &amp;#64;user3 -&amp;gt; &amp;#64;user1 ...
&lt;/pre&gt;
&lt;p&gt;Now that you know the basics, let&amp;#8217;s automate rotation and current duty
notifications with Slack&amp;#8217;s built-in &lt;tt class="docutils literal"&gt;/remind&lt;/tt&gt; command. First, set up a
reminder to rotate users&amp;nbsp;weekly:&lt;/p&gt;
&lt;pre class="literal-block"&gt;
/remind #my-channel to &amp;quot;&amp;#64;dienstplan rotate my-rota&amp;quot; every Monday at 9AM UTC
&lt;/pre&gt;
&lt;p&gt;Second, remind duties to a current on-call&amp;nbsp;person:&lt;/p&gt;
&lt;pre class="literal-block"&gt;
/remind #my-channel to &amp;quot;&amp;#64;dienstplan who my-rota&amp;quot; every Monday, Tuesday, Wednesday, Thursday, Friday at 10AM UTC
&lt;/pre&gt;
&lt;p&gt;Get help&amp;nbsp;with:&lt;/p&gt;
&lt;pre class="literal-block"&gt;
&amp;#64;dienstplan help
&lt;/pre&gt;
&lt;/div&gt;
&lt;div class="section" id="up-running"&gt;
&lt;h2&gt;Up &lt;span class="amp"&gt;&amp;amp;&lt;/span&gt;&amp;nbsp;Running&lt;/h2&gt;
&lt;p&gt;Grab &lt;tt class="docutils literal"&gt;dienstplan&lt;/tt&gt; from the &lt;a class="reference external" href="https://github.com/pilosus/dienstplan"&gt;GitHub&lt;/a&gt; repository along with user
documentation, installation guide and &lt;a class="reference external" href="https://github.com/pilosus/dienstplan-deploy"&gt;deployment scripts&lt;/a&gt;.&lt;/p&gt;
&lt;/div&gt;
</content><category term="Blog"></category><category term="foss"></category><category term="slack"></category><category term="clojure"></category></entry><entry><title>pip-license-checker: detect third-party dependencies with “wrong” licenses</title><link href="https://blog.pilosus.org/posts/2021/09/07/pip-license-checker/" rel="alternate"></link><published>2021-09-07T09:10:00+02:00</published><updated>2021-09-16T15:54:00+02:00</updated><author><name>Vitaly Samigullin</name></author><id>tag:blog.pilosus.org,2021-09-07:/posts/2021/09/07/pip-license-checker/</id><summary type="html">&lt;p class="first last"&gt;Detect license types for your poject&amp;#8217;s third-party&amp;nbsp;dependencies&lt;/p&gt;
</summary><content type="html">&lt;div class="contents topic" id="table-of-contents"&gt;
&lt;p class="topic-title"&gt;Table of&amp;nbsp;Contents&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;a class="reference internal" href="#intro-1" id="toc-entry-1"&gt;Intro&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="reference internal" href="#what-is-pip-license-checker-1" id="toc-entry-2"&gt;What is &lt;cite&gt;pip-license-checker&lt;/cite&gt;?&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="reference internal" href="#why-license-checks-are-needed" id="toc-entry-3"&gt;Why license checks are&amp;nbsp;needed?&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="reference internal" href="#why-automating-license-compliance-checks-is-needed" id="toc-entry-4"&gt;Why automating license compliance checks is&amp;nbsp;needed?&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="reference internal" href="#who-needs-to-check-licenses" id="toc-entry-5"&gt;Who needs to check&amp;nbsp;licenses?&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="reference internal" href="#why-pip-license-checker" id="toc-entry-6"&gt;Why &lt;cite&gt;pip-license-checker&lt;/cite&gt;?&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="reference internal" href="#what-are-license-types" id="toc-entry-7"&gt;What are license types?&lt;/a&gt;&lt;ul&gt;
&lt;li&gt;&lt;a class="reference internal" href="#copyright-or-end-user-license-agreement" id="toc-entry-8"&gt;Copyright or End-user license&amp;nbsp;agreement&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="reference internal" href="#public-domain-like-licenses-1" id="toc-entry-9"&gt;Public-domain-like&amp;nbsp;licenses&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="reference internal" href="#permissive-licenses-requiring-attribution" id="toc-entry-10"&gt;Permissive licenses requiring&amp;nbsp;attribution&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="reference internal" href="#copyleft" id="toc-entry-11"&gt;Copyleft&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="reference internal" href="#weak-copyleft-1" id="toc-entry-12"&gt;Weak&amp;nbsp;copyleft&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="reference internal" href="#strong-copyleft-1" id="toc-entry-13"&gt;Strong&amp;nbsp;copyleft&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="reference internal" href="#copyleft-over-a-network" id="toc-entry-14"&gt;Copyleft over a&amp;nbsp;network&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a class="reference internal" href="#license-categorisation-1" id="toc-entry-15"&gt;License&amp;nbsp;categorisation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="reference internal" href="#technical-facts" id="toc-entry-16"&gt;Technical&amp;nbsp;facts&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="reference internal" href="#outro-1" id="toc-entry-17"&gt;Outro&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="reference internal" href="#disclaimer-1" id="toc-entry-18"&gt;Disclaimer&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="reference internal" href="#addendum-1" id="toc-entry-19"&gt;Addendum&lt;/a&gt;&lt;ul&gt;
&lt;li&gt;&lt;a class="reference internal" href="#interview-2021-09-15" id="toc-entry-20"&gt;Interview&amp;nbsp;2021-09-15&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class="section" id="intro-1"&gt;
&lt;span id="intro"&gt;&lt;/span&gt;&lt;h2&gt;&lt;a class="toc-backref" href="#toc-entry-1"&gt;Intro&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;A software project may have hundreds of third-party dependencies. Even
when available freely over the Internet, these dependencies may be
distributed under licenses with some conditions, more or less
restrictive. There are over a hundred popular free software and
open-source licenses used nowadays. All of them may be roughly divided
into a few license types depending on their conditions for usage,
attribution, modification and distribution. Third-party dependencies
under licenses of some types are good for your project, while others
may result in license violations. License violations can pose high
risks, both financial and reputation losses. Understanding what
license types are good for your project&amp;#8217;s 3rd-party dependencies is
important. Once you know what are you looking for, check your
dependencies regularly. &lt;a class="reference external" href="https://github.com/pilosus/pip-license-checker"&gt;pip-license-checker&lt;/a&gt; is a tool for
automating license type checks that may&amp;nbsp;help.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="what-is-pip-license-checker-1"&gt;
&lt;span id="what-is-pip-license-checker"&gt;&lt;/span&gt;&lt;h2&gt;&lt;a class="toc-backref" href="#toc-entry-2"&gt;What is &lt;cite&gt;pip-license-checker&lt;/cite&gt;?&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;&lt;a class="reference external" href="https://github.com/pilosus/pip-license-checker"&gt;pip-license-checker&lt;/a&gt; is a license compliance tool. It detects
&lt;a class="reference internal" href="#license-types"&gt;license types&lt;/a&gt; for your project&amp;#8217;s third-party dependencies. The tool
covers the majority of popular free/libre and open-source (&lt;a class="reference external" href="https://en.wikipedia.org/wiki/Free_and_open-source_software"&gt;&lt;span class="caps"&gt;FLOSS&lt;/span&gt;&lt;/a&gt;)
licenses of all types: public domain, permissive, copyleft. It also
detects proprietary licenses or &lt;a class="reference external" href="https://en.wikipedia.org/wiki/End-user_license_agreement"&gt;&lt;span class="caps"&gt;EULA&lt;/span&gt;&lt;/a&gt; as well as technical problems
obtaining the license for a&amp;nbsp;dependency.&lt;/p&gt;
&lt;p&gt;Originally developed with Python packages in mind, the tool operates
in two&amp;nbsp;modes:&lt;/p&gt;
&lt;ol class="arabic simple"&gt;
&lt;li&gt;Detecting license &lt;em&gt;names and types&lt;/em&gt; for the list of Python&amp;nbsp;dependencies&lt;/li&gt;
&lt;li&gt;Detecting license &lt;strong&gt;types&lt;/strong&gt; for any list of dependencies with their
license&amp;nbsp;names&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;The tool supports the output of some popular language-specific license
detection&amp;nbsp;plugins:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;a class="reference external" href="https://github.com/jaredsburrows/gradle-license-plugin"&gt;gradle-license-plugin&lt;/a&gt; for Andoid&amp;nbsp;(Java),&lt;/li&gt;
&lt;li&gt;&lt;a class="reference external" href="https://github.com/CocoaPods/cocoapods-acknowledgements"&gt;cocoapods-acknowledgements&lt;/a&gt; of iOS (Swift,&amp;nbsp;Objective-C),&lt;/li&gt;
&lt;li&gt;&lt;a class="reference external" href="https://www.npmjs.com/package/license-checker"&gt;license-checker&lt;/a&gt; for&amp;nbsp;JavaScript,&lt;/li&gt;
&lt;li&gt;&lt;a class="reference external" href="https://github.com/technomancy/lein-licenses"&gt;lein-licenses&lt;/a&gt; for Clojure&amp;nbsp;(Leiningen),&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;as well as universal &lt;cite&gt;&lt;span class="caps"&gt;CSV&lt;/span&gt;&lt;/cite&gt; format.&lt;/p&gt;
&lt;p&gt;The tool is distributed both as&amp;nbsp;a:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;command-line&amp;nbsp;tool,&lt;/li&gt;
&lt;li&gt;containerized&amp;nbsp;application&lt;/li&gt;
&lt;li&gt;&lt;a class="reference external" href="https://github.com/pilosus/action-pip-license-checker"&gt;GitHub Action&lt;/a&gt;,&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;so that it&amp;#8217;s easy to use locally on your machine or incorporate into
the project&amp;#8217;s &lt;span class="caps"&gt;CI&lt;/span&gt;/&lt;span class="caps"&gt;CD&lt;/span&gt;&amp;nbsp;pipeline.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Takeaways&lt;/strong&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;a class="reference external" href="https://github.com/pilosus/pip-license-checker"&gt;pip-license-checker&lt;/a&gt; is a tool to check license types in your
project&amp;#8217;s third-party dependencies. It covers the majority of popular
&lt;span class="caps"&gt;FLOSS&lt;/span&gt; licenses. It has integration with some popular license fetching
plugins. You can easily incorporate the tool into &lt;span class="caps"&gt;CI&lt;/span&gt;/&lt;span class="caps"&gt;CD&lt;/span&gt; pipeline to
automate license compliance checks.&lt;/blockquote&gt;
&lt;/div&gt;
&lt;div class="section" id="why-license-checks-are-needed"&gt;
&lt;span id="why-check-license"&gt;&lt;/span&gt;&lt;h2&gt;&lt;a class="toc-backref" href="#toc-entry-3"&gt;Why license checks are&amp;nbsp;needed?&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Nobody develops software from scratch today. Software development has
evolved from an artisan shop&amp;#8217;s craft to a heavy industry assembly
line. We, software developers, &lt;em&gt;rely heavily on third-party code&lt;/em&gt;:
libraries, packages, platforms, operating systems. Building something
new has become, to a larger extent, assembling parts, applying some
glue, implementing custom logic with the code written by someone else,
someone outside of the project or the company. Most of such
third-party dependencies today are &lt;em&gt;freely available over the
internet&lt;/em&gt;, both as executable code that a machine can run, and as
human-readable code, we, developers, can use, copy and learn&amp;nbsp;from.&lt;/p&gt;
&lt;p&gt;But does being freely available over the network mean these
third-party dependencies are really free? Free as in &amp;#8220;free lunch&amp;#8221;?
Free as in &amp;#8220;freedom&amp;#8221;? Most of the dependencies are distributed under
popular &lt;span class="caps"&gt;FLOSS&lt;/span&gt; licenses, e.g. &lt;a class="reference external" href="https://www.apache.org/licenses/LICENSE-2.0.html"&gt;Apache License&lt;/a&gt;, &lt;a class="reference external" href="https://opensource.org/licenses/MIT"&gt;&lt;span class="caps"&gt;MIT&lt;/span&gt; License&lt;/a&gt;,
&lt;a class="reference external" href="https://www.mozilla.org/en-US/MPL/2.0/"&gt;Mozilla Public License&lt;/a&gt; or &lt;a class="reference external" href="https://www.gnu.org/licenses/gpl-3.0.en.html"&gt;&lt;span class="caps"&gt;GNU&lt;/span&gt; General Public License&lt;/a&gt; to name a
few. The licenses differ on the &lt;strong&gt;permissiveness/restrictiveness
scale&lt;/strong&gt;, ranging from the public-domain-like licenses with no
conditions to the licenses requiring the authors of derivative works
to give away the source code and use the same or compatible license
under certain circumstances. Some dependencies are distributed as
&amp;#8220;freeware&amp;#8221;, i.e. under proprietary end-user license agreements
(&lt;a class="reference external" href="https://en.wikipedia.org/wiki/End-user_license_agreement"&gt;&lt;span class="caps"&gt;EULA&lt;/span&gt;&lt;/a&gt;), usually with some permissions and&amp;nbsp;conditions.&lt;/p&gt;
&lt;p&gt;The conditions some licenses impose may be unacceptable for your
personal project or your company. License violation is a &lt;strong&gt;legal
risk&lt;/strong&gt;. The court cases are &lt;a class="reference external" href="https://wiki.fsfe.org/Migrated/GPL%20Enforcement%20Cases"&gt;real&lt;/a&gt;. Both financial and reputation
losses may be high. In such cases preventing license violations is
crucial to mitigate or avoid&amp;nbsp;risks.&lt;/p&gt;
&lt;p&gt;Sometimes choosing a &amp;#8220;wrong&amp;#8221; license for your &lt;span class="caps"&gt;FLOSS&lt;/span&gt; project may become
an obstacle for its wide adoption (e.g. copyleft license for a project
in a presence of many other alternative proprietary or free software
projects of high quality). Checking licenses for project&amp;#8217;s
dependencies may help to understand your options when choosing the
license most suitable for your&amp;nbsp;goals.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Takeaways&lt;/strong&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Third-party dependencies freely available over the internet are
usually distributed under some terms and conditions of &lt;a class="reference external" href="https://en.wikipedia.org/wiki/Free_and_open-source_software"&gt;&lt;span class="caps"&gt;FLOSS&lt;/span&gt;&lt;/a&gt;
licenses or &lt;a class="reference external" href="https://en.wikipedia.org/wiki/End-user_license_agreement"&gt;&lt;span class="caps"&gt;EULA&lt;/span&gt;&lt;/a&gt;. The conditions vary in their
permissiveness. Some license types may be too restrictive to be used
in your&amp;nbsp;project.&lt;/p&gt;
&lt;p&gt;Legal implications and reputation losses of license violations are
&lt;a class="reference external" href="https://wiki.fsfe.org/Migrated/GPL%20Enforcement%20Cases"&gt;real&lt;/a&gt;. To mitigate or avoid the risks regular license compliance
checks are&amp;nbsp;needed.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;/div&gt;
&lt;div class="section" id="why-automating-license-compliance-checks-is-needed"&gt;
&lt;span id="why-automate"&gt;&lt;/span&gt;&lt;h2&gt;&lt;a class="toc-backref" href="#toc-entry-4"&gt;Why automating license compliance checks is&amp;nbsp;needed?&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;The number of third-party dependencies (deps) in a project may be
high: from tens in a tiny personal pet project to hundreds in a big
one, to even thousands in the enterprise settings. As the project
evolves the list of dependencies may change over time&amp;nbsp;too.&lt;/p&gt;
&lt;p&gt;The &lt;a class="reference external" href="https://spdx.org/licenses/"&gt;list of popular &lt;span class="caps"&gt;FLOSS&lt;/span&gt; licenses&lt;/a&gt; is also long. &lt;strong&gt;Over 100
licenses and counting!&lt;/strong&gt; While some licenses are short and easy to
understand, others are challenging reads that may require a lawyer
qualification to grasp the conditions. Arguably, no average software
developer ever read all of the &lt;span class="caps"&gt;FLOSS&lt;/span&gt; licenses that exist in the&amp;nbsp;wild.&lt;/p&gt;
&lt;p&gt;Increasing dependencies versions (so-called version bumps) in the
project may also result in dependencies changing their licenses. This
is the most relevant for the deps at the early stages of their&amp;nbsp;development.&lt;/p&gt;
&lt;p&gt;All in all, when managing a project&amp;#8217;s 3rd-party dependencies, we need
not only to comply with their licenses but do these license checks
regularly, in an automated way. That requires a tool to be
incorporated into the project&amp;#8217;s continuous integration and continuous
delivery (&lt;span class="caps"&gt;CD&lt;/span&gt;/&lt;span class="caps"&gt;CD&lt;/span&gt;)&amp;nbsp;pipeline.&lt;/p&gt;
&lt;p&gt;This is exactly what &lt;a class="reference external" href="https://github.com/pilosus/pip-license-checker"&gt;pip-license-checker&lt;/a&gt; is&amp;nbsp;for.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Takeaways&lt;/strong&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Modern projects rely on &lt;strong&gt;10s, 100s or even 1000s&lt;/strong&gt; of third-party
dependencies. Their list is changing over time as projects
evolve. This is too much to check licenses&amp;nbsp;manually.&lt;/p&gt;
&lt;p&gt;&lt;a class="reference external" href="https://www.fsf.org/"&gt;Free Software Foundation&lt;/a&gt; and &lt;a class="reference external" href="https://opensource.org/"&gt;Open Source Initiative&lt;/a&gt;, the two
most respected non-profit organisations for free/libre and
open-source software, approved &lt;strong&gt;100s&lt;/strong&gt; of popular &lt;span class="caps"&gt;FLOSS&lt;/span&gt;
licenses. Reading and understanding their conditions is a challenge
for an average software developer. Expertise is&amp;nbsp;needed.&lt;/p&gt;
&lt;p&gt;Dependencies themselves may change their licenses over their
lifetime&amp;nbsp;too.&lt;/p&gt;
&lt;p&gt;Automated regular license compliance checks are&amp;nbsp;needed.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;/div&gt;
&lt;div class="section" id="who-needs-to-check-licenses"&gt;
&lt;span id="who-needs-checks"&gt;&lt;/span&gt;&lt;h2&gt;&lt;a class="toc-backref" href="#toc-entry-5"&gt;Who needs to check&amp;nbsp;licenses?&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Anyone&amp;nbsp;who:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;develops a proprietary project based on other &lt;span class="caps"&gt;FLOSS&lt;/span&gt;&amp;nbsp;code&lt;/li&gt;
&lt;li&gt;creates, contributes to, or maintains a &lt;span class="caps"&gt;FLOSS&lt;/span&gt;&amp;nbsp;project&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;As an independent open-source developer, you may want to check your
project deps&amp;#8217; licenses to make sure the project&amp;#8217;s license itself
complies with the libraries and packages it&amp;#8217;s based on. That will help
to avoid the risk of reputation losses or enable you to choose the
right license for your project to make&amp;nbsp;headway.&lt;/p&gt;
&lt;p&gt;As a pro-profit company, you may want to make sure your proprietary
software product doesn&amp;#8217;t violate dependencies licenses to avoid legal
risks, financial and reputation losses. As a startup, you may want to
have license compliance in place before an investment round, valuation
or&amp;nbsp;acquisition.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="why-pip-license-checker"&gt;
&lt;span id="who-pip-license-checker"&gt;&lt;/span&gt;&lt;h2&gt;&lt;a class="toc-backref" href="#toc-entry-6"&gt;Why &lt;cite&gt;pip-license-checker&lt;/cite&gt;?&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;There are dozens of tools and plugins to detect &lt;strong&gt;license names&lt;/strong&gt;
for the third-party dependencies used in your project. But as it has
been said above, for a large number of dependencies and an
&lt;a class="reference external" href="https://en.wikipedia.org/wiki/License_proliferation"&gt;ever-growing number&lt;/a&gt; of open source licenses, having the list of
license names may bring little or no value without legal expertise&amp;nbsp;applied.&lt;/p&gt;
&lt;p&gt;&lt;a class="reference external" href="https://github.com/pilosus/pip-license-checker"&gt;pip-license-checker&lt;/a&gt; brings some of the expertise needed by
detecting &lt;a class="reference internal" href="#license-types"&gt;license types&lt;/a&gt;, so that a user may decide what types are
unacceptable in her/his project and get alerted whenever the licenses
of &amp;#8220;wrong&amp;#8221; types are found among the project&amp;#8217;s third-party&amp;nbsp;dependencies.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="what-are-license-types"&gt;
&lt;span id="license-types"&gt;&lt;/span&gt;&lt;h2&gt;&lt;a class="toc-backref" href="#toc-entry-7"&gt;What are license&amp;nbsp;types?&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Despite a large number of licenses used by third-party dependencies,
all of them can be split into a relatively small number of&amp;nbsp;categories.&lt;/p&gt;
&lt;div class="section" id="copyright-or-end-user-license-agreement"&gt;
&lt;span id="copyright"&gt;&lt;/span&gt;&lt;h3&gt;&lt;a class="toc-backref" href="#toc-entry-8"&gt;Copyright or End-user license&amp;nbsp;agreement&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;At the highest level, we may divide all the licenses into proprietary
end-user license agreements (&lt;a class="reference external" href="https://en.wikipedia.org/wiki/End-user_license_agreement"&gt;&lt;span class="caps"&gt;EULA&lt;/span&gt;&lt;/a&gt;) and free/libre and open-source
(&lt;a class="reference external" href="https://en.wikipedia.org/wiki/Free_and_open-source_software"&gt;&lt;span class="caps"&gt;FLOSS&lt;/span&gt;&lt;/a&gt;) licenses. &lt;span class="caps"&gt;EULA&lt;/span&gt; dependencies may be freely available over
the internet or even come with the source code available along with an
executable object code, but their usage, distribution and modification
may still be regulated in a more traditional copyright fashion: you
need to ask authors for some permissions explicitly. By contrast, free
software licenses grant the recipient of a piece of software rights to
use, modify, redistribute the&amp;nbsp;software.&lt;/p&gt;
&lt;p&gt;&lt;span class="caps"&gt;FLOSS&lt;/span&gt; licenses, in turn, can be broken down into more types. They
differ on the &amp;#8220;permissiveness/restrictiveness&amp;#8221; scale by the conditions
they impose on someone who modifies and distributes the original
software or its derivative&amp;nbsp;works.&lt;/p&gt;
&lt;p&gt;If you use a third-party dependency under a proprietary license
agreement, it&amp;#8217;s worth reading it as some unexpected conditions may
apply even if the dependency is freely available over the&amp;nbsp;network.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="public-domain-like-licenses-1"&gt;
&lt;span id="public-domain-like-licenses"&gt;&lt;/span&gt;&lt;h3&gt;&lt;a class="toc-backref" href="#toc-entry-9"&gt;Public-domain-like&amp;nbsp;licenses&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;The most permissive type of &lt;span class="caps"&gt;FLOSS&lt;/span&gt; licenses is &lt;a class="reference external" href="https://en.wikipedia.org/wiki/Public-domain-equivalent_license"&gt;public domain&lt;/a&gt;
equivalent licenses. &lt;a class="reference external" href="https://opensource.org/licenses/0BSD"&gt;Zero-Clause &lt;span class="caps"&gt;BSD&lt;/span&gt;&lt;/a&gt; is a great example of
public-domain-like licenses. This license type has &lt;strong&gt;no conditions&lt;/strong&gt;,
but may have a &lt;a class="reference external" href="https://en.wikipedia.org/wiki/Liability_waiver"&gt;liability waiver&lt;/a&gt;.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="permissive-licenses-requiring-attribution"&gt;
&lt;span id="permissive-with-notice"&gt;&lt;/span&gt;&lt;h3&gt;&lt;a class="toc-backref" href="#toc-entry-10"&gt;Permissive licenses requiring&amp;nbsp;attribution&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Permissive licenses with attribution clause usually allow modification
and redistribution under the license of choice with the only condition
of preserving original copyright notice. Some licenses are
indiscriminate about how exactly the attribution is to be done, while
others require prominent notice, e.g. preserving the original notice
in the source code and adding the copyright notice to the
application&amp;#8217;s legal section. &lt;a class="reference external" href="https://www.apache.org/licenses/LICENSE-2.0.html"&gt;Apache License&lt;/a&gt; and &lt;a class="reference external" href="https://opensource.org/licenses/MIT"&gt;&lt;span class="caps"&gt;MIT&lt;/span&gt; License&lt;/a&gt; both
fall under this license&amp;nbsp;type.&lt;/p&gt;
&lt;p&gt;Along with public-domain-like licenses, this license type is the most
permissive of all &lt;span class="caps"&gt;FLOSS&lt;/span&gt; licenses, which makes them easy to be hijacked
into a proprietary close-sourced code base or incorporated into a
&lt;span class="caps"&gt;FLOSS&lt;/span&gt; project with no&amp;nbsp;relicensing.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="copyleft"&gt;
&lt;span id="copyleft-licenses"&gt;&lt;/span&gt;&lt;h3&gt;&lt;a class="toc-backref" href="#toc-entry-11"&gt;Copyleft&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;&lt;a class="reference external" href="https://en.wikipedia.org/wiki/Copyleft"&gt;Copyleft&lt;/a&gt; is the next big category of licenses. The licenses of this
type may also be called &lt;strong&gt;reciprocal&lt;/strong&gt; as they require that
modification and distribution of derivative works are done under the
same or compatible license with the source code made&amp;nbsp;available.&lt;/p&gt;
&lt;p&gt;The copyleft licenses differ on the &amp;#8220;permissiveness/restrictiveness&amp;#8221;
scale as well and can be broken down into the following fine-grained
copyleft license&amp;nbsp;types.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="weak-copyleft-1"&gt;
&lt;span id="weak-copyleft"&gt;&lt;/span&gt;&lt;h3&gt;&lt;a class="toc-backref" href="#toc-entry-12"&gt;Weak&amp;nbsp;copyleft&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Weak copyleft licenses trigger the copyleft (reciprocal share-alike
mechanism requiring to disclose the derivative work&amp;#8217;s source code and
to use the same or compatible license) when an original file or a
library under the weak copyleft license is modified and
redistributed. Simply using or incorporating the file or a library
under weak copyleft into a &lt;a class="reference external" href="https://www.mozilla.org/en-US/MPL/2.0/FAQ/#virality"&gt;larger work&lt;/a&gt; (e.g. by dynamic linking)
and distributing it does not require giving away the source code or
use the corresponding weak copyleft license. &lt;a class="reference external" href="https://www.mozilla.org/en-US/MPL/2.0/"&gt;Mozilla Public License&lt;/a&gt;
and &lt;a class="reference external" href="https://www.eclipse.org/org/documents/epl-2.0/EPL-2.0.txt"&gt;Eclipse Public License&lt;/a&gt; are good examples of weak copyleft type
of &lt;span class="caps"&gt;FLOSS&lt;/span&gt;&amp;nbsp;licenses.&lt;/p&gt;
&lt;p&gt;Third-party deps under weak copyleft licenses try to be
business-friendly by allowing incorporating into a larger work but
care about sharing the improvements made to the original work. Weak
copyleft licenses may be unacceptable in some cases but very welcome
in&amp;nbsp;others.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="strong-copyleft-1"&gt;
&lt;span id="strong-copyleft"&gt;&lt;/span&gt;&lt;h3&gt;&lt;a class="toc-backref" href="#toc-entry-13"&gt;Strong&amp;nbsp;copyleft&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;The strong copyleft license type tries to go even further in its
conditions that require to disclose the source code of the derivative
works and require to use the same strong copyleft license. Combining
your proprietary modules with a module under strong copyleft into a
single executable file; dynamic linking of a library under strong
copyleft license in your permissive &lt;span class="caps"&gt;FLOSS&lt;/span&gt; project; or even exchanging
data between your program and a standalone program under strong
copyleft in such a way that &lt;a class="reference external" href="https://www.gnu.org/licenses/gpl-faq.html#MereAggregation"&gt;semantics of the communication are
intimate enough&lt;/a&gt;, exchanging complex internal data structures, may be
considered a work based on the software under the strong copyleft
(derivative work) so that in case of distribution the source code must
be given away and the same strong copyleft license must be used for
the derivative work. &lt;a class="reference external" href="https://www.gnu.org/licenses/gpl-3.0.en.html"&gt;&lt;span class="caps"&gt;GNU&lt;/span&gt; General Public License&lt;/a&gt; is the most
illustrative example of a strong copyleft license&amp;nbsp;type.&lt;/p&gt;
&lt;p&gt;Third-party dependencies under strong copyleft licenses cannot be part
of a proprietary code delivered to the outside users (unless a
proprietor decided to give away a source code of the project and
relicense it under a corresponding strong copyleft license). Private
usage of a derivative work without distribution to outside users or
customers (e.g. on your computer only, or on your company&amp;#8217;s server
only) is not considered a distribution and does not trigger the&amp;nbsp;copyleft.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="copyleft-over-a-network"&gt;
&lt;span id="network-copyleft"&gt;&lt;/span&gt;&lt;h3&gt;&lt;a class="toc-backref" href="#toc-entry-14"&gt;Copyleft over a&amp;nbsp;network&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;The most restrictive license category of all copyleft
licenses. Network copyleft license type picks up where the strong
copyleft has left, i.e. it expands what distribution is and amends
what private usage is by considering users interaction with a
derivative work over a network as the work&amp;#8217;s distribution. &lt;a class="reference external" href="https://www.gnu.org/licenses/agpl-3.0.html"&gt;&lt;span class="caps"&gt;GNU&lt;/span&gt; Affero
General Public License&lt;/a&gt;, a notable example of the
copyleft-over-a-network license, aims at programs that are &lt;a class="reference external" href="https://www.gnu.org/licenses/why-affero-gpl.en.html"&gt;used on
servers&lt;/a&gt;:&lt;/p&gt;
&lt;blockquote&gt;
[&amp;#8230;] if you run a modified program on a server and let other users
communicate with it there, your server must also allow them to
download the source code corresponding to the modified version
running there.&lt;/blockquote&gt;
&lt;p&gt;Copyleft over a network license type may be too restrictive in many
cases so that detecting it in your project&amp;#8217;s dependencies is&amp;nbsp;important.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="section" id="license-categorisation-1"&gt;
&lt;span id="license-categorisation"&gt;&lt;/span&gt;&lt;h2&gt;&lt;a class="toc-backref" href="#toc-entry-15"&gt;License&amp;nbsp;categorisation&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Categorisation of popular &lt;span class="caps"&gt;FLOSS&lt;/span&gt; licenses into a few above-mentioned
license types is the primary value of the &lt;a class="reference external" href="https://github.com/pilosus/pip-license-checker"&gt;pip-license-checker&lt;/a&gt;
tool. Technically speaking, it&amp;#8217;s just a mapping between regular
expressions for license names to license types. But making up such a
mapping requires reading and understanding the &lt;a class="reference external" href="https://spdx.org/licenses/"&gt;license texts&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Some licenses are derived from others, e.g. a license &lt;span class="caps"&gt;ZYX&lt;/span&gt; version 3.0
is a successor of the deprecated license &lt;span class="caps"&gt;ZYX&lt;/span&gt; version 2.0. In such
cases, instead of reading both text categorisation could have relied
on the latest version&amp;#8217;s text along with the license&amp;#8217;s official &lt;span class="caps"&gt;FAQ&lt;/span&gt;
explaining the differences between the two&amp;nbsp;versions.&lt;/p&gt;
&lt;p&gt;Lacking lawyers support in the license categorisation process could
have also resulted in some inaccuracies. Please, &lt;a class="reference external" href="https://github.com/pilosus/pip-license-checker/issues"&gt;report issues&lt;/a&gt;
regarding the license&amp;nbsp;types.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="technical-facts"&gt;
&lt;span id="tech-facts"&gt;&lt;/span&gt;&lt;h2&gt;&lt;a class="toc-backref" href="#toc-entry-16"&gt;Technical&amp;nbsp;facts&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;&lt;a class="reference external" href="https://github.com/pilosus/pip-license-checker"&gt;pip-license-checker&lt;/a&gt; is a program written in &lt;a class="reference external" href="https://clojure.org/"&gt;Clojure&lt;/a&gt;. With
quality in mind, the code has high &lt;a class="reference external" href="https://en.wikipedia.org/wiki/Code_coverage"&gt;test coverage&lt;/a&gt; and uses
generative property-based testing. The tool&amp;#8217;s own &lt;span class="caps"&gt;CI&lt;/span&gt;/&lt;span class="caps"&gt;CD&lt;/span&gt; pipeline
includes a step for checking 3rd-party dependencies&amp;#8217; licenses with
&lt;a class="reference external" href="https://github.com/pilosus/pip-license-checker"&gt;pip-license-checker&lt;/a&gt;.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="outro-1"&gt;
&lt;span id="outro"&gt;&lt;/span&gt;&lt;h2&gt;&lt;a class="toc-backref" href="#toc-entry-17"&gt;Outro&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Make use you know what license types are safe/desirable for your
project&amp;#8217;s 3rd-party dependencies. Is your project an open-source
library for others to dynamically link in their software? Or a
closed-source mobile app available over a popular digital distribution
service as an executable? Or an embedded software for a device for
wide public use? A website&amp;#8217;s frontend software delivered to users when
the website is requested via a browser? Or a backend software
privately run in the background on your company&amp;#8217;s server? In other
words, what&amp;#8217;s your project&amp;#8217;s usage and distribution &amp;#8220;profile&amp;#8221; and what
are the actions that are not acceptable for the project: source code
disclosure, an obligation to change the license to reciprocal one,
dependency&amp;#8217;s authors&amp;#8217; attribution,&amp;nbsp;etc.?&lt;/p&gt;
&lt;p&gt;Once you know the answers, try to incorporate third-party deps license
checks into your project&amp;#8217;s &lt;span class="caps"&gt;CI&lt;/span&gt;/&lt;span class="caps"&gt;CD&lt;/span&gt; pipeline to detect licenses you are
not safe to use. The &lt;a class="reference external" href="https://github.com/pilosus/pip-license-checker"&gt;pip-license-checker&lt;/a&gt; tool may help with&amp;nbsp;that.&lt;/p&gt;
&lt;p&gt;&lt;span class="caps"&gt;P.S.&lt;/span&gt; See the tool&amp;#8217;s documentation in the &lt;a class="reference external" href="https://github.com/pilosus/pip-license-checker"&gt;pip-license-checker&lt;/a&gt; GitHub
repository and the correspondent &lt;a class="reference external" href="https://github.com/pilosus/action-pip-license-checker"&gt;GitHub Action&lt;/a&gt;. Your feedback is
welcome via &lt;a class="reference external" href="https://blog.pilosus.org/pages/contact/"&gt;email&lt;/a&gt; or &lt;a class="reference external" href="https://github.com/pilosus/pip-license-checker/issues"&gt;GitHub Issues&lt;/a&gt;.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="disclaimer-1"&gt;
&lt;span id="disclaimer"&gt;&lt;/span&gt;&lt;h2&gt;&lt;a class="toc-backref" href="#toc-entry-18"&gt;Disclaimer&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;&lt;a class="reference external" href="https://github.com/pilosus/pip-license-checker"&gt;pip-license-checker&lt;/a&gt; and this blog post are provided on an &amp;#8220;as-is&amp;#8221;
basis and make no warranties regarding any information provided
through them and disclaim liability for damages resulting from using
them. Using &lt;a class="reference external" href="https://github.com/pilosus/pip-license-checker"&gt;pip-license-checker&lt;/a&gt; and this blog post does not
constitute legal advice nor does it create an attorney-client&amp;nbsp;relationship.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="addendum-1"&gt;
&lt;span id="addendum"&gt;&lt;/span&gt;&lt;h2&gt;&lt;a class="toc-backref" href="#toc-entry-19"&gt;Addendum&lt;/a&gt;&lt;/h2&gt;
&lt;div class="section" id="interview-2021-09-15"&gt;
&lt;h3&gt;&lt;a class="toc-backref" href="#toc-entry-20"&gt;Interview&amp;nbsp;2021-09-15&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;I&amp;#8217;ve been &lt;a class="reference external" href="https://youtu.be/OMT2uouK__8"&gt;interviewed&lt;/a&gt; by &lt;a class="reference external" href="https://www.yegor256.com/"&gt;Yegor Bugayenko&lt;/a&gt; recently. We&amp;#8217;ve talked
about the &lt;a class="reference external" href="https://github.com/pilosus/pip-license-checker"&gt;pip-license-checker&lt;/a&gt;. As my very first public presentation
of the project, the interview isn&amp;#8217;t a perfect one. I&amp;#8217;ve updated and
extended this blog post in an attempt to fix all the flaws of my
answers to the interviewer&amp;#8217;s questions. Overall, the interview was
good to show the project&amp;#8217;s shortcomings and to give some directions
for further&amp;nbsp;development.&lt;/p&gt;
&lt;!-- Links --&gt;
&lt;/div&gt;
&lt;/div&gt;
</content><category term="Blog"></category><category term="license"></category><category term="dependencies"></category><category term="license-management"></category><category term="compliance"></category><category term="foss"></category><category term="clojure"></category></entry><entry><title>Book review: The Clean Coder by Robert C. Martin</title><link href="https://blog.pilosus.org/posts/2020/06/14/the-clean-coder-book-review/" rel="alternate"></link><published>2020-06-14T22:30:00+02:00</published><updated>2020-06-14T22:30:00+02:00</updated><author><name>Vitaly Samigullin</name></author><id>tag:blog.pilosus.org,2020-06-14:/posts/2020/06/14/the-clean-coder-book-review/</id><summary type="html">&lt;p class="first last"&gt;A Code Of Conduct For Professional&amp;nbsp;Programmers&lt;/p&gt;
</summary><content type="html">&lt;p&gt;&lt;em&gt;A Code Of Conduct For Professional Programmers&lt;/em&gt;, a subtitle of the
book, says it all. Robert C. Martin answers the question: what does it
mean to be a professional&amp;nbsp;programmer.&lt;/p&gt;
&lt;p&gt;If professionalism could be boiled down to a single aspect it&amp;#8217;s
&lt;em&gt;responsibility&lt;/em&gt;. Responsibility for the&amp;nbsp;following:&lt;/p&gt;
&lt;ol class="arabic simple"&gt;
&lt;li&gt;Product&amp;nbsp;quality&lt;/li&gt;
&lt;li&gt;Time&amp;nbsp;estimates&lt;/li&gt;
&lt;li&gt;Keeping competences and skills up to&amp;nbsp;date&lt;/li&gt;
&lt;li&gt;Understanding business goals and&amp;nbsp;interests&lt;/li&gt;
&lt;/ol&gt;
&lt;div class="section" id="product-quality"&gt;
&lt;h2&gt;Product&amp;nbsp;quality&lt;/h2&gt;
&lt;p&gt;The book dedicates a few chapters to different aspects of keeping high
standards of product development. The technical aspect of quality is
about &lt;em&gt;software testing&lt;/em&gt;:&lt;/p&gt;
&lt;ol class="loweralpha simple"&gt;
&lt;li&gt;&lt;em&gt;hierarchy of testing&lt;/em&gt; (unit, component, integration, system,
research&amp;nbsp;testing)&lt;/li&gt;
&lt;li&gt;&lt;em&gt;test-driven development&lt;/em&gt; (&lt;span class="caps"&gt;TDD&lt;/span&gt;) as a way to deliver quality code, a
safety net for refactoring, self-documentation for developers, a way
to reduce code complexity and design program architecture more&amp;nbsp;carefully.&lt;/li&gt;
&lt;li&gt;&lt;em&gt;acceptance testing&lt;/em&gt; as project documentation and criteria of
what&amp;#8217;s &amp;#8220;done&amp;#8221; (tests&amp;nbsp;passing)&lt;/li&gt;
&lt;li&gt;testing&amp;nbsp;automation&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;The non-technical aspect of quality includes techniques to boost
attention span. The author also dismantles some myths about
productivity: coding late in the night, overtime work,&amp;nbsp;etc.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="time-estimates"&gt;
&lt;h2&gt;Time&amp;nbsp;estimates&lt;/h2&gt;
&lt;p&gt;Being responsible means you want to be predictable to your colleagues,
deliver code on time. Correct time estimates are the single most
important way to be predictable for a professional&amp;nbsp;programmer.&lt;/p&gt;
&lt;p&gt;The author just scratches the surface of the technical aspects of time
estimation (&lt;span class="caps"&gt;PERT&lt;/span&gt; technique, planning poker, etc.). But he goes to
great lengths to educate about the skill of saying &amp;#8220;No&amp;#8221; and&amp;nbsp;&amp;#8220;Yes&amp;#8221;.&lt;/p&gt;
&lt;p&gt;It&amp;#8217;s okay for the business to try to get precise time estimates to
eliminate the risks. It&amp;#8217;s also okay for the business to wish to get
things done quickly. Yet accepting unrealistic deadlines comes at a
price for a programmer: work/life balance, productivity, and product
quality, all of them deteriorate so that possible short-run benefits
of working under pressure turn into a long-term&amp;nbsp;disaster.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="keeping-competences-and-skills-up-to-date"&gt;
&lt;h2&gt;Keeping competences and skills up to&amp;nbsp;date&lt;/h2&gt;
&lt;p&gt;A professional programmer must keep his/her technical and soft skills
up to date. Technical skills require regular training. Just like
professional musicians do their chores with scales, professional
programmers should practice so-called code kata, programming or tech
design problems, the author&amp;nbsp;argues.&lt;/p&gt;
&lt;p&gt;Learning new languages and systems in your spare time is also
essential for a professional&amp;nbsp;programmer.&lt;/p&gt;
&lt;p&gt;Soft skills may include the ability to work in a team, do pair
programming, or mentoring interns and&amp;nbsp;colleagues.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="understanding-business-goals-and-interests"&gt;
&lt;h2&gt;Understanding business goals and&amp;nbsp;interests&lt;/h2&gt;
&lt;p&gt;As a professional (i.e. responsible) programmer, you care about
business interests. The business problems are your problems&amp;nbsp;too.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="conclusion"&gt;
&lt;h2&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;The book has been published back in 2011. The industry has progressed
quite a bit since then. Many bits of advice and techniques &amp;#8220;Uncle Bob&amp;#8221;
suggests in his book now is a common practice. It&amp;#8217;s unclear if the
book will become a revelation for a reader in 2020. Yet it certainly
encourages you to think about what does it mean exactly to be a
professional and master your&amp;nbsp;skills.&lt;/p&gt;
&lt;/div&gt;
</content><category term="Blog"></category><category term="book"></category><category term="review"></category></entry><entry><title>PostgreSQL: find sequences of same-parameter values within ordered groups</title><link href="https://blog.pilosus.org/posts/2020/04/18/postgresql-consecutive-occurrences-within-ordered-groups/" rel="alternate"></link><published>2020-04-18T18:00:00+02:00</published><updated>2020-04-18T18:00:00+02:00</updated><author><name>Vitaly Samigullin</name></author><id>tag:blog.pilosus.org,2020-04-18:/posts/2020/04/18/postgresql-consecutive-occurrences-within-ordered-groups/</id><summary type="html">&lt;p class="first last"&gt;Using a window function find and group consecutive
occurences of the values with the same&amp;nbsp;paratemer&lt;/p&gt;
</summary><content type="html">&lt;p&gt;Recently I&amp;#8217;ve been solving a problem of finding database items that
have to be grouped by one parameter (let&amp;#8217;s say a name), then ordered
by the timestamp within each group. The tricky part was to find groups
where the last N most recent items all have the same second parameter
(say, a flag). A colleague of mine pointed out to even more
interesting variation of the same problem: find such groups that
contain sequences of the size N of rows with the same&amp;nbsp;flag.&lt;/p&gt;
&lt;p&gt;Let&amp;#8217;s create a &lt;a class="reference external" href="http://sqlfiddle.com/#!17/a1730/3"&gt;table&lt;/a&gt; to illustrate the problem for the sequence
size of 3 or more and a flag &lt;tt class="docutils literal"&gt;A&lt;/tt&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="k"&gt;CREATE&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;TABLE&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;names&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;varchar&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;dt&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;timestamp&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;flag&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;varchar&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="k"&gt;INSERT&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;INTO&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;names&lt;/span&gt;
&lt;span class="w"&gt;  &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;dt&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;flag&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;VALUES&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;Name1&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;2020-01-01 00:00:00&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;A&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;Name1&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;2020-02-01 00:00:00&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;A&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;Name1&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;2020-03-01 00:00:00&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;A&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;Name1&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;2020-04-01 00:00:00&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;B&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;Name1&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;2020-05-01 00:00:00&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;B&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;Name1&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;2020-06-01 00:00:00&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;A&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;Name1&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;2020-07-01 00:00:00&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;B&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;Name1&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;2020-08-01 00:00:00&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;A&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;Name2&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;2020-01-01 00:00:00&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;A&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;Name2&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;2020-02-01 00:00:00&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;A&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;Name2&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;2020-03-01 00:00:00&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;B&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;Name2&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;2020-04-01 00:00:00&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;A&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;What we would like to&amp;nbsp;have:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;The result should be grouped by the &lt;tt class="docutils literal"&gt;name&lt;/tt&gt; column&lt;/li&gt;
&lt;li&gt;Within the group, the rows should be ordered by &lt;tt class="docutils literal"&gt;dt&lt;/tt&gt; column&lt;/li&gt;
&lt;li&gt;We are interested only in the groups containing sequences of 3 or
more rows with the flag &lt;tt class="docutils literal"&gt;A&lt;/tt&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;As we see in the data above &lt;tt class="docutils literal"&gt;Name1&lt;/tt&gt; has a sequence of three rows
with the flag &lt;tt class="docutils literal"&gt;A&lt;/tt&gt; (with dt &lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;2020-01-01&lt;/span&gt;&lt;/tt&gt;, &lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;2020-02-01&lt;/span&gt;&lt;/tt&gt;, and
&lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;2020-03-01&lt;/span&gt;&lt;/tt&gt;). All other ordered by date consecutive rows with the
same flag have two items or less. Thus the resulting &lt;span class="caps"&gt;SQL&lt;/span&gt; query should
return &lt;tt class="docutils literal"&gt;Name1&lt;/tt&gt;.&lt;/p&gt;
&lt;p&gt;Whenever we need to group a set by the subsets of rows (windows) and
then do something over these subsets, &lt;a class="reference external" href="https://www.postgresqltutorial.com/postgresql-window-function/"&gt;window function&lt;/a&gt; comes in
handy. In this case, we would use &lt;a class="reference external" href="https://www.postgresqltutorial.com/postgresql-row_number/"&gt;ROW_NUMBER()&lt;/a&gt; that assigns a
sequential integer to each row in a result&amp;nbsp;set.&lt;/p&gt;
&lt;p&gt;Postgres window functions have &lt;tt class="docutils literal"&gt;&lt;span class="caps"&gt;PARTITION&lt;/span&gt; &lt;span class="caps"&gt;BY&lt;/span&gt;&lt;/tt&gt; clause that divides
rows into multiple partitions. We can use more than one column here to
group by the &lt;tt class="docutils literal"&gt;name&lt;/tt&gt; and the &lt;tt class="docutils literal"&gt;flag&lt;/tt&gt; columns. Let&amp;#8217;s explore&amp;nbsp;it:&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="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;ROW_NUMBER&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;OVER&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;PARTITION&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;BY&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;ORDER&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;BY&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;dt&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;AS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;name_part&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;ROW_NUMBER&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;OVER&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;PARTITION&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;BY&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;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;flag&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;ORDER&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;BY&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;dt&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;AS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;name_flag_part&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;ROW_NUMBER&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;OVER&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;PARTITION&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;BY&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;ORDER&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;BY&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;dt&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;ROW_NUMBER&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;OVER&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;PARTITION&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;BY&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;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;flag&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;ORDER&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;BY&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;dt&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;AS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;seq&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;names&lt;/span&gt;
&lt;span class="k"&gt;ORDER&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;BY&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;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;dt&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;div class="figure"&gt;
&lt;img alt="window function with partitioning by multiple columns" class="responsive" src="/images/row_number.png" /&gt;
&lt;/div&gt;
&lt;p&gt;As you can see partitioning by the &lt;tt class="docutils literal"&gt;name&lt;/tt&gt; column in &lt;tt class="docutils literal"&gt;ROW_NUMBER()&lt;/tt&gt;
function assigns consecutive row numbers within groups by the &lt;tt class="docutils literal"&gt;name&lt;/tt&gt;
column (a red bar for the &lt;tt class="docutils literal"&gt;Name1&lt;/tt&gt;, a black one for &lt;tt class="docutils literal"&gt;Name2&lt;/tt&gt;).&lt;/p&gt;
&lt;p&gt;When partitioning by the two columns &lt;tt class="docutils literal"&gt;name&lt;/tt&gt; and &lt;tt class="docutils literal"&gt;flag&lt;/tt&gt; we have
independent numbering within each name group for each flag&amp;nbsp;subgroup:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;green (flag &lt;tt class="docutils literal"&gt;A&lt;/tt&gt;, name &lt;tt class="docutils literal"&gt;Name1&lt;/tt&gt;)&lt;/li&gt;
&lt;li&gt;blue (flag &lt;tt class="docutils literal"&gt;B&lt;/tt&gt;, name &lt;tt class="docutils literal"&gt;Name1&lt;/tt&gt;)&lt;/li&gt;
&lt;li&gt;yellow (flag &lt;tt class="docutils literal"&gt;A&lt;/tt&gt;, name &lt;tt class="docutils literal"&gt;Name2&lt;/tt&gt;)&lt;/li&gt;
&lt;li&gt;magenta (flag &lt;tt class="docutils literal"&gt;B&lt;/tt&gt;, name &lt;tt class="docutils literal"&gt;Name2&lt;/tt&gt;)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Given these numberings, how can we detect a sequence occurrence? We
can subtract a row number for &lt;tt class="docutils literal"&gt;name&lt;/tt&gt; and &lt;tt class="docutils literal"&gt;flag&lt;/tt&gt; partitioning from
the row number for the &lt;tt class="docutils literal"&gt;name&lt;/tt&gt; column partitioning (see alias &lt;tt class="docutils literal"&gt;seq&lt;/tt&gt;
above). The subtaction gives each flag subgroup the same numbers that
we can use for&amp;nbsp;counting:&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;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;MIN&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;dt&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;AS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;min_dt&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;flag&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;COUNT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;AS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;counter&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;seq&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;ROW_NUMBER&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;OVER&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;PARTITION&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;BY&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;ORDER&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;BY&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;dt&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;ROW_NUMBER&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;OVER&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;PARTITION&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;BY&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;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;flag&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;ORDER&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;BY&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;dt&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;AS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;seq&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;names&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;numbered&lt;/span&gt;
&lt;span class="k"&gt;GROUP&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;BY&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;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;flag&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;seq&lt;/span&gt;
&lt;span class="k"&gt;HAVING&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;COUNT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;AND&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;flag&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;A&amp;#39;&lt;/span&gt;
&lt;span class="k"&gt;ORDER&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;BY&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;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;min_dt&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;div class="figure"&gt;
&lt;img alt="window functions" class="responsive" src="/images/row_number2.png" /&gt;
&lt;/div&gt;
&lt;p&gt;We do the&amp;nbsp;following:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;Find the difference between row numbers when partitioning by a name
and partitioning by a name and a&amp;nbsp;flag&lt;/li&gt;
&lt;li&gt;Group the set by name, flag and the difference from the step&amp;nbsp;above&lt;/li&gt;
&lt;li&gt;Apply filtering for the grouped result to find sequences with flag
&lt;tt class="docutils literal"&gt;A&lt;/tt&gt; only with 3 or more&amp;nbsp;rows&lt;/li&gt;
&lt;li&gt;Order by the name and the timestamp of the first row in the&amp;nbsp;sequence&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Now that we found the solution let&amp;#8217;s finalize the query to meet our&amp;nbsp;goal:&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="k"&gt;DISTINCT&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt;
&lt;span class="w"&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;flag&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;COUNT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;AS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;counter&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;seq&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="n"&gt;ROW_NUMBER&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;OVER&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;PARTITION&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;BY&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;ORDER&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;BY&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;dt&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;ROW_NUMBER&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;OVER&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;PARTITION&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;BY&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;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;flag&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;ORDER&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;BY&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;dt&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;AS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;seq&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;names&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;numbered&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;GROUP&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;BY&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;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;flag&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;seq&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;HAVING&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;COUNT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;AND&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;flag&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;A&amp;#39;&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;filtered&lt;/span&gt;
&lt;span class="k"&gt;ORDER&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;BY&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Summing up the post I would say that &lt;span class="caps"&gt;SQL&lt;/span&gt; and PostgreSQL specifically
is a powerfull tool worth learning. Window functions allow doing some
sophisticated analytics with a single &lt;span class="caps"&gt;SQL&lt;/span&gt;&amp;nbsp;query.&lt;/p&gt;
</content><category term="Blog"></category><category term="sql"></category><category term="postgres"></category></entry><entry><title>workflow-tools: start using GitHub Actions at scale</title><link href="https://blog.pilosus.org/posts/2020/04/01/workflow-tools-start-using-github-actions-at-scale/" rel="alternate"></link><published>2020-04-01T18:10:00+02:00</published><updated>2020-04-01T18:10:00+02:00</updated><author><name>Vitaly Samigullin</name></author><id>tag:blog.pilosus.org,2020-04-01:/posts/2020/04/01/workflow-tools-start-using-github-actions-at-scale/</id><summary type="html">&lt;p&gt;Microservice architecture may have dozens or even hundreds of
lookalikes services that require similar &lt;span class="caps"&gt;CI&lt;/span&gt;/&lt;span class="caps"&gt;CD&lt;/span&gt; workflows. With
infrastructure as code approach taken by the GitHub Actions, why not
using code generation? Provisioning a repository for a new
microservice may also be&amp;nbsp;automated.&lt;/p&gt;
&lt;p&gt;I&amp;#8217;ve just opensourced an internal tool …&lt;/p&gt;</summary><content type="html">&lt;p&gt;Microservice architecture may have dozens or even hundreds of
lookalikes services that require similar &lt;span class="caps"&gt;CI&lt;/span&gt;/&lt;span class="caps"&gt;CD&lt;/span&gt; workflows. With
infrastructure as code approach taken by the GitHub Actions, why not
using code generation? Provisioning a repository for a new
microservice may also be&amp;nbsp;automated.&lt;/p&gt;
&lt;p&gt;I&amp;#8217;ve just opensourced an internal tool we use here at &lt;a class="reference external" href="https://anna.money/"&gt;&lt;span class="caps"&gt;ANNA&lt;/span&gt;&lt;/a&gt; to
automate GitHub Actions-based &lt;span class="caps"&gt;CI&lt;/span&gt;/&lt;span class="caps"&gt;CD&lt;/span&gt; workflow
generation. &lt;a class="reference external" href="https://github.com/anna-money/workflow-tools"&gt;workflow-tools&lt;/a&gt; is a collection of &lt;span class="caps"&gt;CLI&lt;/span&gt; tools used&amp;nbsp;to:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;generate GitHub Actions workflows from a&amp;nbsp;Jijna2-template&lt;/li&gt;
&lt;li&gt;set GitHub secrets (used by the&amp;nbsp;workflows)&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="section" id="examples"&gt;
&lt;h2&gt;Examples&lt;/h2&gt;
&lt;div class="figure"&gt;
&lt;a class="reference external image-reference" href="https://blog.pilosus.org/images/workflow-tools.png"&gt;&lt;img alt="workflow-tools" class="align-right" src="https://blog.pilosus.org/images/workflow-tools.png" style="width: 300px;" /&gt;&lt;/a&gt;
&lt;/div&gt;
&lt;p&gt;Let&amp;#8217;s consider a real world example: setting up a GitHub Actions workflow for the &lt;a class="reference external" href="https://github.com/anna-money/workflow-tools"&gt;workflow-tools&lt;/a&gt; repository itself.
We&amp;nbsp;need:&lt;/p&gt;
&lt;ol class="arabic simple"&gt;
&lt;li&gt;Generate a GitHub Action workflow using &lt;tt class="docutils literal"&gt;workflow_generator&lt;/tt&gt; tool&lt;/li&gt;
&lt;li&gt;Set GitHub Secrets the workflow needs using &lt;tt class="docutils literal"&gt;workflow_secret&lt;/tt&gt; tool&lt;/li&gt;
&lt;/ol&gt;
&lt;div class="section" id="generating-workflow"&gt;
&lt;h3&gt;Generating&amp;nbsp;workflow&lt;/h3&gt;
&lt;div class="highlight"&gt;&lt;table class="highlighttable"&gt;&lt;tr&gt;&lt;td class="linenos"&gt;&lt;div class="linenodiv"&gt;&lt;pre&gt;&lt;span class="normal"&gt;1&lt;/span&gt;
&lt;span class="normal"&gt;2&lt;/span&gt;
&lt;span class="normal"&gt;3&lt;/span&gt;
&lt;span class="normal"&gt;4&lt;/span&gt;
&lt;span class="normal"&gt;5&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;div&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="nv"&gt;WORKFLOW_RUNNER_VERSION&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;ubuntu-latest&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;
workflow_generator&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;
docs/examples/master.tmpl&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;
~/PATH-TO-YOUR-REPO/.github/workflows/master.yml&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;
-e&lt;span class="w"&gt; &lt;/span&gt;docs/examples/envfile
&lt;/pre&gt;&lt;/div&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/div&gt;
&lt;p&gt;First, we define a Jinja2-template for the workflow (see a
&lt;a class="reference external" href="https://github.com/anna-money/workflow-tools/blob/master/docs/examples/master.tmpl"&gt;master.yml&lt;/a&gt; at line 3).  Variables to be substituted should be
marked up this&amp;nbsp;way:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="x"&gt;[[ workflow.your_variable ]]&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;When rendering the resulting file, &lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;workflow-generator&lt;/span&gt;&lt;/tt&gt; tool substitutes the markup with the value
of corresponding environment&amp;nbsp;variable:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="x"&gt;WORKFLOW_YOUR_VARIABLE&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;The environment variable can be set globally, for a single command
run, or can be read from the envfile specified by the option flag
&lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;-e&lt;/span&gt;&lt;/tt&gt; (see an &lt;a class="reference external" href="https://github.com/anna-money/workflow-tools/blob/master/docs/examples/envfile"&gt;envfile&lt;/a&gt; at line&amp;nbsp;5).&lt;/p&gt;
&lt;p&gt;Envfile comes in handy when a template uses many variables at
once. It&amp;#8217;s also easier to share variables between the templates using
envfile. The envfile has the &lt;em&gt;lowest precedence&lt;/em&gt;, it can be overridden
(line&amp;nbsp;1).&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="setting-secrets"&gt;
&lt;h3&gt;Setting&amp;nbsp;secrets&lt;/h3&gt;
&lt;p&gt;Now that we generated the workflow, let&amp;#8217;s set a GitHub secret used by the&amp;nbsp;template:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;table class="highlighttable"&gt;&lt;tr&gt;&lt;td class="linenos"&gt;&lt;div class="linenodiv"&gt;&lt;pre&gt;&lt;span class="normal"&gt;1&lt;/span&gt;
&lt;span class="normal"&gt;2&lt;/span&gt;
&lt;span class="normal"&gt;3&lt;/span&gt;
&lt;span class="normal"&gt;4&lt;/span&gt;
&lt;span class="normal"&gt;5&lt;/span&gt;
&lt;span class="normal"&gt;6&lt;/span&gt;
&lt;span class="normal"&gt;7&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;div&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;workflow_secret&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;--owner&lt;span class="o"&gt;=&lt;/span&gt;anna-money&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;--repo&lt;span class="o"&gt;=&lt;/span&gt;workflow-tools&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;--token&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;YOUR-PERSONAL-ACCESS-TOKEN&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;update&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="w"&gt;   &lt;/span&gt;--key&lt;span class="o"&gt;=&lt;/span&gt;PYPI_PUSH_USER&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="w"&gt;   &lt;/span&gt;--value&lt;span class="o"&gt;=&lt;/span&gt;YOUR_SECRET_VALUE
&lt;/pre&gt;&lt;/div&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/div&gt;
&lt;p&gt;First, we need to get a &lt;a class="reference external" href="https://github.com/settings/tokens"&gt;personal access token&lt;/a&gt; (see line 4). &lt;tt class="docutils literal"&gt;workflow_secret&lt;/tt&gt; tool has multiple commands
(see &lt;a class="reference external" href="https://workflow-tools.readthedocs.io/en/latest/workflow-tools.html"&gt;Tools autodocs&lt;/a&gt;). To set up new or update existing secret &lt;tt class="docutils literal"&gt;update&lt;/tt&gt; command is used (line 5).
The command accepts &lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;--key&lt;/span&gt;&lt;/tt&gt; and &lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;--value&lt;/span&gt;&lt;/tt&gt; options (lines 6, 7). &lt;tt class="docutils literal"&gt;workflow_secret&lt;/tt&gt; also have tool-wide
options used for each command: &lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;--owner&lt;/span&gt;&lt;/tt&gt; (GitHub user, line 2), &lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;--repo&lt;/span&gt;&lt;/tt&gt; (GitHub repository name, line 3) and
&lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;--token&lt;/span&gt;&lt;/tt&gt;.&lt;/p&gt;
&lt;p&gt;Finally, let&amp;#8217;s check what secrets are set for the&amp;nbsp;repository:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;table class="highlighttable"&gt;&lt;tr&gt;&lt;td class="linenos"&gt;&lt;div class="linenodiv"&gt;&lt;pre&gt;&lt;span class="normal"&gt;1&lt;/span&gt;
&lt;span class="normal"&gt;2&lt;/span&gt;
&lt;span class="normal"&gt;3&lt;/span&gt;
&lt;span class="normal"&gt;4&lt;/span&gt;
&lt;span class="normal"&gt;5&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;div&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;workflow_secret&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;--owner&lt;span class="o"&gt;=&lt;/span&gt;anna-money&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;--repo&lt;span class="o"&gt;=&lt;/span&gt;workflow-tools&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;--token&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;YOUR-PERSONAL-ACCESS-TOKEN&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;list
&lt;/pre&gt;&lt;/div&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="section" id="why-workflow-tools"&gt;
&lt;h2&gt;Why &lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;workflow-tools&lt;/span&gt;&lt;/tt&gt;?&lt;/h2&gt;
&lt;p&gt;It&amp;#8217;s a common practice to use templating in system administration,
especially once DevOps practices have spread widely. Some tools
&lt;a class="reference external" href="https://docs.ansible.com/ansible/latest/modules/template_module.html"&gt;Ansible template module&lt;/a&gt; have embedded templating mechanisms. It may
be of great help where the tools originally fit. But it may be an
overhead when applied to GitHub Actions&amp;nbsp;configuration.&lt;/p&gt;
&lt;p&gt;Other tools like &lt;a class="reference external" href="https://www.gnu.org/software/gettext/manual/html_node/envsubst-Invocation.html"&gt;envsubst&lt;/a&gt; have been there for a long time and
proofed being useful. But one-size-fits-all approach may affect
usability in a negative&amp;nbsp;way.&lt;/p&gt;
&lt;p&gt;The &lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;workflow-tools&lt;/span&gt;&lt;/tt&gt; approach is to make Jinja2 standalone compiler
with usability focused on the GitHub Actions&amp;nbsp;workflows.&lt;/p&gt;
&lt;p&gt;GitHub secrets setting tool from the &lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;workflow-tools&lt;/span&gt;&lt;/tt&gt; package
follows the same approach: simple &lt;span class="caps"&gt;CLI&lt;/span&gt; that does one thing and does it
well, but not too general-purpose as it could&amp;nbsp;be.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="who-should-use-workflow-tools"&gt;
&lt;h2&gt;Who should use &lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;workflow-tools&lt;/span&gt;&lt;/tt&gt;?&lt;/h2&gt;
&lt;p&gt;If you are using GitHub Actions and similar workflows are needed for
multiple repositories, then &lt;a class="reference external" href="https://github.com/anna-money/workflow-tools"&gt;workflow-tools&lt;/a&gt; package is for&amp;nbsp;you.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="project-links"&gt;
&lt;h2&gt;Project&amp;nbsp;links&lt;/h2&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;a class="reference external" href="https://github.com/anna-money/workflow-tools"&gt;GitHub&amp;nbsp;project&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="reference external" href="https://workflow-tools.readthedocs.io/"&gt;Documentation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="reference external" href="https://pypi.org/project/workflow-tools/"&gt;PyPI&amp;nbsp;Package&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
</content><category term="Blog"></category><category term="opensource"></category><category term="automation"></category><category term="microservice"></category></entry><entry><title>Python logging: why printf-style string formatting may be better than f-strings</title><link href="https://blog.pilosus.org/posts/2020/01/24/python-f-strings-in-logging/" rel="alternate"></link><published>2020-01-24T09:10:00+01:00</published><updated>2020-01-25T13:45:00+01:00</updated><author><name>Vitaly Samigullin</name></author><id>tag:blog.pilosus.org,2020-01-24:/posts/2020/01/24/python-f-strings-in-logging/</id><summary type="html">&lt;p class="first last"&gt;Python logging module provides optimizations when %-formatting is&amp;nbsp;used&lt;/p&gt;
</summary><content type="html">&lt;p&gt;Python provides more than one way to format strings: &lt;a class="reference external" href="https://docs.python.org/3/library/stdtypes.html#printf-style-string-formatting"&gt;%-formatting&lt;/a&gt;,
&lt;a class="reference external" href="https://docs.python.org/3/library/string.html#formatstrings"&gt;str.format()&lt;/a&gt;, &lt;a class="reference external" href="https://docs.python.org/3/library/string.html#formatstrings"&gt;string.Template&lt;/a&gt; and &lt;a class="reference external" href="https://www.python.org/dev/peps/pep-0498/"&gt;f-strings&lt;/a&gt;.  What format
developers use is the matter of personal aesthetic reasons rather than
anything else. Still there are use cases where good old printf-style
&lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;%-formatting&lt;/span&gt;&lt;/tt&gt; may have advantages over other formats. Python&amp;#8217;s
&lt;a class="reference external" href="https://docs.python.org/3/library/logging.html"&gt;logging&lt;/a&gt; module is one of these cases. Let&amp;#8217;s see what are the main
reasons to use &lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;%-formatting&lt;/span&gt;&lt;/tt&gt; for logging in&amp;nbsp;Python.&lt;/p&gt;
&lt;div class="section" id="python-logging-optimization-format-string-until-it-cannot-be-avoided"&gt;
&lt;h2&gt;Python logging optimization: format string until it cannot be&amp;nbsp;avoided&lt;/h2&gt;
&lt;p&gt;Python &lt;a class="reference external" href="https://docs.python.org/3/library/logging.html"&gt;logging&lt;/a&gt; methods like &lt;a class="reference external" href="https://docs.python.org/3/library/logging.html#logging.debug"&gt;debug&lt;/a&gt; get in message argument along
with optional &lt;tt class="docutils literal"&gt;args&lt;/tt&gt; and &lt;tt class="docutils literal"&gt;kwargs&lt;/tt&gt;. Args used for message
formatting. &lt;strong&gt;Formatting of these arguments is deferred until it cannot
be avoided&lt;/strong&gt;. That means the final message is not evaluated if its log
level is below the logger&amp;#8217;s log level. On the other hand, f-string is
really an expression that evaluated at runtime and it lacks logging&amp;#8217;s
optimizations. Let&amp;#8217;s see an&amp;nbsp;example:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="n"&gt;In&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;logging&lt;/span&gt;
   &lt;span class="o"&gt;...&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;logging&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;basicConfig&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;level&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;logging&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;INFO&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
   &lt;span class="o"&gt;...&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;logger&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;logging&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;getLogger&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;TestLogger&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;In&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;A&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
   &lt;span class="o"&gt;...&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;     &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="fm"&gt;__str__&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;...&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;         &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;Unnecessary slow computations are done here!&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
   &lt;span class="o"&gt;...&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;         &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="vm"&gt;__class__&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="vm"&gt;__name__&lt;/span&gt;
   &lt;span class="o"&gt;...&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
   &lt;span class="o"&gt;...&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;obj&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;A&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="n"&gt;In&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt; &lt;span class="n"&gt;logger&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;debug&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;log level below INFO with args: &lt;/span&gt;&lt;span class="si"&gt;%s&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;obj&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;In&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;logger&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;debug&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;log level below INFO with f-string: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;obj&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;Unnecessary&lt;/span&gt; &lt;span class="n"&gt;slow&lt;/span&gt; &lt;span class="n"&gt;computations&lt;/span&gt; &lt;span class="n"&gt;are&lt;/span&gt; &lt;span class="n"&gt;done&lt;/span&gt; &lt;span class="n"&gt;here&lt;/span&gt;&lt;span class="err"&gt;!&lt;/span&gt;

&lt;span class="n"&gt;In&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt; &lt;span class="n"&gt;logger&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;debug&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;log level below INFO with args: &lt;/span&gt;&lt;span class="si"&gt;%s&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt; &lt;span class="o"&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;Unnecessary&lt;/span&gt; &lt;span class="n"&gt;slow&lt;/span&gt; &lt;span class="n"&gt;computations&lt;/span&gt; &lt;span class="n"&gt;are&lt;/span&gt; &lt;span class="n"&gt;done&lt;/span&gt; &lt;span class="n"&gt;here&lt;/span&gt;&lt;span class="err"&gt;!&lt;/span&gt;

&lt;span class="n"&gt;In&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;logger&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;info&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;log level INFO with args: &lt;/span&gt;&lt;span class="si"&gt;%s&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;obj&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;Unnecessary&lt;/span&gt; &lt;span class="n"&gt;slow&lt;/span&gt; &lt;span class="n"&gt;computations&lt;/span&gt; &lt;span class="n"&gt;are&lt;/span&gt; &lt;span class="n"&gt;done&lt;/span&gt; &lt;span class="n"&gt;here&lt;/span&gt;&lt;span class="err"&gt;!&lt;/span&gt;
&lt;span class="n"&gt;INFO&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;TestLogger&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;log&lt;/span&gt; &lt;span class="n"&gt;level&lt;/span&gt; &lt;span class="n"&gt;INFO&lt;/span&gt; &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;A&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;As you can see in &lt;strong&gt;step 3&lt;/strong&gt; a message doesn&amp;#8217;t get logged as the root
logger&amp;#8217;s level is higher than &lt;tt class="docutils literal"&gt;debug&lt;/tt&gt;. Logging module&amp;#8217;s inner
optimization makes sure that the message is not formatted in this
case. Compare this to &lt;strong&gt;step 6&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;In &lt;strong&gt;step 4&lt;/strong&gt; the f-string is also not logged because of the low log
level. But it&amp;#8217;s formatted anyway. That&amp;#8217;s why you see &lt;tt class="docutils literal"&gt;obj.__str__&lt;/tt&gt;
called.&lt;/p&gt;
&lt;p&gt;Please note, that logging optimization doesn&amp;#8217;t work for &lt;tt class="docutils literal"&gt;&amp;quot;format
string&amp;quot; % values&lt;/tt&gt; format. You can see that in &lt;strong&gt;step 5&lt;/strong&gt;. The magic
applies for &lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;%-format&lt;/span&gt;&lt;/tt&gt; message &lt;strong&gt;if and only if the values for
message formatting are passed in as logger method&amp;#8217;s&amp;nbsp;arguments!&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;So &lt;strong&gt;step 3&lt;/strong&gt; shows the optimized way to do message formatting in&amp;nbsp;logging.&lt;/p&gt;
&lt;p&gt;By the way, &lt;a class="reference external" href="https://docs.python.org/3/library/logging.html"&gt;logging&lt;/a&gt; module has other nice &lt;a class="reference external" href="https://docs.python.org/3/howto/logging.html#optimization"&gt;optimizations&lt;/a&gt; that worth&amp;nbsp;learning.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="sentry-integration-with-logging"&gt;
&lt;h2&gt;Sentry integration with&amp;nbsp;logging&lt;/h2&gt;
&lt;p&gt;Sentry is a popular error tracking solution. Sentry &lt;a class="reference external" href="https://docs.sentry.io/platforms/python/logging/"&gt;integrates&lt;/a&gt; with
the Python logging module. &lt;a class="reference external" href="https://docs.sentry.io/data-management/event-grouping/"&gt;Error aggregation&lt;/a&gt; is another great
feature of Sentry. Sentry looks at the event&amp;#8217;s stacktrace, exception,
or message and group it with existing ones if they are the same. If
you get a hundred of exceptions that are all the same they get grouped
into one with nice&amp;nbsp;statistics.&lt;/p&gt;
&lt;p&gt;When integrating Sentry with Python logging, it&amp;#8217;s important to use
&lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;%-formatting&lt;/span&gt;&lt;/tt&gt;, so that Sentry can group your messages. As an
example, let&amp;#8217;s log failed attempts to retrieve an &lt;span class="caps"&gt;URL&lt;/span&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="n"&gt;logger&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Failed to retrieve URL &lt;/span&gt;&lt;span class="si"&gt;%s&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;url&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;With printf-style formatting your messages will be grouped together no
matter what value &lt;tt class="docutils literal"&gt;url&lt;/tt&gt; variable holds. But in this&amp;nbsp;case:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="n"&gt;logger&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Failed to retrieve URL &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;url&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;/pre&gt;&lt;/div&gt;
&lt;p&gt;you get a separate event in Sentry for every single unique &lt;span class="caps"&gt;URL&lt;/span&gt;. If you
get 1000 unique &lt;span class="caps"&gt;URL&lt;/span&gt;, your Sentry dashboard might get&amp;nbsp;messy.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="conclusions"&gt;
&lt;h2&gt;Conclusions&lt;/h2&gt;
&lt;p&gt;Although Python is not Perl, there&amp;#8217;s still more than one way to do
it. Which string formatting style to use is an open question. Your
project may not use Sentry, or you may prefer f-strings for
readability reasons. You may see deferred formatting in logging for
printf-style strings as a futile micro-optimization that won&amp;#8217;t improve
your app performance. But still, there may be cases where using an
oldie but goldie &lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;%-formatting&lt;/span&gt;&lt;/tt&gt; is beneficial, even at price of
string formatting&amp;nbsp;inconsistency.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="updates"&gt;
&lt;h2&gt;Updates&lt;/h2&gt;
&lt;p&gt;&lt;em&gt;2020-01-24 21:35&lt;/em&gt;. Many thanks to &lt;a class="reference external" href="https://blog.pilosus.org/posts/2020/01/24/python-f-strings-in-logging/#comment-4769289809"&gt;symmitchry&lt;/a&gt; who stressed out the
importance of passing values for a message to be formatted as
&lt;em&gt;arguments to logger&amp;#8217;s method&lt;/em&gt; (e.g. args to
&lt;tt class="docutils literal"&gt;logger.debug()&lt;/tt&gt;). Probably, that wasn&amp;#8217;t quite clear in the first
edition of the post. I&amp;#8217;ve made this point&amp;nbsp;explicit.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;2020-01-25 13:45&lt;/em&gt;. As a redditor pointed out in the &lt;a class="reference external" href="https://old.reddit.com/r/Python/comments/etcnq2/python_logging_why_printfstyle_string_formatting/ffgmvn2/"&gt;post
discussion&lt;/a&gt; a Python linter &lt;tt class="docutils literal"&gt;pylint&lt;/tt&gt; has a rule &lt;a class="reference external" href="https://docs.pylint.org/en/1.6.0/features.html"&gt;W1202&lt;/a&gt; that warns
you when &lt;tt class="docutils literal"&gt;str.format&lt;/tt&gt; is used in logging. It&amp;#8217;s worth to mention a
rule W1201 too, which is triggered for the cases like in step 5 (see
the code snippet&amp;nbsp;above).&lt;/p&gt;
&lt;/div&gt;
</content><category term="Blog"></category><category term="python"></category><category term="logging"></category><category term="f-string"></category></entry><entry><title>Python third-party tools configuration: pyproject.toml vs setup.cfg</title><link href="https://blog.pilosus.org/posts/2019/12/26/python-third-party-tools-configuration/" rel="alternate"></link><published>2019-12-26T12:10:00+01:00</published><updated>2019-12-26T12:10:00+01:00</updated><author><name>Vitaly Samigullin</name></author><id>tag:blog.pilosus.org,2019-12-26:/posts/2019/12/26/python-third-party-tools-configuration/</id><summary type="html">&lt;p class="first last"&gt;Universal config file for Python third-party tools in 2020? It&amp;#8217;s a&amp;nbsp;myth&lt;/p&gt;
</summary><content type="html">&lt;p&gt;A couple of years ago two PEPs have been proposed to fix some package
building interface problems. Basically, in order to build a Python
package we need to checkout source of the project, then install a
build system and finally run it. &lt;a class="reference external" href="https://www.python.org/dev/peps/pep-0517/"&gt;&lt;span class="caps"&gt;PEP&lt;/span&gt; 517&lt;/a&gt; defines the last step,
including how to get the build system dynamically specify more
dependencies that the build system requires to perform its job. The
&lt;a class="reference external" href="https://www.python.org/dev/peps/pep-0518/"&gt;&lt;span class="caps"&gt;PEP&lt;/span&gt; 518&lt;/a&gt; defines the second step, including a new configuration file
&lt;tt class="docutils literal"&gt;pyproject.toml&lt;/tt&gt; to be used by the building&amp;nbsp;system.&lt;/p&gt;
&lt;p&gt;As &lt;tt class="docutils literal"&gt;setuptools&lt;/tt&gt; have been dominating in Python packaging field for a
long time, many third-party tools adopted its configuration file
&lt;a class="reference external" href="https://setuptools.readthedocs.io/en/latest/setuptools.html#configuring-setup-using-setup-cfg-files"&gt;setup.cfg&lt;/a&gt; too. Libraries for testing, static code analysis tools
like linters or type checkers along with their custom configuration
files often allow to use &lt;tt class="docutils literal"&gt;setup.cfg&lt;/tt&gt;. After all, it&amp;#8217;s &lt;span class="caps"&gt;INI&lt;/span&gt;-style
configuration format, easy for humans to read and edit it, with no
standard behind it&amp;nbsp;though.&lt;/p&gt;
&lt;p&gt;Moving tool settings to the &lt;tt class="docutils literal"&gt;setup.cfg&lt;/tt&gt; as opposed to supporing a
bunch of custom config files seems to be common practice. But now that
we have the new PEPs, should we abandon &lt;tt class="docutils literal"&gt;setup.cfg&lt;/tt&gt; in favour of the
brand new &lt;tt class="docutils literal"&gt;pyproject.toml&lt;/tt&gt;? Let&amp;#8217;s&amp;nbsp;see.&lt;/p&gt;
&lt;div class="section" id="popular-python-tools-and-their-configs"&gt;
&lt;h2&gt;6 popular Python tools and their&amp;nbsp;configs&lt;/h2&gt;
&lt;p&gt;Here are the 6 Python tools that I use daily both at my job and for
the personal projects: &lt;a class="reference external" href="https://docs.pytest.org/en/latest/"&gt;pytest&lt;/a&gt; for testing, &lt;a class="reference external" href="https://pytest-cov.readthedocs.io/en/latest/"&gt;pytest-cov&lt;/a&gt; as a
pytest plugin for &lt;a class="reference external" href="https://coverage.readthedocs.io/en/coverage-5.0/"&gt;coverage&lt;/a&gt; tool that calculates code coverage,
&lt;a class="reference external" href="http://mypy-lang.org/"&gt;mypy&lt;/a&gt; as a type checker, &lt;a class="reference external" href="http://flake8.pycqa.org/en/latest/"&gt;flake8&lt;/a&gt; as a code style checker, &lt;a class="reference external" href="https://black.readthedocs.io/en/stable/"&gt;black&lt;/a&gt;
for code formatting, and &lt;a class="reference external" href="https://timothycrosley.github.io/isort/"&gt;isort&lt;/a&gt; for sorting&amp;nbsp;imports.&lt;/p&gt;
&lt;p&gt;In a table below you&amp;#8217;ll find these tools along with their versions at
the time of writing, popular and (arguably) universal config files as
well as their &lt;em&gt;native&lt;/em&gt; configuration file&amp;nbsp;formats.&lt;/p&gt;
&lt;table border="1" class="docutils"&gt;
&lt;colgroup&gt;
&lt;col width="14%" /&gt;
&lt;col width="8%" /&gt;
&lt;col width="14%" /&gt;
&lt;col width="22%" /&gt;
&lt;col width="12%" /&gt;
&lt;col width="29%" /&gt;
&lt;/colgroup&gt;
&lt;thead valign="bottom"&gt;
&lt;tr&gt;&lt;th class="head"&gt;tool&lt;/th&gt;
&lt;th class="head"&gt;version&lt;/th&gt;
&lt;th class="head"&gt;&lt;tt class="docutils literal"&gt;setup.cfg&lt;/tt&gt;&lt;/th&gt;
&lt;th class="head"&gt;&lt;tt class="docutils literal"&gt;pyproject.toml&lt;/tt&gt;&lt;/th&gt;
&lt;th class="head"&gt;&lt;tt class="docutils literal"&gt;tox.ini&lt;/tt&gt;&lt;/th&gt;
&lt;th class="head"&gt;custom format&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody valign="top"&gt;
&lt;tr&gt;&lt;td&gt;&lt;a class="reference external" href="https://docs.pytest.org/en/latest/"&gt;pytest&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;5.3.2&lt;/td&gt;
&lt;td&gt;yes&lt;/td&gt;
&lt;td&gt;no (&lt;span class="caps"&gt;WIP&lt;/span&gt;)&lt;/td&gt;
&lt;td&gt;yes&lt;/td&gt;
&lt;td&gt;&lt;tt class="docutils literal"&gt;pytest.ini&lt;/tt&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;a class="reference external" href="https://pytest-cov.readthedocs.io/en/latest/"&gt;pytest-cov&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;2.8.1&lt;/td&gt;
&lt;td&gt;yes&lt;/td&gt;
&lt;td&gt;no&lt;/td&gt;
&lt;td&gt;yes&lt;/td&gt;
&lt;td&gt;&lt;tt class="docutils literal"&gt;.coveragerc&lt;/tt&gt;, pytest conf&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;a class="reference external" href="http://mypy-lang.org/"&gt;mypy&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;0.750.0&lt;/td&gt;
&lt;td&gt;yes&lt;/td&gt;
&lt;td&gt;no (won&amp;#8217;t fix?)&lt;/td&gt;
&lt;td&gt;no&lt;/td&gt;
&lt;td&gt;&lt;tt class="docutils literal"&gt;mypy.ini&lt;/tt&gt; (preferred)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;a class="reference external" href="http://flake8.pycqa.org/en/latest/"&gt;flake8&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;3.7.9&lt;/td&gt;
&lt;td&gt;yes&lt;/td&gt;
&lt;td&gt;yes (unofficially)&lt;/td&gt;
&lt;td&gt;yes&lt;/td&gt;
&lt;td&gt;&lt;tt class="docutils literal"&gt;.flake8&lt;/tt&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;a class="reference external" href="https://black.readthedocs.io/en/stable/"&gt;black&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;19.10b0&lt;/td&gt;
&lt;td&gt;no&lt;/td&gt;
&lt;td&gt;yes&lt;/td&gt;
&lt;td&gt;no&lt;/td&gt;
&lt;td&gt;&lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;--config&lt;/span&gt;&lt;/tt&gt; any &lt;span class="caps"&gt;TOML&lt;/span&gt; file&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;a class="reference external" href="https://timothycrosley.github.io/isort/"&gt;isort&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;4.3.21&lt;/td&gt;
&lt;td&gt;yes&lt;/td&gt;
&lt;td&gt;yes&lt;/td&gt;
&lt;td&gt;yes&lt;/td&gt;
&lt;td&gt;&lt;tt class="docutils literal"&gt;.isort.cfg&lt;/tt&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;If there&amp;#8217;s something to take in from the table, then it&amp;#8217;s the
following. First, there&amp;#8217;s no consensus in the Python community over
the usage of &lt;tt class="docutils literal"&gt;pyproject.toml&lt;/tt&gt;. The most notable discussion involves
Guido van Rossum in &lt;a class="reference external" href="https://github.com/python/mypy/issues/5205#issuecomment-410281336"&gt;mypy issue&lt;/a&gt; on the GitHub. &lt;span class="caps"&gt;PEP&lt;/span&gt; 518 is all about
configuration for package building systems like &lt;tt class="docutils literal"&gt;distutils&lt;/tt&gt;,
&lt;tt class="docutils literal"&gt;setuptools&lt;/tt&gt;, &lt;a class="reference external" href="https://github.com/takluyver/flit"&gt;flit&lt;/a&gt;, &lt;a class="reference external" href="https://cournape.github.io/Bento/"&gt;bento&lt;/a&gt;. It says nothing about configuration
for tools outside of the package builing context. But what if package
building process involves type checking or code formatting? Also,
&lt;tt class="docutils literal"&gt;setup.cfg&lt;/tt&gt; is a part of the &lt;tt class="docutils literal"&gt;setuptools&lt;/tt&gt; which is a building
system too. Still, people use it for third-party tools configuration
outside of the package building context, e.g. for imports sorting
checks in their &lt;span class="caps"&gt;CI&lt;/span&gt; pipelines. All in all, mypy has set a precedent for
not supporting &lt;tt class="docutils literal"&gt;pyproject.toml&lt;/tt&gt; on purpose, so that some other tools
developers hesitate either to openly support the new config file like
flake8 does, or just postpone implementation until a consensus is
emerged in the Python&amp;nbsp;community.&lt;/p&gt;
&lt;div class="figure"&gt;
&lt;img alt="How standards prolifirate" class="align-right" src="https://imgs.xkcd.com/comics/standards.png" /&gt;
&lt;/div&gt;
&lt;p&gt;Secondly, there&amp;#8217;s always a &lt;a class="reference external" href="https://black.readthedocs.io/en/stable/"&gt;black&lt;/a&gt; sheep of the family. Being a great
formatting tool that eventually has ended so many holy wars about code
formatting, black doesn&amp;#8217;t support anything except for &lt;span class="caps"&gt;TOML&lt;/span&gt; config
files. Even if you are more inclined towards ol&amp;#8217; reliable
&lt;tt class="docutils literal"&gt;setup.cfg&lt;/tt&gt;, you still has a black sheep in your
setup.cfg-compatible&amp;nbsp;flock.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="conclusion"&gt;
&lt;h2&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;&lt;a class="reference external" href="https://github.com/toml-lang/toml"&gt;&lt;span class="caps"&gt;TOML&lt;/span&gt;&lt;/a&gt; is a great human-readable, machine-editable, strong typed,
well-defined configuration format. Having Python- and
setuptools-independent configuration file &lt;tt class="docutils literal"&gt;pyproject.toml&lt;/tt&gt; is a
great idea. Still, as the usage of &lt;tt class="docutils literal"&gt;pyproject.toml&lt;/tt&gt; for non-package
building context is not defined, we are in the &lt;a class="reference external" href="https://xkcd.com/927/"&gt;xkcd&lt;/a&gt;
how-standards-proliferate situation. Until consensus is finally set,
it&amp;#8217;s better off either use &lt;tt class="docutils literal"&gt;setup.cfg&lt;/tt&gt; with an annoying exception
for &lt;tt class="docutils literal"&gt;black&lt;/tt&gt;, or stick to the tools custom config formats and&amp;nbsp;files.&lt;/p&gt;
&lt;/div&gt;
</content><category term="Blog"></category><category term="python"></category><category term="tool"></category><category term="standard"></category></entry><entry><title>Python interview questions and answers</title><link href="https://blog.pilosus.org/posts/2019/12/15/python-interview-questions/" rel="alternate"></link><published>2019-12-15T14:00:00+01:00</published><updated>2019-12-15T14:00:00+01:00</updated><author><name>Vitaly Samigullin</name></author><id>tag:blog.pilosus.org,2019-12-15:/posts/2019/12/15/python-interview-questions/</id><summary type="html">&lt;p class="first last"&gt;Python questions and answers for a technical&amp;nbsp;interview&lt;/p&gt;
</summary><content type="html">&lt;p&gt;Although the questions you find in this blog post are universal, they
are crafted for a technical interview for a web developer&amp;nbsp;position.&lt;/p&gt;
&lt;p&gt;The questions have been tested in real life: I used them in more
than 35 interview sessions. They allowed my team and me to hire three
brilliant developers: a junior developer (ex-Yandex), a middle
developer (ex-Yandex), and a senior&amp;nbsp;one.&lt;/p&gt;
&lt;p&gt;The questions have also been reviewed by my peers and got positive
feedback. Some of my colleagues use these questions in their technical
interviews. I also use the questions when consulting people about
preparing for the interview or getting their first software&amp;nbsp;job.&lt;/p&gt;
&lt;p&gt;The questions are not unique. I&amp;#8217;m not revealing any professional
secrets by posting them. The single most valuable thing about these
questions is the principle they are combined in an interview
session. You can also use these questions to come up with your
questions for the Python interview as an interviewer. Also, you can
use them to prepare for an interview as an&amp;nbsp;interviewee.&lt;/p&gt;
&lt;div class="contents topic" id="table-of-contents"&gt;
&lt;p class="topic-title"&gt;Table of&amp;nbsp;Contents&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;a class="reference internal" href="#introduction" id="toc-entry-1"&gt;Introduction&lt;/a&gt;&lt;ul&gt;
&lt;li&gt;&lt;a class="reference internal" href="#premises" id="toc-entry-2"&gt;Premises&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="reference internal" href="#candidates-cvs" id="toc-entry-3"&gt;Candidates &lt;span class="amp"&gt;&amp;amp;&lt;/span&gt;&amp;nbsp;CVs&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="reference internal" href="#non-technical-questions" id="toc-entry-4"&gt;Non-technical&amp;nbsp;questions&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a class="reference internal" href="#python-questions" id="toc-entry-5"&gt;Python questions&lt;/a&gt;&lt;ul&gt;
&lt;li&gt;&lt;a class="reference internal" href="#how-language-evolves" id="toc-entry-6"&gt;How language&amp;nbsp;evolves&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="reference internal" href="#basic-types-and-data-structures-in-python" id="toc-entry-7"&gt;Basic types and data structures in&amp;nbsp;Python&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="reference internal" href="#function-default-arguments" id="toc-entry-8"&gt;Function default&amp;nbsp;arguments&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="reference internal" href="#what-s-decorator" id="toc-entry-9"&gt;What&amp;#8217;s&amp;nbsp;decorator?&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="reference internal" href="#decorator-that-prints-function-execution-time" id="toc-entry-10"&gt;Decorator that prints function execution&amp;nbsp;time&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="reference internal" href="#using-decorator" id="toc-entry-11"&gt;Using&amp;nbsp;decorator&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="reference internal" href="#decorated-function-s-name" id="toc-entry-12"&gt;Decorated function&amp;#8217;s&amp;nbsp;name&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="reference internal" href="#decorators-for-generators" id="toc-entry-13"&gt;Decorators for&amp;nbsp;generators&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="reference internal" href="#decorator-with-arguments" id="toc-entry-14"&gt;Decorator with&amp;nbsp;arguments&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="reference internal" href="#class-variables" id="toc-entry-15"&gt;Class&amp;nbsp;variables&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="reference internal" href="#list-slices" id="toc-entry-16"&gt;List&amp;nbsp;slices&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="reference internal" href="#what-s-list-dict-comprehension" id="toc-entry-17"&gt;What&amp;#8217;s list/dict&amp;nbsp;comprehension?&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="reference internal" href="#generator-expressions" id="toc-entry-18"&gt;Generator&amp;nbsp;expressions&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="reference internal" href="#what-useful-functions-from-functools-do-you-know" id="toc-entry-19"&gt;What useful functions from functools do you&amp;nbsp;know?&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="reference internal" href="#asyncio-multithreading-multiprocessing" id="toc-entry-20"&gt;Asyncio, multithreading,&amp;nbsp;multiprocessing&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="reference internal" href="#python-ecosystem" id="toc-entry-21"&gt;Python&amp;nbsp;ecosystem&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a class="reference internal" href="#computer-science-questions" id="toc-entry-22"&gt;Computer science questions&lt;/a&gt;&lt;ul&gt;
&lt;li&gt;&lt;a class="reference internal" href="#time-complexity" id="toc-entry-23"&gt;Time&amp;nbsp;complexity&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="reference internal" href="#balanced-brackets-in-a-lisp-program-problem" id="toc-entry-24"&gt;Balanced brackets in a Lisp-program&amp;nbsp;problem&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a class="reference internal" href="#databases" id="toc-entry-25"&gt;Databases&lt;/a&gt;&lt;ul&gt;
&lt;li&gt;&lt;a class="reference internal" href="#why-rdbms" id="toc-entry-26"&gt;Why &lt;span class="caps"&gt;RDBMS&lt;/span&gt;?&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="reference internal" href="#mysql-postgresql-queries" id="toc-entry-27"&gt;MySQL/PostgreSQL&amp;nbsp;queries&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="reference internal" href="#nosql-overview" id="toc-entry-28"&gt;NoSQL&amp;nbsp;Overview&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="reference internal" href="#redis-overview" id="toc-entry-29"&gt;Redis&amp;nbsp;Overview&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a class="reference internal" href="#testing" id="toc-entry-30"&gt;Testing&lt;/a&gt;&lt;ul&gt;
&lt;li&gt;&lt;a class="reference internal" href="#unit-testing-overview" id="toc-entry-31"&gt;Unit-testing&amp;nbsp;overview&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="reference internal" href="#how-do-you-test-a-function-that-changes-data-in-mysql" id="toc-entry-32"&gt;How do you test a function that changes data in&amp;nbsp;MySQL?&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a class="reference internal" href="#devops-sysadmin-skills" id="toc-entry-33"&gt;DevOps/SysAdmin skills&lt;/a&gt;&lt;ul&gt;
&lt;li&gt;&lt;a class="reference internal" href="#broken-response-from-google-com" id="toc-entry-34"&gt;Broken response from&amp;nbsp;google.com&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="reference internal" href="#ip-addresses-top-from-nginx-log" id="toc-entry-35"&gt;&lt;span class="caps"&gt;IP&lt;/span&gt; addresses top from Nginx&amp;nbsp;log&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="reference internal" href="#docker" id="toc-entry-36"&gt;Docker&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a class="reference internal" href="#system-design-and-architecture" id="toc-entry-37"&gt;System Design and Architecture&lt;/a&gt;&lt;ul&gt;
&lt;li&gt;&lt;a class="reference internal" href="#system-design-of-pastebin-like-service" id="toc-entry-38"&gt;System design of pastebin-like service&lt;/a&gt;&lt;ul&gt;
&lt;li&gt;&lt;a class="reference internal" href="#user-story-business-description" id="toc-entry-39"&gt;User story (business&amp;nbsp;description)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="reference internal" href="#assume" id="toc-entry-40"&gt;Assume&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="reference internal" href="#hints" id="toc-entry-41"&gt;Hints&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="reference internal" href="#capacity-planning-for-3-years" id="toc-entry-42"&gt;Capacity planning for 3&amp;nbsp;years&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="reference internal" href="#service-architecture" id="toc-entry-43"&gt;Service&amp;nbsp;architecture&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="reference internal" href="#write-api" id="toc-entry-44"&gt;Write &lt;span class="caps"&gt;API&lt;/span&gt;&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="reference internal" href="#unique-url-path-generation" id="toc-entry-45"&gt;Unique &lt;span class="caps"&gt;URL&lt;/span&gt; path&amp;nbsp;generation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="reference internal" href="#request-write-api" id="toc-entry-46"&gt;Request Write &lt;span class="caps"&gt;API&lt;/span&gt;&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="reference internal" href="#write-api-response" id="toc-entry-47"&gt;Write &lt;span class="caps"&gt;API&lt;/span&gt;&amp;nbsp;Response&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="reference internal" href="#request-read-api" id="toc-entry-48"&gt;Request Read &lt;span class="caps"&gt;API&lt;/span&gt;&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="reference internal" href="#read-api-reponse" id="toc-entry-49"&gt;Read &lt;span class="caps"&gt;API&lt;/span&gt;&amp;nbsp;Reponse&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="reference internal" href="#statistics" id="toc-entry-50"&gt;Statistics&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a class="reference internal" href="#sources-and-useful-links" id="toc-entry-51"&gt;Sources and useful&amp;nbsp;links&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class="section" id="introduction"&gt;
&lt;h2&gt;&lt;a class="toc-backref" href="#toc-entry-1"&gt;Introduction&lt;/a&gt;&lt;/h2&gt;
&lt;div class="section" id="premises"&gt;
&lt;h3&gt;&lt;a class="toc-backref" href="#toc-entry-2"&gt;Premises&lt;/a&gt;&lt;/h3&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;Software development is no longer an art, nor a lifelong quest, but
rather an everyday office job, though highly-skilled one. That&amp;#8217;s why
a candidate&amp;#8217;s abilities for teamwork, coping with chores are super
important. But pure computer science, algorithms are in the
background, whether you like it or&amp;nbsp;not.&lt;/li&gt;
&lt;li&gt;An algorithm-based technical interview is an analogue of &lt;span class="caps"&gt;IQ&lt;/span&gt;-tests
that have been invented in the early &lt;span class="caps"&gt;XX&lt;/span&gt; century in the &lt;span class="caps"&gt;US&lt;/span&gt; for a
quick military recruits categorization. &lt;span class="caps"&gt;IQ&lt;/span&gt; provides some insights
about human intelligence but also has its flaws. Demonstrated
algorithm skill also provides an insight about candidate&amp;#8217;s
intelligence but also has its flaws. Algorithm-based technical
interviews are used by corporations to quickly process a huge queue
of candidates, hire the ones they need and evaluate their grades and
salary. Corporations everybody wants to work for can afford a false
negative in hiring, i.e. not hiring the right person, as many
candidates compete to be hired by these companies. If you are not
one of these companies, then &lt;strong&gt;you&lt;/strong&gt; compete with other companies
for the candidates. So don&amp;#8217;t pretend to be a Google if you are not&amp;nbsp;Google.&lt;/li&gt;
&lt;li&gt;Human intelligence neither is an &amp;#8220;on/off&amp;#8221;, nor a range. Rather,
intelligence is a swiss army knife, a multitool. It may contain an
excellent sharp blade for everyday chores, it may also have a
screwdriver that merely works, and it may also have a corkscrew that
you actually don&amp;#8217;t need at all. Can we evaluate an overall score for
such a multitool? Sure. But why would we? Unless you are Google,
find a multitool with the blades and bits that solve your exact
problems, not the abstract&amp;nbsp;ones.&lt;/li&gt;
&lt;li&gt;You need an interview to find out three things: a) why the candidate
has left (or are ready to leave) his/her previous (current) job; b)
what is he/she looking for at the new company; c) what value can
he/she bring to your&amp;nbsp;team?&lt;/li&gt;
&lt;li&gt;Hiring error can be mitigated by the probationary period. Take it
seriously. Set some goals (tasks) for probation. The same for the
candidate: during probation decide if the company is the right
choice. But hiring error is still an error that costs your company
money and time. You should do your best to avoid this error. The
same for the candidate. That&amp;#8217;s why you never tell lies about your
project and team in the interview. Still, there&amp;#8217;s nothing wrong with
presenting your project positively. Actually, it&amp;#8217;s your duty if you
want to hire someone in a highly competitive job&amp;nbsp;market.&lt;/li&gt;
&lt;li&gt;There are no two identical interviews even for the same grade. The
technical interview is always improvisation. It&amp;#8217;s an attempt to
reveal a candidate&amp;#8217;s boundaries of competence, strengths and
weaknesses in a short period of&amp;nbsp;time.&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class="section" id="candidates-cvs"&gt;
&lt;h3&gt;&lt;a class="toc-backref" href="#toc-entry-3"&gt;Candidates &lt;span class="amp"&gt;&amp;amp;&lt;/span&gt;&amp;nbsp;CVs&lt;/a&gt;&lt;/h3&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;Not everybody is a &lt;span class="caps"&gt;CV&lt;/span&gt;-ninja.&amp;nbsp;Really&lt;/li&gt;
&lt;li&gt;Too high salary expectations isn&amp;#8217;t a reason to reject a
candidate &lt;span class="caps"&gt;CV&lt;/span&gt;. You always can ask questions that will either lower
the expectations or prove you are interviewing a&amp;nbsp;genius.&lt;/li&gt;
&lt;li&gt;(sarcasm mode on) Of course, an interviewer always knows best!
(sarcasm mode off) But there are no solid grounds for reject
candidate CVs with grammar mistakes or expressions of inexperience or
quirks in their CVs. Asking inadequately complex questions is also
not a good idea. The interview is not about you, it&amp;#8217;s about the&amp;nbsp;candidate.&lt;/li&gt;
&lt;li&gt;Don&amp;#8217;t afraid of people with some irrelevant background,
e.g. designers, analysts, etc., who changed his/her profession to a
technical one. Be honest, your boring &lt;span class="caps"&gt;REST&lt;/span&gt; &lt;span class="caps"&gt;API&lt;/span&gt; doesn&amp;#8217;t require a
university graduate&amp;nbsp;program.&lt;/li&gt;
&lt;li&gt;A candidate&amp;#8217;s technology stack may not be 100% yours. Still, he/she
may be a good match for your team. Should you hire someone with 3+
years of experience with Flask but no Django experience for your
Django project? Probably you&amp;nbsp;should.&lt;/li&gt;
&lt;li&gt;The only way to hire someone quickly is to do as many interviews as
you&amp;nbsp;can.&lt;/li&gt;
&lt;li&gt;Average interview time for an interviewer is about 2.5 hours (skim
over the CVs, prepare for the interview, the interview itself,
discussion with an &lt;span class="caps"&gt;HR&lt;/span&gt; specialist and a &lt;span class="caps"&gt;CTO&lt;/span&gt;)&lt;/li&gt;
&lt;li&gt;Don&amp;#8217;t make a technical interview for too long. 2 hour is enough. 1.5
hours is&amp;nbsp;optimal.&lt;/li&gt;
&lt;li&gt;Three interviews a week is a good pace. More than 3 is too much for
the interviewer and his/her team. Less than 3 makes your search&amp;nbsp;longer.&lt;/li&gt;
&lt;li&gt;Average time for hiring a Python developer depends on the project,
expected experience and your offer conditions. As a rule of thumb, 3
months from submitting an open position to &lt;span class="caps"&gt;HR&lt;/span&gt; department to the
first working day is&amp;nbsp;okay.&lt;/li&gt;
&lt;li&gt;Not every software engineer is an introvert. There are people with
outstanding &amp;#8220;selling&amp;#8221; skills who are prepared for typical interview
questions, present themselves, but still lack solid knowledge of
software development. Don&amp;#8217;t let them trick you! Ask whiteboard
programming&amp;nbsp;questions!&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class="section" id="non-technical-questions"&gt;
&lt;h3&gt;&lt;a class="toc-backref" href="#toc-entry-4"&gt;Non-technical&amp;nbsp;questions&lt;/a&gt;&lt;/h3&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;What have you done in your previous/current&amp;nbsp;project?&lt;/li&gt;
&lt;li&gt;Describe your team line-up (frontend/backend developers, &lt;span class="caps"&gt;QA&lt;/span&gt;
engineers, &lt;span class="caps"&gt;SRE&lt;/span&gt;/DevOps/SysAdmins, product/project managers,&amp;nbsp;etc)?&lt;/li&gt;
&lt;li&gt;How planning, code review, etc. are organized in your project? What
you liked and disliked about&amp;nbsp;it?&lt;/li&gt;
&lt;li&gt;Why did you decice to leave (have already left) the&amp;nbsp;project?&lt;/li&gt;
&lt;li&gt;What do you expect from the new project (things to develop, processes,&amp;nbsp;etc)?&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="section" id="python-questions"&gt;
&lt;h2&gt;&lt;a class="toc-backref" href="#toc-entry-5"&gt;Python&amp;nbsp;questions&lt;/a&gt;&lt;/h2&gt;
&lt;div class="section" id="how-language-evolves"&gt;
&lt;h3&gt;&lt;a class="toc-backref" href="#toc-entry-6"&gt;How language&amp;nbsp;evolves&lt;/a&gt;&lt;/h3&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;What&amp;#8217;s new in the latest Python version? (&lt;a class="reference external" href="https://docs.python.org/3/whatsnew/3.8.html"&gt;Python 3.8&lt;/a&gt; at the moment of&amp;nbsp;writing)&lt;/li&gt;
&lt;li&gt;How Python 3+ differs from Python 2.7? (&lt;a class="reference external" href="https://docs.python.org/3.0/whatsnew/3.0.html"&gt;What&amp;#8217;s new in Python 3&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;What new syntax has been introduced since Python 2.7? (minimal expectations: asyncio, type&amp;nbsp;hinting)&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class="section" id="basic-types-and-data-structures-in-python"&gt;
&lt;h3&gt;&lt;a class="toc-backref" href="#toc-entry-7"&gt;Basic types and data structures in&amp;nbsp;Python&lt;/a&gt;&lt;/h3&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;What are the basic types in&amp;nbsp;Python?&lt;/li&gt;
&lt;li&gt;What are the basic data structures in&amp;nbsp;Python?&lt;/li&gt;
&lt;li&gt;All data types and structures in Python can be divided into two groups. Name these groups (mutable,&amp;nbsp;immutable)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Problem. What lines of the following code raise exceptions? What&amp;nbsp;expectations?&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="n"&gt;d&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;
&lt;span class="n"&gt;d&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;a&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
&lt;span class="n"&gt;d&lt;/span&gt;&lt;span class="p"&gt;[(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;
&lt;span class="n"&gt;d&lt;/span&gt;&lt;span class="p"&gt;[[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;]]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;       &lt;span class="c1"&gt;# TypeError: unhashable type: &amp;#39;list&amp;#39;&lt;/span&gt;
&lt;span class="n"&gt;d&lt;/span&gt;&lt;span class="p"&gt;[(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,[&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;])]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt; &lt;span class="c1"&gt;# TypeError: unhashable type: &amp;#39;list&amp;#39;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;div class="section" id="function-default-arguments"&gt;
&lt;h3&gt;&lt;a class="toc-backref" href="#toc-entry-8"&gt;Function default&amp;nbsp;arguments&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Problem. We have got a&amp;nbsp;function:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;fun_default&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;val&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;lst&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;list&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[])&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;list&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;lst&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;val&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;lst&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;What the following print statements will print after the block of assignments is&amp;nbsp;executed?&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="n"&gt;l1&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;fun_default&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;l2&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;fun_default&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[])&lt;/span&gt;
&lt;span class="n"&gt;l3&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;fun_default&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;l1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;# [1, 3]&lt;/span&gt;
&lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;l2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;# [2]&lt;/span&gt;
&lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;l3&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;# [1, 3]&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;How would you fix the function so that print statements print [1], [2], [3]&amp;nbsp;respectively?&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;fun_default_fixed&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;val&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;lst&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;list&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;None&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;list&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;lst&lt;/span&gt; &lt;span class="ow"&gt;is&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;lst&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
    &lt;span class="n"&gt;lst&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;val&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;lst&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;div class="section" id="what-s-decorator"&gt;
&lt;h3&gt;&lt;a class="toc-backref" href="#toc-entry-9"&gt;What&amp;#8217;s&amp;nbsp;decorator?&lt;/a&gt;&lt;/h3&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;What&amp;#8217;s&amp;nbsp;decorator?&lt;/li&gt;
&lt;li&gt;What decorators from the standard library you&amp;nbsp;know?&lt;/li&gt;
&lt;li&gt;Tell about &lt;a class="reference external" href="https://docs.python.org/3.7/howto/descriptor.html"&gt;descriptor protocol&lt;/a&gt; (complex&amp;nbsp;question)&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class="section" id="decorator-that-prints-function-execution-time"&gt;
&lt;h3&gt;&lt;a class="toc-backref" href="#toc-entry-10"&gt;Decorator that prints function execution&amp;nbsp;time&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;We have got a function that queries a database. We want each function
call to print time of its execution. Implement it with a decorator.
Assume: decorated function may have any number of positional or
keyword&amp;nbsp;arguments.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;time&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;measure&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;func&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;wrapper&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;kwargs&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;Wrapper function&amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;
        &lt;span class="n"&gt;before&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;func&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;kwargs&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;after&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;time&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;Time elapsed: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;after&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;before&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; seconds&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;wrapper&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;div class="section" id="using-decorator"&gt;
&lt;h3&gt;&lt;a class="toc-backref" href="#toc-entry-11"&gt;Using&amp;nbsp;decorator&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;How do you apply your decorator to a function &lt;cite&gt;fun&lt;/cite&gt; using syntactic
sugar and without&amp;nbsp;it?&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="c1"&gt;# with syntactic sugar&lt;/span&gt;
&lt;span class="nd"&gt;@measure&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;fun&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;l&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;list&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="k"&gt;pass&lt;/span&gt;

&lt;span class="c1"&gt;# sugar free&lt;/span&gt;
&lt;span class="n"&gt;fun&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;measure&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;fun&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;fun&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;# function&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;div class="section" id="decorated-function-s-name"&gt;
&lt;h3&gt;&lt;a class="toc-backref" href="#toc-entry-12"&gt;Decorated function&amp;#8217;s&amp;nbsp;name&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;What is function &lt;cite&gt;docstring&lt;/cite&gt; and &lt;cite&gt;name&lt;/cite&gt; after decorator is&amp;nbsp;applied?&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="n"&gt;fun&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="vm"&gt;__name__&lt;/span&gt;  &lt;span class="c1"&gt;# &amp;#39;wrapper&amp;#39;&lt;/span&gt;
&lt;span class="n"&gt;fun&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="vm"&gt;__doc__&lt;/span&gt;  &lt;span class="c1"&gt;# &amp;#39;Wrapper function&amp;#39;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;How do you return the original name and docstring to the decorated&amp;nbsp;function?&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;functools&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;wraps&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;time&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;measure&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;func&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="nd"&gt;@wraps&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;func&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;wrapper&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;kwargs&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;Wrapper function&amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;
        &lt;span class="n"&gt;before&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;func&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;kwargs&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;after&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;time&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;Time elapsed: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;after&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;before&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; seconds&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;wrapper&lt;/span&gt;

&lt;span class="nd"&gt;@measure&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;fun&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;l&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;list&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="w"&gt;    &lt;/span&gt;&lt;span class="sd"&gt;&amp;quot;&amp;quot;&amp;quot;My func&amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;
    &lt;span class="k"&gt;pass&lt;/span&gt;

&lt;span class="n"&gt;fun&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="vm"&gt;__name__&lt;/span&gt;  &lt;span class="c1"&gt;# &amp;#39;fun&amp;#39;&lt;/span&gt;
&lt;span class="n"&gt;fun&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="vm"&gt;__doc__&lt;/span&gt;   &lt;span class="c1"&gt;# &amp;#39;My func&amp;#39;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;How would you do that without &lt;cite&gt;functools.wraps&lt;/cite&gt;?&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;time&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;measure&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;func&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;wrapper&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;kwargs&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;Wrapper function&amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;
        &lt;span class="n"&gt;before&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;func&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;kwargs&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;after&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;time&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;Time elapsed: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;after&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;before&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; seconds&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;
    &lt;span class="n"&gt;wrapper&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="vm"&gt;__doc__&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;func&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="vm"&gt;__doc__&lt;/span&gt;
    &lt;span class="n"&gt;wrapper&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="vm"&gt;__name__&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;func&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="vm"&gt;__name__&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;wrapper&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;div class="section" id="decorators-for-generators"&gt;
&lt;h3&gt;&lt;a class="toc-backref" href="#toc-entry-13"&gt;Decorators for&amp;nbsp;generators&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;What time do we get if we decorate a generator function like the&amp;nbsp;following?&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;typing&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Generator&lt;/span&gt;&lt;span class="p"&gt;,&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;fun_gen&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;l&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;list&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;Generator&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="kc"&gt;None&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;None&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;l&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
          &lt;span class="k"&gt;yield&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Why time we see is so small? How would you measure the real time of
iteration over the whole&amp;nbsp;generator?&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;time&lt;/span&gt;
&lt;span class="kn"&gt;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;Generator&lt;/span&gt;&lt;span class="p"&gt;,&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;measure_gen&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;func&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;wrapper&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;kwargs&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;before&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;yield from&lt;/span&gt; &lt;span class="n"&gt;func&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;kwargs&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;after&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;time&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;Time elapsed: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;after&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;before&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; seconds&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;wrapper&lt;/span&gt;

&lt;span class="nd"&gt;@measure_gen&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;fun_gen&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;l&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;list&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;Generator&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="kc"&gt;None&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;None&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;l&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;yield&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;div class="section" id="decorator-with-arguments"&gt;
&lt;h3&gt;&lt;a class="toc-backref" href="#toc-entry-14"&gt;Decorator with&amp;nbsp;arguments&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Rewrite the decorator from the previous question so that it could take
in an argument for a limit. Execution time is printed if it exceeds
the&amp;nbsp;limit.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;measure&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;limit&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="c1"&gt;# Don&amp;#39;t forget to set limit when decorating a function!&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;outer_wrapper&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;func&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;inner_wrapper&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;kwargs&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
            &lt;span class="n"&gt;before&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
            &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;func&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;kwargs&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="n"&gt;after&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

            &lt;span class="n"&gt;elapsed&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;after&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;before&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;elapsed&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;limit&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;Time elapsed: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;after&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;before&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; seconds&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;inner_wrapper&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;outer_wrapper&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;How do we decorate a function using syntactic sugar and without&amp;nbsp;it?&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="n"&gt;new_measure&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;measure&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;fun&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;new_measure&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;fun&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;fun&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;

&lt;span class="c1"&gt;# sugar free&lt;/span&gt;
&lt;span class="n"&gt;new_measure&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;measure&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;fun&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;new_measure&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;fun&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;fun&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;div class="section" id="class-variables"&gt;
&lt;h3&gt;&lt;a class="toc-backref" href="#toc-entry-15"&gt;Class&amp;nbsp;variables&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;What&amp;#8217;s a class&amp;nbsp;variable?&lt;/p&gt;
&lt;p&gt;Problem.&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;Parent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;object&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Child1&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Parent&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;pass&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Child2&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Parent&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;pass&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;What will the following print statements&amp;nbsp;print?&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Parent&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Child1&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Child2&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="c1"&gt;# 1 1 1&lt;/span&gt;

&lt;span class="n"&gt;Child1&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;
&lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Parent&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Child1&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Child2&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="c1"&gt;# 1 2 1&lt;/span&gt;

&lt;span class="n"&gt;Parent&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;
&lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Parent&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Child1&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Child2&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="c1"&gt;# 3 2 3&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;div class="section" id="list-slices"&gt;
&lt;h3&gt;&lt;a class="toc-backref" href="#toc-entry-16"&gt;List&amp;nbsp;slices&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Problem. What are the results of this code block&amp;nbsp;execution?&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="n"&gt;l&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

&lt;span class="n"&gt;l&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;  &lt;span class="c1"&gt;# [2]&lt;/span&gt;
&lt;span class="n"&gt;l&lt;/span&gt;&lt;span class="p"&gt;[:&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;  &lt;span class="c1"&gt;# [1, 2, 3, 4]&lt;/span&gt;
&lt;span class="n"&gt;l&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;:]&lt;/span&gt;  &lt;span class="c1"&gt;# []&lt;/span&gt;
&lt;span class="n"&gt;l&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;   &lt;span class="c1"&gt;# IndexError&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;div class="section" id="what-s-list-dict-comprehension"&gt;
&lt;h3&gt;&lt;a class="toc-backref" href="#toc-entry-17"&gt;What&amp;#8217;s list/dict&amp;nbsp;comprehension?&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Implement a list of even numbers in a range [0, 10) to the power 2
using list&amp;nbsp;comprehension.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nb"&gt;range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Implement the same using map and&amp;nbsp;filter.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="nb"&gt;map&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;x&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;x&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="k"&gt;lambda&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;div class="section" id="generator-expressions"&gt;
&lt;h3&gt;&lt;a class="toc-backref" href="#toc-entry-18"&gt;Generator&amp;nbsp;expressions&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;What&amp;#8217;s the easiest way to turn the above list comprehension into a
generator (generator&amp;nbsp;expression)?&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nb"&gt;range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;div class="section" id="what-useful-functions-from-functools-do-you-know"&gt;
&lt;h3&gt;&lt;a class="toc-backref" href="#toc-entry-19"&gt;What useful functions from functools do you&amp;nbsp;know?&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;The question may be a follow-up for decorator questions and
&lt;cite&gt;functools.wraps&lt;/cite&gt;. functools is a great module with loads of useful
things. What would be great to hear from the candidate is something
like &lt;cite&gt;reduce&lt;/cite&gt; and &lt;cite&gt;partial&lt;/cite&gt;.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="asyncio-multithreading-multiprocessing"&gt;
&lt;h3&gt;&lt;a class="toc-backref" href="#toc-entry-20"&gt;Asyncio, multithreading,&amp;nbsp;multiprocessing&lt;/a&gt;&lt;/h3&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;What&amp;#8217;s the difference between a process and a&amp;nbsp;thread?&lt;/li&gt;
&lt;li&gt;Problem. We have got a list of 1000 URLs, we have to iterate over it
and make &lt;span class="caps"&gt;GET&lt;/span&gt; requests. How would you do that? (&lt;span class="caps"&gt;IO&lt;/span&gt;-bound tasks -&amp;gt;
asyncio, Tornado etc или&amp;nbsp;multithreading)&lt;/li&gt;
&lt;li&gt;Problem. We have got 1000 video files than should be rendered
somehow using Python. How would you do that? (&lt;span class="caps"&gt;CPU&lt;/span&gt;-bound tasks -&amp;gt;&amp;nbsp;multiprocessing)&lt;/li&gt;
&lt;li&gt;Give an overview of &lt;cite&gt;asyncio&lt;/cite&gt; or similar libraries for asynchronous
programming. How does it work? (huge question, expectations are
covered in &lt;a class="reference external" href="https://trio.readthedocs.io/en/latest/tutorial.html"&gt;Trio tutorial&lt;/a&gt;)&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class="section" id="python-ecosystem"&gt;
&lt;h3&gt;&lt;a class="toc-backref" href="#toc-entry-21"&gt;Python&amp;nbsp;ecosystem&lt;/a&gt;&lt;/h3&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;What do you use for background tasks in a Python project? (&lt;cite&gt;celery&lt;/cite&gt;)&lt;/li&gt;
&lt;li&gt;How do you pack your Python code? (&lt;cite&gt;wheels&lt;/cite&gt;, &lt;cite&gt;eggs&lt;/cite&gt;, what&amp;#8217;s the&amp;nbsp;difference?)&lt;/li&gt;
&lt;li&gt;How do you document your Python project? (&lt;cite&gt;sphinx-doc&lt;/cite&gt;)&lt;/li&gt;
&lt;li&gt;What do you use for &lt;span class="caps"&gt;REST&lt;/span&gt; &lt;span class="caps"&gt;API&lt;/span&gt; documenting? (&lt;cite&gt;swagger&lt;/cite&gt;)&lt;/li&gt;
&lt;li&gt;What &lt;span class="caps"&gt;ORM&lt;/span&gt; do you use? (&lt;cite&gt;SQLAlchemy&lt;/cite&gt;, &lt;cite&gt;Django &lt;span class="caps"&gt;ORM&lt;/span&gt;&lt;/cite&gt;)&lt;/li&gt;
&lt;li&gt;What static analysis tools and linters for Python code do you use?
(&lt;cite&gt;mypy&lt;/cite&gt;, &lt;cite&gt;flake8&lt;/cite&gt;)&lt;/li&gt;
&lt;li&gt;What web frameworks do you use? (&lt;cite&gt;Flask&lt;/cite&gt;, &lt;cite&gt;Django&lt;/cite&gt;, &lt;cite&gt;aiohttp&lt;/cite&gt;,
&lt;cite&gt;sanic&lt;/cite&gt;,&amp;nbsp;etc.)&lt;/li&gt;
&lt;li&gt;What package management tools do you use? (&lt;cite&gt;pip&lt;/cite&gt;, &lt;cite&gt;pipenv&lt;/cite&gt;,
&lt;cite&gt;poetry&lt;/cite&gt;,&amp;nbsp;etc.)&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="section" id="computer-science-questions"&gt;
&lt;h2&gt;&lt;a class="toc-backref" href="#toc-entry-22"&gt;Computer science&amp;nbsp;questions&lt;/a&gt;&lt;/h2&gt;
&lt;div class="section" id="time-complexity"&gt;
&lt;h3&gt;&lt;a class="toc-backref" href="#toc-entry-23"&gt;Time&amp;nbsp;complexity&lt;/a&gt;&lt;/h3&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;What&amp;#8217;s time&amp;nbsp;complexity?&lt;/li&gt;
&lt;li&gt;What&amp;#8217;s Big&amp;nbsp;O?&lt;/li&gt;
&lt;li&gt;What&amp;#8217;s time complexity for the following&amp;nbsp;algorithm?&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="c1"&gt;# O(n^2)&lt;/span&gt;
&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;n&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;y&lt;/span&gt; &lt;span class="ow"&gt;in&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;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;What&amp;#8217;s time complexity for each of these&amp;nbsp;statements?&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt; &lt;span class="c1"&gt;# where type(s) is list/tuple # O(n)&lt;/span&gt;
&lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt; &lt;span class="c1"&gt;# where type(s) is set        # O(1)&lt;/span&gt;
&lt;span class="nb"&gt;list&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;append&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;                               &lt;span class="c1"&gt;# O(1)&lt;/span&gt;
&lt;span class="nb"&gt;list&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;insert&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;                               &lt;span class="c1"&gt;# O(n)&lt;/span&gt;
&lt;span class="nb"&gt;list&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;index&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;                                 &lt;span class="c1"&gt;# O(1)&lt;/span&gt;
&lt;span class="nb"&gt;list&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sort&lt;/span&gt;                                   &lt;span class="c1"&gt;# O(n log n)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Check out other data structures &lt;a class="reference external" href="https://wiki.python.org/moin/TimeComplexity"&gt;time complexity&lt;/a&gt; if&amp;nbsp;needed.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="balanced-brackets-in-a-lisp-program-problem"&gt;
&lt;h3&gt;&lt;a class="toc-backref" href="#toc-entry-24"&gt;Balanced brackets in a Lisp-program&amp;nbsp;problem&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Problem. I like to code in Racket, which is a Lisp dialect that uses a
&lt;a class="reference external" href="https://blog.pilosus.org/images/git-the-princess.png"&gt;lot of brackets&lt;/a&gt;.  Implement a Python function that takes in text
and returns &lt;cite&gt;True&lt;/cite&gt; if brackets in the text are balanced, and returns
&lt;cite&gt;False&lt;/cite&gt; otherwise.&lt;/p&gt;
&lt;p&gt;Examples of balanced&amp;nbsp;brackets:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;()&amp;quot;&lt;/span&gt;
&lt;span class="s2"&gt;&amp;quot;(())&amp;quot;&lt;/span&gt;
&lt;span class="s2"&gt;&amp;quot;(custom-set-variables &amp;#39;(inhibit-startup-screen t) &amp;#39;(package-selected-packages (quote&lt;/span&gt;
&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sql&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;indent&lt;/span&gt; &lt;span class="n"&gt;magit&lt;/span&gt; &lt;span class="n"&gt;dockerfile&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;mode&lt;/span&gt; &lt;span class="n"&gt;yaml&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;mode&lt;/span&gt; &lt;span class="n"&gt;gitlab&lt;/span&gt; &lt;span class="n"&gt;markdown&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;mode&lt;/span&gt; &lt;span class="n"&gt;lorem&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;ipsum&lt;/span&gt; &lt;span class="n"&gt;jedi&lt;/span&gt;
&lt;span class="n"&gt;elpy&lt;/span&gt; &lt;span class="n"&gt;anaconda&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;mode&lt;/span&gt; &lt;span class="n"&gt;afternoon&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;theme&lt;/span&gt;&lt;span class="p"&gt;))))&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;
&lt;span class="s2"&gt;&amp;quot;(hello [world {!}])&amp;quot;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Examples of unbalanced&amp;nbsp;brackets:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;(&amp;quot;&lt;/span&gt;
&lt;span class="s2"&gt;&amp;quot;)(&amp;quot;&lt;/span&gt;
&lt;span class="s2"&gt;&amp;quot;())&amp;quot;&lt;/span&gt;
&lt;span class="s2"&gt;&amp;quot;({))&amp;quot;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;balance&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="sd"&gt;&amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;
&lt;span class="sd"&gt;  Return True if string&amp;#39;s parentheses are balanced, False otherwise&lt;/span&gt;

&lt;span class="sd"&gt;  &amp;gt;&amp;gt;&amp;gt; balance(&amp;quot;&amp;quot;)&lt;/span&gt;
&lt;span class="sd"&gt;  True&lt;/span&gt;
&lt;span class="sd"&gt;  &amp;gt;&amp;gt;&amp;gt; balance(&amp;quot;(())&amp;quot;)&lt;/span&gt;
&lt;span class="sd"&gt;  True&lt;/span&gt;
&lt;span class="sd"&gt;  &amp;gt;&amp;gt;&amp;gt; balance(&amp;quot;))((&amp;quot;)&lt;/span&gt;
&lt;span class="sd"&gt;  False&lt;/span&gt;
&lt;span class="sd"&gt;  &amp;gt;&amp;gt;&amp;gt; balance(&amp;quot;()()(())(()())&amp;quot;)&lt;/span&gt;
&lt;span class="sd"&gt;  True&lt;/span&gt;
&lt;span class="sd"&gt;  &amp;gt;&amp;gt;&amp;gt; balance(&amp;quot;(]&amp;quot;)&lt;/span&gt;
&lt;span class="sd"&gt;  False&lt;/span&gt;
&lt;span class="sd"&gt;  &amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;
  &lt;span class="n"&gt;opening&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;(&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;[&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;{&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="n"&gt;closing&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;)&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;]&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;}&amp;quot;&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="nb"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;zip&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;opening&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;closing&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
  &lt;span class="n"&gt;stack&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
  &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;ch&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
      &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;ch&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;opening&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
          &lt;span class="n"&gt;stack&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;mapping&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;ch&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
      &lt;span class="k"&gt;elif&lt;/span&gt; &lt;span class="n"&gt;ch&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;closing&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
          &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;stack&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ch&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="n"&gt;stack&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;pop&lt;/span&gt;&lt;span class="p"&gt;()):&lt;/span&gt;
              &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;False&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;stack&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;If needed the function can be simplified to check only for round&amp;nbsp;brackets.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="section" id="databases"&gt;
&lt;h2&gt;&lt;a class="toc-backref" href="#toc-entry-25"&gt;Databases&lt;/a&gt;&lt;/h2&gt;
&lt;div class="section" id="why-rdbms"&gt;
&lt;h3&gt;&lt;a class="toc-backref" href="#toc-entry-26"&gt;Why &lt;span class="caps"&gt;RDBMS&lt;/span&gt;?&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Problem. We are a startup with &lt;span class="caps"&gt;CTO&lt;/span&gt;, &lt;span class="caps"&gt;CEO&lt;/span&gt; and a couple of devs. We have
no admins yet. We are developing authentication service (users
registration, access rights, etc) in Python. Why do we need a
database? Why not using a simple text file? Python has all we need to
write to and read from a text file. We can just write user login and
password hash to the file when the user is signing up. And then we
read the credentials when the user is signing in in. It&amp;#8217;s easy. It
doesn&amp;#8217;t require database admin work. Why not a text&amp;nbsp;file?&lt;/p&gt;
&lt;p&gt;If needed, the question can be put this way: what mechanisms of a
relational database do you know, that are complex enough to implement,
but are necessary for our&amp;nbsp;case?&lt;/p&gt;
&lt;p&gt;&lt;span class="caps"&gt;DB&lt;/span&gt; features that the candidate expected to&amp;nbsp;mention:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;index&lt;/li&gt;
&lt;li&gt;relations&lt;/li&gt;
&lt;li&gt;transactions&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The candidate may be asked to elaborate on each of these features. For
example, you can ask him/her about&amp;nbsp;indices:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;What&amp;#8217;s time complexity for searching using the&amp;nbsp;index?&lt;/li&gt;
&lt;li&gt;Problem. In table &lt;cite&gt;candidates&lt;/cite&gt; we have a &lt;cite&gt;is_interviewed&lt;/cite&gt; column
with possible values 0 or 1. How adding an index for this column
will affect search&amp;nbsp;performance?&lt;/li&gt;
&lt;li&gt;What&amp;#8217;s composite index (or multicolumn index)? What are the rules of
using composite&amp;nbsp;index?&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class="section" id="mysql-postgresql-queries"&gt;
&lt;h3&gt;&lt;a class="toc-backref" href="#toc-entry-27"&gt;MySQL/PostgreSQL&amp;nbsp;queries&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Problem. We have got service like iTunes where users can buy or rent a
video. This is a PostgreSQL database schema for the&amp;nbsp;service:&lt;/p&gt;
&lt;div class="figure"&gt;
&lt;a class="reference external image-reference" href="https://blog.pilosus.org/images/sql-tables.pdf"&gt;&lt;img alt="PostgreSQL dababase schema for technical interview questions" class="responsive" src="https://blog.pilosus.org/images/sql-tables.jpg" /&gt;&lt;/a&gt;
&lt;/div&gt;
&lt;p&gt;Write a &lt;span class="caps"&gt;SQL&lt;/span&gt; query to select top-20 movies starring actor John Doe. The
query also&amp;nbsp;selects:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;Movie&amp;nbsp;title&lt;/li&gt;
&lt;li&gt;Movie&amp;nbsp;language&lt;/li&gt;
&lt;li&gt;Movie release&amp;nbsp;year&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Results should be ordered in descendant order by a release&amp;nbsp;year.&lt;/p&gt;
&lt;p&gt;(If help needed) use tables: &lt;cite&gt;film&lt;/cite&gt;, &lt;cite&gt;language&lt;/cite&gt;, &lt;cite&gt;film_actor&lt;/cite&gt;, &lt;cite&gt;actor&lt;/cite&gt;&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;f&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;title&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;l&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;f&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;release_year&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;film&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;AS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;
&lt;span class="k"&gt;JOIN&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;language&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;AS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;l&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;ON&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;l&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;language_id&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;f&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;language_id&lt;/span&gt;
&lt;span class="k"&gt;JOIN&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;film_actor&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;AS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;fa&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;ON&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;fa&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;film_id&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;f&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;film_id&lt;/span&gt;
&lt;span class="k"&gt;JOIN&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;actor&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;AS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;ON&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;actor_id&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;fa&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;actor_id&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;a&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;first_name&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;John&amp;#39;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;AND&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;last_name&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;Doe&amp;#39;&lt;/span&gt;
&lt;span class="k"&gt;ORDER&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;BY&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;release_year&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;DESC&lt;/span&gt;
&lt;span class="k"&gt;LIMIT&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Write a &lt;span class="caps"&gt;SQL&lt;/span&gt; query to select top-10 customers who paid most, but not
less than 100. The query also&amp;nbsp;selects:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;Customer&amp;#8217;s &lt;span class="caps"&gt;ID&lt;/span&gt; and full&amp;nbsp;name&lt;/li&gt;
&lt;li&gt;Customer sum of&amp;nbsp;payments&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;(If help needed) use tables: &lt;cite&gt;payment&lt;/cite&gt;, &lt;cite&gt;customer&lt;/cite&gt;&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="k"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;customer_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;first_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;last_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;SUM&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;amount&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;AS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;money&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;payment&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;AS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt;
&lt;span class="k"&gt;JOIN&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;customer&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;AS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;c&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;ON&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;customer_id&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;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;customer_id&lt;/span&gt;
&lt;span class="k"&gt;GROUP&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;BY&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;customer_id&lt;/span&gt;
&lt;span class="k"&gt;HAVING&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;SUM&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;amount&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;
&lt;span class="k"&gt;ORDER&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;BY&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;money&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;DESC&lt;/span&gt;
&lt;span class="k"&gt;LIMIT&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;div class="section" id="nosql-overview"&gt;
&lt;h3&gt;&lt;a class="toc-backref" href="#toc-entry-28"&gt;NoSQL&amp;nbsp;Overview&lt;/a&gt;&lt;/h3&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;What&amp;#8217;s&amp;nbsp;NoSQL?&lt;/li&gt;
&lt;li&gt;What types of NoSQL do you&amp;nbsp;know?&lt;/li&gt;
&lt;li&gt;What NoSQL &lt;span class="caps"&gt;DB&lt;/span&gt; did you&amp;nbsp;use?&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Minimal expected answer: NoSQL is a database for collections of
data. Data is denormalized, &lt;span class="caps"&gt;JOIN&lt;/span&gt; is not supported (usually implemented
in your&amp;nbsp;app).&lt;/p&gt;
&lt;p&gt;Types of NoSQL&amp;nbsp;DBs:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;key-value store (hash table implementation: Redis,&amp;nbsp;AeroSpike)&lt;/li&gt;
&lt;li&gt;document-store (like key-value but with documents, e.g &lt;span class="caps"&gt;JSON&lt;/span&gt;, for
values:&amp;nbsp;MongoDB)&lt;/li&gt;
&lt;li&gt;wide column store (HBase,&amp;nbsp;Cassandra)&lt;/li&gt;
&lt;li&gt;graph database&amp;nbsp;(Neo4j)&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class="section" id="redis-overview"&gt;
&lt;h3&gt;&lt;a class="toc-backref" href="#toc-entry-29"&gt;Redis&amp;nbsp;Overview&lt;/a&gt;&lt;/h3&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;What&amp;#8217;s Redis is used&amp;nbsp;for?&lt;/li&gt;
&lt;li&gt;Why it&amp;#8217;s so popular for caching&amp;nbsp;layer?&lt;/li&gt;
&lt;li&gt;How Redis is different from MySQL when it comes to data&amp;nbsp;storage?&lt;/li&gt;
&lt;li&gt;What happens if a machine that runs Redis server is&amp;nbsp;rebooted?&lt;/li&gt;
&lt;li&gt;What high availability mechanisms for Redis do you&amp;nbsp;know?&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Expectations: Redis is an in-memory key-value &lt;span class="caps"&gt;DB&lt;/span&gt;. Data snapshot
mechanism can be used in Redis to dump data to the disk. Redis
Sentinel is a mechanism for high&amp;nbsp;availability.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="section" id="testing"&gt;
&lt;h2&gt;&lt;a class="toc-backref" href="#toc-entry-30"&gt;Testing&lt;/a&gt;&lt;/h2&gt;
&lt;div class="section" id="unit-testing-overview"&gt;
&lt;h3&gt;&lt;a class="toc-backref" href="#toc-entry-31"&gt;Unit-testing&amp;nbsp;overview&lt;/a&gt;&lt;/h3&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;What&amp;#8217;s&amp;nbsp;unit-testing?&lt;/li&gt;
&lt;li&gt;How it&amp;#8217;s different from integration or functional&amp;nbsp;testing?&lt;/li&gt;
&lt;li&gt;What libraries did you use for testing in Python? (&lt;cite&gt;unittest&lt;/cite&gt;,
&lt;cite&gt;pytest&lt;/cite&gt;) How they are different from each&amp;nbsp;other?&lt;/li&gt;
&lt;li&gt;When you write a unit-test? (in the existing project, in the new
project, when fixing a bug, when implementing a new&amp;nbsp;feature)&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class="section" id="how-do-you-test-a-function-that-changes-data-in-mysql"&gt;
&lt;h3&gt;&lt;a class="toc-backref" href="#toc-entry-32"&gt;How do you test a function that changes data in&amp;nbsp;MySQL?&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Problem. We&amp;#8217;ve got a function that executes a &lt;cite&gt;&lt;span class="caps"&gt;INSERT&lt;/span&gt;&lt;/cite&gt; &lt;span class="caps"&gt;SQL&lt;/span&gt; query in
the test database. Other developers in your team use the same
database. We need to write a unittest for the function. But we don&amp;#8217;t
want to change test &lt;span class="caps"&gt;DB&lt;/span&gt; on each test run. How do you do&amp;nbsp;it?&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;mock&lt;/li&gt;
&lt;li&gt;transaction&amp;nbsp;rollback&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="section" id="devops-sysadmin-skills"&gt;
&lt;h2&gt;&lt;a class="toc-backref" href="#toc-entry-33"&gt;DevOps/SysAdmin&amp;nbsp;skills&lt;/a&gt;&lt;/h2&gt;
&lt;div class="section" id="broken-response-from-google-com"&gt;
&lt;h3&gt;&lt;a class="toc-backref" href="#toc-entry-34"&gt;Broken response from&amp;nbsp;google.com&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Problem. When I open a browser, go to &lt;cite&gt;https://www.google.com&lt;/cite&gt; and see
the raw response, it&amp;#8217;s normal&amp;nbsp;text.&lt;/p&gt;
&lt;p&gt;Here I do a &lt;span class="caps"&gt;GET&lt;/span&gt; request using &lt;cite&gt;curl&lt;/cite&gt;, but a response seems to be&amp;nbsp;broken.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&amp;gt;&lt;span class="w"&gt; &lt;/span&gt;GET&lt;span class="w"&gt; &lt;/span&gt;/&lt;span class="w"&gt; &lt;/span&gt;HTTP/2
&amp;gt;&lt;span class="w"&gt; &lt;/span&gt;Host:&lt;span class="w"&gt; &lt;/span&gt;www.google.com
&amp;gt;&lt;span class="w"&gt; &lt;/span&gt;User-Agent:&lt;span class="w"&gt; &lt;/span&gt;curl/7.58.0
&amp;gt;&lt;span class="w"&gt; &lt;/span&gt;Accept:&lt;span class="w"&gt; &lt;/span&gt;*/*
&amp;gt;&lt;span class="w"&gt; &lt;/span&gt;Accept-Encoding:&lt;span class="w"&gt; &lt;/span&gt;gzip,&lt;span class="w"&gt; &lt;/span&gt;deflate

%�j��~d+.K�W��me&lt;span class="sb"&gt;`&lt;/span&gt;�,x�x�m�G�67��&lt;span class="sb"&gt;`&lt;/span&gt;&lt;span class="nv"&gt;$#&lt;/span&gt;������~&lt;span class="o"&gt;{&lt;/span&gt;d���&lt;span class="o"&gt;]&lt;/span&gt;�.&lt;span class="err"&gt;&amp;#39;&lt;/span&gt;����
����Q���Mʥu���&amp;lt;�O&lt;span class="o"&gt;)&lt;/span&gt;,�&lt;span class="p"&gt;&amp;amp;&lt;/span&gt;�����,���E�.���_�
&lt;span class="p"&gt;&amp;amp;&lt;/span&gt;�I&lt;span class="se"&gt;\�&lt;/span&gt;�p��#&lt;span class="o"&gt;]&lt;/span&gt;�,�����K+����p�vk
j���k&lt;span class="s2"&gt;&amp;quot;�fe�=�`�J4�I/��m6{�E���;3+�v�BT1T��(W��J��m�yO��&lt;/span&gt;
&lt;span class="s2"&gt;�j��&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Why is it&amp;nbsp;so?&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;curl&lt;span class="w"&gt; &lt;/span&gt;-v&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;https://www.google.com&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;--header&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Accept-Encoding: gzip, deflate&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;--output&lt;span class="w"&gt; &lt;/span&gt;-
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;div class="section" id="ip-addresses-top-from-nginx-log"&gt;
&lt;h3&gt;&lt;a class="toc-backref" href="#toc-entry-35"&gt;&lt;span class="caps"&gt;IP&lt;/span&gt; addresses top from Nginx&amp;nbsp;log&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Problem. Our service is under high load because of DDoS-attack. It
makes hard for our legitimate customers to get a response from the
service. We want to ban &lt;span class="caps"&gt;IP&lt;/span&gt; addresses bots get used of. To do so we
want to make a top of &lt;span class="caps"&gt;IP&lt;/span&gt; addresses with the most requests. We ask our
admins to provide web server logs as a file &lt;cite&gt;nginx.log&lt;/cite&gt;.&lt;/p&gt;
&lt;p&gt;Using console &lt;span class="caps"&gt;GNU&lt;/span&gt; utils create a file with top &lt;span class="caps"&gt;IP&lt;/span&gt;-addresses with most
requests in descending order. Assume that &lt;span class="caps"&gt;IP&lt;/span&gt;-address is in 9th column
in the log, columns are separated by &lt;cite&gt;t&lt;/cite&gt;.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;awk&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;{print $9}&amp;#39;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;nginx.log&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;sort&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;uniq&lt;span class="w"&gt; &lt;/span&gt;-c&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;awk&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;{print $1&amp;quot;,&amp;quot;$2}&amp;#39;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;sort&lt;span class="w"&gt; &lt;/span&gt;-r&lt;span class="w"&gt; &lt;/span&gt;-t,&lt;span class="w"&gt; &lt;/span&gt;-nk1&lt;span class="w"&gt; &lt;/span&gt;&amp;gt;&lt;span class="w"&gt; &lt;/span&gt;bots.csv
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;div class="section" id="docker"&gt;
&lt;h3&gt;&lt;a class="toc-backref" href="#toc-entry-36"&gt;Docker&lt;/a&gt;&lt;/h3&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;What&amp;#8217;s a docker&amp;nbsp;container?&lt;/li&gt;
&lt;li&gt;What&amp;#8217;s the difference between docker and virtualbox or other virtual&amp;nbsp;machines?&lt;/li&gt;
&lt;li&gt;Why docker? We can just copy files to the real hardware&amp;nbsp;machine&lt;/li&gt;
&lt;li&gt;Why not using virtualenv instead of&amp;nbsp;Docker?&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="section" id="system-design-and-architecture"&gt;
&lt;h2&gt;&lt;a class="toc-backref" href="#toc-entry-37"&gt;System Design and&amp;nbsp;Architecture&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Parental advisory: highly opinionated content&amp;nbsp;(35+)&lt;/strong&gt;&lt;/p&gt;
&lt;div class="section" id="system-design-of-pastebin-like-service"&gt;
&lt;h3&gt;&lt;a class="toc-backref" href="#toc-entry-38"&gt;System design of pastebin-like&amp;nbsp;service&lt;/a&gt;&lt;/h3&gt;
&lt;div class="section" id="user-story-business-description"&gt;
&lt;h4&gt;&lt;a class="toc-backref" href="#toc-entry-39"&gt;User story (business&amp;nbsp;description)&lt;/a&gt;&lt;/h4&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;A user submits text&amp;nbsp;(copypasta)&lt;/li&gt;
&lt;li&gt;Text is saved, the user gets a unique &lt;span class="caps"&gt;URL&lt;/span&gt; (base serive &lt;span class="caps"&gt;URL&lt;/span&gt; + short&amp;nbsp;path)&lt;/li&gt;
&lt;li&gt;User goes to the &lt;span class="caps"&gt;URL&lt;/span&gt; and sees his/her original&amp;nbsp;text&lt;/li&gt;
&lt;li&gt;Text is stored until it&amp;#8217;s&amp;nbsp;expired&lt;/li&gt;
&lt;li&gt;Service deletes expired&amp;nbsp;texts&lt;/li&gt;
&lt;li&gt;Once the text is expired, it&amp;#8217;s not accessible anymore by its &lt;span class="caps"&gt;URL&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;Service is operating under high&amp;nbsp;load&lt;/li&gt;
&lt;li&gt;A product owner needs monthly statistics for URLs&amp;nbsp;visits&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class="section" id="assume"&gt;
&lt;h4&gt;&lt;a class="toc-backref" href="#toc-entry-40"&gt;Assume&lt;/a&gt;&lt;/h4&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;Copypasta can be in text format&amp;nbsp;only&lt;/li&gt;
&lt;li&gt;The product owner doesn&amp;#8217;t need realtime&amp;nbsp;statistics&lt;/li&gt;
&lt;li&gt;Service gets 10 million new copypastas a month (3.85 &lt;span class="caps"&gt;RPS&lt;/span&gt;)&lt;/li&gt;
&lt;li&gt;Service has 100 million copypasta reads a month (38.5 &lt;span class="caps"&gt;RPS&lt;/span&gt;)&lt;/li&gt;
&lt;li&gt;We need capacity planning (hardware planning) for 3&amp;nbsp;years&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class="section" id="hints"&gt;
&lt;h4&gt;&lt;a class="toc-backref" href="#toc-entry-41"&gt;Hints&lt;/a&gt;&lt;/h4&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;Average text size is&amp;nbsp;1Kb&lt;/li&gt;
&lt;li&gt;Short &lt;span class="caps"&gt;URL&lt;/span&gt; path takes 7 bytes in &lt;span class="caps"&gt;DB&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span class="caps"&gt;URL&lt;/span&gt; expiry (or &lt;span class="caps"&gt;TTL&lt;/span&gt;) takes 4 bytes in &lt;span class="caps"&gt;DB&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span class="caps"&gt;URL&lt;/span&gt; timestamp takes 5 bytes in &lt;span class="caps"&gt;DB&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;File path to a copypasta takes 255 bytes in &lt;span class="caps"&gt;DB&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;In total: 1.27 &lt;span class="caps"&gt;KB&lt;/span&gt; per copypasta or 1.27 Gb of copypasta a&amp;nbsp;month&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class="section" id="capacity-planning-for-3-years"&gt;
&lt;h4&gt;&lt;a class="toc-backref" href="#toc-entry-42"&gt;Capacity planning for 3&amp;nbsp;years&lt;/a&gt;&lt;/h4&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;36 * 12.7 = 457 Gb of&amp;nbsp;copypasta&lt;/li&gt;
&lt;li&gt;36 * 10 = 360 million&amp;nbsp;URLs&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class="section" id="service-architecture"&gt;
&lt;h4&gt;&lt;a class="toc-backref" href="#toc-entry-43"&gt;Service&amp;nbsp;architecture&lt;/a&gt;&lt;/h4&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;  Client
    |
    V
Load Balancer
    |
    V
 Web Server (Reverse Proxy, i.e. Nginx)
  |      |
  V      V
Write   Read                                Analytics (MapReduce)
API     API---------------------------       |         |
 |       |        |                  |       |         |
 |       V        |                  |       |         |
 |      Cache     |                  |       |         V
 ------------------------------      |       |       SQL for Analytics
 V                |            |     |       |
SQL-----------    |            V     V       V
 |_Master    |    |           Object (File) Store
 |_Slave     V    |
            SQL   V
 ^              Read-only Replica
 |
 |
Periodic Task
(remove expired)
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;div class="section" id="write-api"&gt;
&lt;h4&gt;&lt;a class="toc-backref" href="#toc-entry-44"&gt;Write &lt;span class="caps"&gt;API&lt;/span&gt;&lt;/a&gt;&lt;/h4&gt;
&lt;ol class="arabic simple"&gt;
&lt;li&gt;Generate a &lt;span class="caps"&gt;URL&lt;/span&gt;, check for unique constraint in &lt;span class="caps"&gt;SQL&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;If the &lt;span class="caps"&gt;URL&lt;/span&gt; is unique, make an &lt;span class="caps"&gt;INSERT&lt;/span&gt; into &lt;span class="caps"&gt;DB&lt;/span&gt; table, otherwise go to&amp;nbsp;1.&lt;/li&gt;
&lt;/ol&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="n"&gt;shortlink&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;char&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;7&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;NOT&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;NULL&lt;/span&gt;
&lt;span class="n"&gt;expiration_in_minutes&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;NOT&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;NULL&lt;/span&gt;
&lt;span class="n"&gt;created_at&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;datetime&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;NOT&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;NULL&lt;/span&gt;
&lt;span class="n"&gt;paste_path&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;varchar&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;255&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;NOT&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;NULL&lt;/span&gt;
&lt;span class="k"&gt;PRIMARY&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;KEY&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;shortlink&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;CREATE&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;INDEX&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;shortlink_idx&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;ON&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;pastes&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;shortlink&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;USING&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;BTREE&lt;/span&gt;&lt;span class="p"&gt;;;&lt;/span&gt;
&lt;span class="k"&gt;CREATE&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;INDEX&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;created_at_idx&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;ON&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;pastes&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;created_at&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;USING&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;BTREE&lt;/span&gt;&lt;span class="p"&gt;;;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;div class="section" id="unique-url-path-generation"&gt;
&lt;h4&gt;&lt;a class="toc-backref" href="#toc-entry-45"&gt;Unique &lt;span class="caps"&gt;URL&lt;/span&gt; path&amp;nbsp;generation&lt;/a&gt;&lt;/h4&gt;
&lt;ol class="arabic simple"&gt;
&lt;li&gt;Generate a random string md5(&amp;#8220;user &lt;span class="caps"&gt;IP&lt;/span&gt; address string&amp;#8221; + &amp;#8220;timestamp&amp;nbsp;string&amp;#8221;)&lt;/li&gt;
&lt;li&gt;Encode string from 1 with &lt;cite&gt;Base 62&lt;/cite&gt; (like Base 64 but without escape&amp;nbsp;characters)&lt;/li&gt;
&lt;/ol&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;base_encode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;num&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="mi"&gt;62&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;digits&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
    &lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="n"&gt;num&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
      &lt;span class="n"&gt;remainder&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;modulo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;num&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;digits&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;remainder&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="n"&gt;num&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;divide&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;num&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;digits&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;digits&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;reverse&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;ol class="arabic simple" start="3"&gt;
&lt;li&gt;Get first 7 characters from base62 string: it&amp;#8217;s 62**7 possible
short paths (which is much more than we need in our 3 years plan,
i.e. 360 million&amp;nbsp;URLs)&lt;/li&gt;
&lt;/ol&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;base_encode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;md5&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ip_address&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="n"&gt;timestamp&lt;/span&gt;&lt;span class="p"&gt;))[:&lt;/span&gt;&lt;span class="n"&gt;URL_LENGTH&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;div class="section" id="request-write-api"&gt;
&lt;h4&gt;&lt;a class="toc-backref" href="#toc-entry-46"&gt;Request Write &lt;span class="caps"&gt;API&lt;/span&gt;&lt;/a&gt;&lt;/h4&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;$&lt;span class="w"&gt; &lt;/span&gt;curl&lt;span class="w"&gt; &lt;/span&gt;-X&lt;span class="w"&gt; &lt;/span&gt;POST&lt;span class="w"&gt; &lt;/span&gt;https://pastebin.com/api/v1.0/paste&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="w"&gt;       &lt;/span&gt;--data&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;{ &amp;quot;expiration_in_minutes&amp;quot;: &amp;quot;60&amp;quot;, &amp;quot;paste_contents&amp;quot;: &amp;quot;My paste&amp;quot; }&amp;#39;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;div class="section" id="write-api-response"&gt;
&lt;h4&gt;&lt;a class="toc-backref" href="#toc-entry-47"&gt;Write &lt;span class="caps"&gt;API&lt;/span&gt;&amp;nbsp;Response&lt;/a&gt;&lt;/h4&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;shortlink&amp;quot;&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;xJlsmeT&amp;quot;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;div class="section" id="request-read-api"&gt;
&lt;h4&gt;&lt;a class="toc-backref" href="#toc-entry-48"&gt;Request Read &lt;span class="caps"&gt;API&lt;/span&gt;&lt;/a&gt;&lt;/h4&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;$&lt;span class="w"&gt; &lt;/span&gt;curl&lt;span class="w"&gt; &lt;/span&gt;https://pastebin.com/api/v1.0/paste?shortlink&lt;span class="o"&gt;=&lt;/span&gt;xJlsmeT
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;div class="section" id="read-api-reponse"&gt;
&lt;h4&gt;&lt;a class="toc-backref" href="#toc-entry-49"&gt;Read &lt;span class="caps"&gt;API&lt;/span&gt;&amp;nbsp;Reponse&lt;/a&gt;&lt;/h4&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;paste&amp;quot;&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;My paste&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;created_at&amp;quot;&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;YYYY-MM-DD HH:MM:SS&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;expiration_in_minutes&amp;quot;&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;60&amp;quot;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;div class="section" id="statistics"&gt;
&lt;h4&gt;&lt;a class="toc-backref" href="#toc-entry-50"&gt;Statistics&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;Since we don&amp;#8217;t need realtime analytics, let&amp;#8217;s use Nginx logs and process them in&amp;nbsp;MapReduce&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;HitCounts&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;MRJob&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;extract_url&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;line&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;Extract the generated url from the log line.&amp;quot;&amp;quot;&amp;quot;&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;extract_year_month&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;line&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;Return the year and month portions of the timestamp.&amp;quot;&amp;quot;&amp;quot;&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;mapper&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;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;line&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="sd"&gt;&amp;quot;&amp;quot;&amp;quot;Parse each log line, extract and transform relevant lines.&lt;/span&gt;

&lt;span class="sd"&gt;        Emit key value pairs of the form:&lt;/span&gt;

&lt;span class="sd"&gt;        (2016-01, url0), 1&lt;/span&gt;
&lt;span class="sd"&gt;        (2016-01, url0), 1&lt;/span&gt;
&lt;span class="sd"&gt;        (2016-01, url1), 1&lt;/span&gt;
&lt;span class="sd"&gt;        &amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;
        &lt;span class="n"&gt;url&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;extract_url&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;line&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;period&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;extract_year_month&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;line&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;yield&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;period&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;reducer&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;key&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="w"&gt;        &lt;/span&gt;&lt;span class="sd"&gt;&amp;quot;&amp;quot;&amp;quot;Sum values for each key.&lt;/span&gt;

&lt;span class="sd"&gt;        (2016-01, url0), 2&lt;/span&gt;
&lt;span class="sd"&gt;        (2016-01, url1), 1&lt;/span&gt;
&lt;span class="sd"&gt;        &amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;
        &lt;span class="k"&gt;yield&lt;/span&gt; &lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;sum&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;values&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="section" id="sources-and-useful-links"&gt;
&lt;h2&gt;&lt;a class="toc-backref" href="#toc-entry-51"&gt;Sources and useful&amp;nbsp;links&lt;/a&gt;&lt;/h2&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;a class="reference external" href="https://www.toptal.com/python/interview-questions"&gt;Toptal Python Interview&amp;nbsp;Questions&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Python Interview Questions (&lt;a class="reference external" href="https://luminousmen.com/post/6"&gt;junior&lt;/a&gt;, &lt;a class="reference external" href="https://luminousmen.com/post/7"&gt;middle&lt;/a&gt;, &lt;a class="reference external" href="https://luminousmen.com/post/8"&gt;senior&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;&lt;a class="reference external" href="https://github.com/checkcheckzz/system-design-interview"&gt;System design&amp;nbsp;interview&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="reference external" href="https://docs.python.org/3.8/reference/index.html"&gt;The Python Language&amp;nbsp;Reference&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="reference external" href="http://www.postgresqltutorial.com/"&gt;PostgreSQL&amp;nbsp;Tutorial&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
</content><category term="Blog"></category><category term="python"></category><category term="interview"></category></entry><entry><title>Вопросы и ответы для собеседования веб-разработчика на Python</title><link href="https://blog.pilosus.org/posts/2019/12/08/python-interview-questions-ru/" rel="alternate"></link><published>2019-12-08T14:57:00+01:00</published><updated>2020-04-18T15:45:00+02:00</updated><author><name>Vitaly Samigullin</name></author><id>tag:blog.pilosus.org,2019-12-08:/posts/2019/12/08/python-interview-questions-ru/</id><summary type="html">&lt;p class="first last"&gt;Вопросы и ответы для технического собеседования&amp;nbsp;Python-разработчика&lt;/p&gt;
</summary><content type="html">&lt;p&gt;Ниже вы найдете принципы проведения и вопросы для технического
собеседования на позицию Python-разработчика. Хотя вопросы довольно
универсальны, больше всего они подойдут для области&amp;nbsp;веб-разработки.&lt;/p&gt;
&lt;p&gt;Вопросы прошли испытания боем: с их помощью было прособеседовано
больше 35 кандидатов. Из них 3 (2 экс-Яндекс) стали частью моей
команды в 2018 году, и еще 1 кандидат вышел в параллельную&amp;nbsp;команду.&lt;/p&gt;
&lt;p&gt;Вопросы получили положительный отклик коллег: люди используют их в
каком-то объеме для собеседований в своих компаниях. Я также
использую эти вопросы для консультации людей, которые готовятся к
прохождению технического&amp;nbsp;собеседования.&lt;/p&gt;
&lt;p&gt;В то же время вопросы едва ли можно считать уникальными. Здесь нет
никаких профессиональных тайн. На мой взгляд, основное преимущество
вопросов &amp;#8212; их система и принципы. На их основе можно составлять свои
интересные и полезные вопросы для проведения собеседования, скроенные
для ваших нужд. Или готовиться к техническому интервью в качестве&amp;nbsp;кандидата.&lt;/p&gt;
&lt;div class="contents topic" id="topic-1"&gt;
&lt;p class="topic-title"&gt;Содержание&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;a class="reference internal" href="#section-1" id="toc-entry-1"&gt;Введение&lt;/a&gt;&lt;ul&gt;
&lt;li&gt;&lt;a class="reference internal" href="#section-2" id="toc-entry-2"&gt;Предпосылки&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="reference internal" href="#section-3" id="toc-entry-3"&gt;Отбор&amp;nbsp;кандидатов&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="reference internal" href="#section-4" id="toc-entry-4"&gt;Гуманитарные&amp;nbsp;вопросы&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a class="reference internal" href="#python-1" id="toc-entry-5"&gt;Python&lt;/a&gt;&lt;ul&gt;
&lt;li&gt;&lt;a class="reference internal" href="#section-5" id="toc-entry-6"&gt;Проверка знаний, как развивается&amp;nbsp;язык&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="reference internal" href="#python-2" id="toc-entry-7"&gt;Основные типы и структуры данных в&amp;nbsp;Python&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="reference internal" href="#section-6" id="toc-entry-8"&gt;Порядок вычисления дефолтных аргументов&amp;nbsp;функций&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="reference internal" href="#section-7" id="toc-entry-9"&gt;Что такое&amp;nbsp;декоратор?&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="reference internal" href="#stdout" id="toc-entry-10"&gt;Декоратор, выводящий в stdout время выполнения&amp;nbsp;функции&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="reference internal" href="#section-8" id="toc-entry-11"&gt;Применение&amp;nbsp;декоратора&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="reference internal" href="#section-9" id="toc-entry-12"&gt;Имя декорированной&amp;nbsp;функции&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="reference internal" href="#section-10" id="toc-entry-13"&gt;Декоратор для&amp;nbsp;генераторов&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="reference internal" href="#section-11" id="toc-entry-14"&gt;Декоратор с&amp;nbsp;аргументами&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="reference internal" href="#section-12" id="toc-entry-15"&gt;Переменные&amp;nbsp;класса&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="reference internal" href="#section-13" id="toc-entry-16"&gt;Слайсы в&amp;nbsp;списках&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="reference internal" href="#list-dict-comprehensions" id="toc-entry-17"&gt;Что такое list/dict&amp;nbsp;comprehensions?&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="reference internal" href="#generator-expressions" id="toc-entry-18"&gt;Generator&amp;nbsp;expressions&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="reference internal" href="#functools" id="toc-entry-19"&gt;Какие полезные функции из модуля functools ты&amp;nbsp;знаешь?&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="reference internal" href="#asyncio-multithreading-multiprocessing" id="toc-entry-20"&gt;Asyncio, multithreading,&amp;nbsp;multiprocessing&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="reference internal" href="#section-14" id="toc-entry-21"&gt;Экосистема&amp;nbsp;языка&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a class="reference internal" href="#section-15" id="toc-entry-22"&gt;Алгоритмы&lt;/a&gt;&lt;ul&gt;
&lt;li&gt;&lt;a class="reference internal" href="#section-16" id="toc-entry-23"&gt;Временная&amp;nbsp;сложность&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="reference internal" href="#lisp" id="toc-entry-24"&gt;Проверка сбалансированности скобок в&amp;nbsp;Lisp-программе&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a class="reference internal" href="#section-17" id="toc-entry-25"&gt;Базы данных&lt;/a&gt;&lt;ul&gt;
&lt;li&gt;&lt;a class="reference internal" href="#rdbms" id="toc-entry-26"&gt;Зачем нужна &lt;span class="caps"&gt;RDBMS&lt;/span&gt;?&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="reference internal" href="#mysql-postgresql" id="toc-entry-27"&gt;Запросы в&amp;nbsp;MySQL/PostgreSQL&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="reference internal" href="#nosql" id="toc-entry-28"&gt;Общие представления о&amp;nbsp;NoSQL&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="reference internal" href="#redis" id="toc-entry-29"&gt;Общие представления о&amp;nbsp;Redis&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a class="reference internal" href="#section-18" id="toc-entry-30"&gt;Тестирование&lt;/a&gt;&lt;ul&gt;
&lt;li&gt;&lt;a class="reference internal" href="#unit" id="toc-entry-31"&gt;Общие представления о&amp;nbsp;unit-тестировании&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="reference internal" href="#mysql" id="toc-entry-32"&gt;Как протестировать функцию, которая изменяет запись в&amp;nbsp;MySQL?&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a class="reference internal" href="#devops" id="toc-entry-33"&gt;DevOps/Администрирование&lt;/a&gt;&lt;ul&gt;
&lt;li&gt;&lt;a class="reference internal" href="#google-com" id="toc-entry-34"&gt;Проблема с нечитаемым ответом&amp;nbsp;google.com&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="reference internal" href="#ip-nginx" id="toc-entry-35"&gt;Топ &lt;span class="caps"&gt;IP&lt;/span&gt; адресов из лога &lt;span class="caps"&gt;NGINX&lt;/span&gt;&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="reference internal" href="#docker" id="toc-entry-36"&gt;Docker&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a class="reference internal" href="#section-19" id="toc-entry-37"&gt;Архитектура&lt;/a&gt;&lt;ul&gt;
&lt;li&gt;&lt;a class="reference internal" href="#pastebin" id="toc-entry-38"&gt;Проектирование сервиса копипасты pastebin&lt;/a&gt;&lt;ul&gt;
&lt;li&gt;&lt;a class="reference internal" href="#section-20" id="toc-entry-39"&gt;Бизнес-описание&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="reference internal" href="#section-21" id="toc-entry-40"&gt;Ограничения и&amp;nbsp;допущения&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="reference internal" href="#section-22" id="toc-entry-41"&gt;Ответить соискателю, если&amp;nbsp;спросит&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="reference internal" href="#section-23" id="toc-entry-42"&gt;Крупноблочная схема&amp;nbsp;архитектуры&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="reference internal" href="#write-api" id="toc-entry-43"&gt;Write &lt;span class="caps"&gt;API&lt;/span&gt;&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="reference internal" href="#section-24" id="toc-entry-44"&gt;Создание уникального&amp;nbsp;урла&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="reference internal" href="#write-api-1" id="toc-entry-45"&gt;Запрос к Write &lt;span class="caps"&gt;API&lt;/span&gt;&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="reference internal" href="#write-api-2" id="toc-entry-46"&gt;Ответ Write &lt;span class="caps"&gt;API&lt;/span&gt;&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="reference internal" href="#read-api" id="toc-entry-47"&gt;Запрос к Read &lt;span class="caps"&gt;API&lt;/span&gt;&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="reference internal" href="#read-api-1" id="toc-entry-48"&gt;Ответ Read &lt;span class="caps"&gt;API&lt;/span&gt;&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="reference internal" href="#section-25" id="toc-entry-49"&gt;Аналитика&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a class="reference internal" href="#section-26" id="toc-entry-50"&gt;Полезные&amp;nbsp;ссылки&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="reference internal" href="#errata" id="toc-entry-51"&gt;Errata&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class="section" id="section-1"&gt;
&lt;h2&gt;&lt;a class="toc-backref" href="#toc-entry-1"&gt;Введение&lt;/a&gt;&lt;/h2&gt;
&lt;div class="section" id="section-2"&gt;
&lt;h3&gt;&lt;a class="toc-backref" href="#toc-entry-2"&gt;Предпосылки&lt;/a&gt;&lt;/h3&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;Разработка софта больше не искусство, не дело жизни, а обычная, хотя
и высококвалифицированная офисная работа. Поэтому особенно ценно в
кандидате то, как он работает в команде, как справляется с
повседневной работой. Как бы банально и избито это ни звучало. А вот
гениальные технические озарения и хитрые алгоритмы, на втором
плане. Те, для кого на первом, сами знают, каким должно быть
техническое&amp;nbsp;собеседование.&lt;/li&gt;
&lt;li&gt;Алгоритмическое собеседование это аналог &lt;span class="caps"&gt;IQ&lt;/span&gt;-тестов, изобретенных
американскими учеными в начале &lt;span class="caps"&gt;XX&lt;/span&gt; века для быстрой категоризации
больших масс призывников по родам войск. &lt;span class="caps"&gt;IQ&lt;/span&gt; дает представление об
интеллекте человека, но не исключает ошибок. Умение решать
алгоритмические задачи тоже дает представление об интеллекте
человека, но не лишено недостатков. Алгоритмические собеседования
нужны крупным компаниям, которым требуется быстро отобрать нужных
людей из огромных масс кандидатов и установить на входе грейд и
зарплату. Они могут позволить себе ошибку ненайма, потому что
кандидаты конкурируют за позиции в этих компаниях, а не наоборот,
как большинстве других случаев. Не стройте из себя Google, если вы
им не&amp;nbsp;являетесь.&lt;/li&gt;
&lt;li&gt;Интеллект человека это не бинарное &amp;#8220;есть/нет&amp;#8221; и не
диапазон. Интеллект &amp;#8212; это что-то вроде швейцарского армейского
ножа-мультитула. В нем может быть острое и практичное лезвие для
повседневного использования, не слишком удобный штопор, которым все
же можно пользоваться, и бесполезная отвертка, которой не сделаешь
ничего. Можно ли дать интегральную оценку такому мультитулу?
Наверное, можно. Но непонятно, зачем. Если вы не Google,
конечно. Подбирайте нож под свои задачи и исходя из своих&amp;nbsp;возможностей.&lt;/li&gt;
&lt;li&gt;Собеседование нужно, чтобы понять, почему человек ушел (или готов
уйти) с текущего места работы, что он ищет на новом месте и что он
может предложить&amp;nbsp;команде.&lt;/li&gt;
&lt;li&gt;Ошибка найма обычно компенсируется испытательным сроком. Нужно
отнестись к нему серьезно и выдать задание на этот
период. Аналогично со стороны сотрудника. И все-таки ошибка найма &amp;#8212;
это ошибка, которая стоит вам времени и денег. Лучшее ее не
совершать. Аналогично для сотрудника. Поэтому врать про проект не
стоит. Но уметь его красиво преподнести нисколько не
зазорно. Напротив, в условиях конкуренции компаний за кандидатов,
это ваша прямая&amp;nbsp;обязанность.&lt;/li&gt;
&lt;li&gt;Не бывает двух одинаковых собеседований даже для кандидатов одного
уровня. Техническое собеседование это всегда импровизация, попытка
найти границы компетентности и сильные стороны кандидата за короткий
промежуток&amp;nbsp;времени.&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class="section" id="section-3"&gt;
&lt;h3&gt;&lt;a class="toc-backref" href="#toc-entry-3"&gt;Отбор&amp;nbsp;кандидатов&lt;/a&gt;&lt;/h3&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;Не все люди умеют составлять&amp;nbsp;резюме.&lt;/li&gt;
&lt;li&gt;Слишком высокие зарплатные ожидания у интересного кандидата – не
повод отказываться от собеседования. Всегда можно позадавать
вопросы, которые снизят неоправданные зарплатные ожидания. Или
подтвердят, что перед вами&amp;nbsp;гений.&lt;/li&gt;
&lt;li&gt;Интевьюер, несомненно, самый умный и замечательный. Но это не повод
забраковывать резюме с грамматическими ошибками (как учат вас горе
HRы), смешными формулировками с проявлениями юношеской неопытности и
всяким таким прочим. Задавать неоправданно сложные вопросы тоже не
стоит. Оставьте высокомерие при&amp;nbsp;себе.&lt;/li&gt;
&lt;li&gt;Не нужно бояться звать на собеседование человека, который раньше
работал дизайнером/юристом/прорабом на заводе, но переучился на
разработчика. Будем же честны, разрабатывать вашу скучную &lt;span class="caps"&gt;REST&lt;/span&gt; &lt;span class="caps"&gt;API&lt;/span&gt;
много ума не надо, а профильное образование нужно далеко не&amp;nbsp;везде.&lt;/li&gt;
&lt;li&gt;У кандидата может не быть 100% попадания в ваш технологический стэк,
что не помешает ему в нем очень быстро разобраться. Скорее всего,
неразумно отвергать кандидата с опытом работы с Flask 3 года, но без
опыта работы с Django, на ваш заурядный Django-проект. Ну&amp;nbsp;правда.&lt;/li&gt;
&lt;li&gt;Единственный способ быстро найти разработчика: провести как можно
больше&amp;nbsp;собеседований&lt;/li&gt;
&lt;li&gt;Среднее время, которое затратит интервьюер на 1 собеседование: ~2.5
часа (отсмотр резюме, подготовка к интервью, само интервью, общение
с &lt;span class="caps"&gt;HR&lt;/span&gt; и &lt;span class="caps"&gt;CTO&lt;/span&gt;).&lt;/li&gt;
&lt;li&gt;Не стоит делать техническое интервью дольше 2 часов. Идеально 1.5&amp;nbsp;часа.&lt;/li&gt;
&lt;li&gt;3 собеседования в неделю – это хороший темп. Больше – тяжело
(физически для интервьюера, для &lt;span class="caps"&gt;HR&lt;/span&gt; в смыле поиска кандидата и для
проекта в смысле отсутствия лида во время собеседований). Меньше –
подбор сотрудника&amp;nbsp;затянется.&lt;/li&gt;
&lt;li&gt;Среднее время поиска Python-разработчика зависит от проекта, грейда
и того, что может предложить ваша компания. В среднем 3 месяца от
размещения заявки в &lt;span class="caps"&gt;HR&lt;/span&gt; до первого дня работы нового сотрудника это&amp;nbsp;нормально.&lt;/li&gt;
&lt;li&gt;Ваш главный союзник в общегуманитарных вопросах собеседования - ваш
&lt;span class="caps"&gt;HR&lt;/span&gt;.&lt;/li&gt;
&lt;li&gt;Не все разработчики – это тихие интроверты. Есть достаточное
количество людей, которые умеют говорить, продавать себя и выучивать
ответы для собеседований, не имея глубоких знаний. Не дайте себя
обмануть, обязательно давайте&amp;nbsp;задачки.&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class="section" id="section-4"&gt;
&lt;h3&gt;&lt;a class="toc-backref" href="#toc-entry-4"&gt;Гуманитарные&amp;nbsp;вопросы&lt;/a&gt;&lt;/h3&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;Что кандидат делал на прошлых местах работы. Какую позицию&amp;nbsp;занимал?&lt;/li&gt;
&lt;li&gt;Какой состав команды был на прошлых проектах (фронт, бэк, &lt;span class="caps"&gt;QA&lt;/span&gt;,
админы,&amp;nbsp;менеджеры)?&lt;/li&gt;
&lt;li&gt;Как был организован процесс планирования, постановки задач и ревью?
Какие в этом были плюсы и&amp;nbsp;минусы?&lt;/li&gt;
&lt;li&gt;Почему кандидат решил уйти (или уже ушел) с прошлого места&amp;nbsp;работы?&lt;/li&gt;
&lt;li&gt;Что хотелось бы от нового проекта? В смысле задач и&amp;nbsp;процессов?&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="section" id="python-1"&gt;
&lt;h2&gt;&lt;a class="toc-backref" href="#toc-entry-5"&gt;Python&lt;/a&gt;&lt;/h2&gt;
&lt;div class="section" id="section-5"&gt;
&lt;h3&gt;&lt;a class="toc-backref" href="#toc-entry-6"&gt;Проверка знаний, как развивается&amp;nbsp;язык&lt;/a&gt;&lt;/h3&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;Расскажи, что появилось в последней версии Python? (&lt;a class="reference external" href="https://docs.python.org/3/whatsnew/3.8.html"&gt;Python 3.8&lt;/a&gt; на момент создания&amp;nbsp;статьи)&lt;/li&gt;
&lt;li&gt;В чем состоят &lt;a class="reference external" href="https://docs.python.org/3.0/whatsnew/3.0.html"&gt;основные различия&lt;/a&gt; между Python 2.7 и&amp;nbsp;3.0&lt;/li&gt;
&lt;li&gt;Какие синтаксические изменения произошли в языке после Python 2.7?
(хочется услышать как минимум об asyncio и type&amp;nbsp;hinting)&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class="section" id="python-2"&gt;
&lt;h3&gt;&lt;a class="toc-backref" href="#toc-entry-7"&gt;Основные типы и структуры данных в&amp;nbsp;Python&lt;/a&gt;&lt;/h3&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;Назови основные типы данных в&amp;nbsp;Python&lt;/li&gt;
&lt;li&gt;Расскажи, какие структуры данных есть в&amp;nbsp;Python&lt;/li&gt;
&lt;li&gt;На какие два больших класса можно разделить типы/структуры данных? (mutable,&amp;nbsp;immutable)&lt;/li&gt;
&lt;li&gt;Зачем вообще нужны immutable объекты? Почему нельзя везде использовать&amp;nbsp;mutable?&lt;/li&gt;
&lt;li&gt;Какие строки в примере ниже выбросят исключение? Какие&amp;nbsp;исключения?&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="n"&gt;d&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;
&lt;span class="n"&gt;d&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;a&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
&lt;span class="n"&gt;d&lt;/span&gt;&lt;span class="p"&gt;[(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;
&lt;span class="n"&gt;d&lt;/span&gt;&lt;span class="p"&gt;[[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;]]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt; &lt;span class="c1"&gt;# TypeError: unhashable type: &amp;#39;list&amp;#39;&lt;/span&gt;
&lt;span class="n"&gt;d&lt;/span&gt;&lt;span class="p"&gt;[(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,[&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;])]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt; &lt;span class="c1"&gt;# TypeError: unhashable type: &amp;#39;list&amp;#39;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;div class="section" id="section-6"&gt;
&lt;h3&gt;&lt;a class="toc-backref" href="#toc-entry-8"&gt;Порядок вычисления дефолтных аргументов&amp;nbsp;функций&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;У нас есть&amp;nbsp;функция:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;fun_default&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;val&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;lst&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;list&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[])&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;list&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;lst&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;val&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;lst&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Какие значения будут иметь следующие списки после выполнения всего блока&amp;nbsp;присваиваний:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="n"&gt;l1&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;fun_default&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;l2&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;fun_default&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[])&lt;/span&gt;
&lt;span class="n"&gt;l3&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;fun_default&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;l1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;# [1, 3]&lt;/span&gt;
&lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;l2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;# [2]&lt;/span&gt;
&lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;l3&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;# [1, 3]&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Как поправить эту функцию, чтобы ответ был [1], [2], [3]&amp;nbsp;соответственно?&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;fun_default_fixed&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;val&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;lst&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;list&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;None&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;list&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;lst&lt;/span&gt; &lt;span class="ow"&gt;is&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;lst&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
    &lt;span class="n"&gt;lst&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;val&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;lst&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;div class="section" id="section-7"&gt;
&lt;h3&gt;&lt;a class="toc-backref" href="#toc-entry-9"&gt;Что такое&amp;nbsp;декоратор?&lt;/a&gt;&lt;/h3&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;Что такое&amp;nbsp;декоратор?&lt;/li&gt;
&lt;li&gt;Какие декораторы из коробки&amp;nbsp;знаешь?&lt;/li&gt;
&lt;li&gt;Расскажи про &lt;a class="reference external" href="https://docs.python.org/3.7/howto/descriptor.html"&gt;протокол descriptor&lt;/a&gt; (сложный вопрос, можно раскручивать&amp;nbsp;долго)&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class="section" id="stdout"&gt;
&lt;h3&gt;&lt;a class="toc-backref" href="#toc-entry-10"&gt;Декоратор, выводящий в stdout время выполнения&amp;nbsp;функции&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;У нас есть функция, которая обращается в БД. Хочется, чтобы каждый
вызов этой функции показывал в консоли время ее выполнения. Реализуй
этот функционал с помощью декоратора. Допущение: декорируемая функция
может иметь любое количество позиционных и непозиционных&amp;nbsp;аргументов.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;time&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;measure&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;func&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;wrapper&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;kwargs&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;Wrapper function&amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;
        &lt;span class="n"&gt;before&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;func&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;kwargs&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;after&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;time&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;Time elapsed: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;after&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;before&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; seconds&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;wrapper&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;div class="section" id="section-8"&gt;
&lt;h3&gt;&lt;a class="toc-backref" href="#toc-entry-11"&gt;Применение&amp;nbsp;декоратора&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Как можно применить декоратор к функции &lt;cite&gt;fun&lt;/cite&gt;, используя синтаксический сахар и без&amp;nbsp;него?&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="c1"&gt;# with syntactic sugar&lt;/span&gt;
&lt;span class="nd"&gt;@measure&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;fun&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;l&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;list&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="k"&gt;pass&lt;/span&gt;

&lt;span class="c1"&gt;# sugar free&lt;/span&gt;
&lt;span class="n"&gt;fun&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;measure&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;fun&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;fun&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;# function&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;div class="section" id="section-9"&gt;
&lt;h3&gt;&lt;a class="toc-backref" href="#toc-entry-12"&gt;Имя декорированной&amp;nbsp;функции&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Какое имя и &lt;cite&gt;docstring&lt;/cite&gt; будут у функции после применения к ней&amp;nbsp;декоратора?&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="n"&gt;fun&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="vm"&gt;__name__&lt;/span&gt;  &lt;span class="c1"&gt;# &amp;#39;wrapper&amp;#39;&lt;/span&gt;
&lt;span class="n"&gt;fun&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="vm"&gt;__doc__&lt;/span&gt;  &lt;span class="c1"&gt;# &amp;#39;Wrapper function&amp;#39;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Как вернуть задекорированной функции оригинальное имя и &lt;cite&gt;doctring&lt;/cite&gt;?&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;functools&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;wraps&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;time&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;measure&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;func&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="nd"&gt;@wraps&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;func&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;wrapper&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;kwargs&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;Wrapper function&amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;
        &lt;span class="n"&gt;before&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;func&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;kwargs&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;after&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;time&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;Time elapsed: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;after&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;before&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; seconds&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;wrapper&lt;/span&gt;

&lt;span class="nd"&gt;@measure&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;fun&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;l&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;list&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="w"&gt;    &lt;/span&gt;&lt;span class="sd"&gt;&amp;quot;&amp;quot;&amp;quot;My func&amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;
    &lt;span class="k"&gt;pass&lt;/span&gt;

&lt;span class="n"&gt;fun&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="vm"&gt;__name__&lt;/span&gt;  &lt;span class="c1"&gt;# &amp;#39;fun&amp;#39;&lt;/span&gt;
&lt;span class="n"&gt;fun&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="vm"&gt;__doc__&lt;/span&gt;  &lt;span class="c1"&gt;# &amp;#39;My func&amp;#39;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Как добиться того же эффекта без &lt;cite&gt;functools.wraps&lt;/cite&gt;?&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;time&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;measure&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;func&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;wrapper&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;kwargs&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;Wrapper function&amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;
        &lt;span class="n"&gt;before&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;func&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;kwargs&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;after&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;time&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;Time elapsed: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;after&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;before&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; seconds&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;
    &lt;span class="n"&gt;wrapper&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="vm"&gt;__doc__&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;func&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="vm"&gt;__doc__&lt;/span&gt;
    &lt;span class="n"&gt;wrapper&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="vm"&gt;__name__&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;func&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="vm"&gt;__name__&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;wrapper&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;div class="section" id="section-10"&gt;
&lt;h3&gt;&lt;a class="toc-backref" href="#toc-entry-13"&gt;Декоратор для&amp;nbsp;генераторов&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Какое время будет выводиться, если применить декоратор к&amp;nbsp;генератору?&lt;/p&gt;
&lt;p&gt;Допустим, мы применим написанный выше декоратор к такой&amp;nbsp;функции:&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;typing&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Generator&lt;/span&gt;&lt;span class="p"&gt;,&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;fun_gen&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;l&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;list&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;Generator&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="kc"&gt;None&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;None&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;l&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
          &lt;span class="k"&gt;yield&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Почему в консоли время, выведенное декоратором, такое маленькое?
Как замерить время итерации по всему&amp;nbsp;генератору?&lt;/p&gt;
&lt;p&gt;Этот вариант декоратора прокрутит генератор до конца и выведет общее время&amp;nbsp;итерирования:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;time&lt;/span&gt;
&lt;span class="kn"&gt;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;Generator&lt;/span&gt;&lt;span class="p"&gt;,&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;measure_gen&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;func&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;wrapper&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;kwargs&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;before&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;yield from&lt;/span&gt; &lt;span class="n"&gt;func&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;kwargs&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;after&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;time&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;Time elapsed: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;after&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;before&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; seconds&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;wrapper&lt;/span&gt;

&lt;span class="nd"&gt;@measure_gen&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;fun_gen&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;l&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;list&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;Generator&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="kc"&gt;None&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;None&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;l&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;yield&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;div class="section" id="section-11"&gt;
&lt;h3&gt;&lt;a class="toc-backref" href="#toc-entry-14"&gt;Декоратор с&amp;nbsp;аргументами&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Перепиши декоратор из задачи выше так, чтобы в него можно было
передать аргумент, который будет устанавливать пороговое значение,
начиная с которого будет выводиться время исполнения декорируемой&amp;nbsp;функции:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;measure&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;limit&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="c1"&gt;# Don&amp;#39;t forget to set limit when decorating a function!&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;outer_wrapper&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;func&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;inner_wrapper&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;kwargs&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
            &lt;span class="n"&gt;before&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
            &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;func&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;kwargs&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="n"&gt;after&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

            &lt;span class="n"&gt;elapsed&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;after&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;before&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;elapsed&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;limit&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;Time elapsed: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;after&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;before&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; seconds&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;inner_wrapper&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;outer_wrapper&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Покажи, как использовать этот декоратор с синтаксическим сахаром и без&amp;nbsp;него.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="c1"&gt;# syntactic sugar&lt;/span&gt;
&lt;span class="nd"&gt;@measure&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;fun&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;l&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;list&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="k"&gt;pass&lt;/span&gt;

&lt;span class="c1"&gt;# sugar free&lt;/span&gt;
&lt;span class="n"&gt;new_measure&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;measure&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;fun&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;new_measure&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;fun&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;fun&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;div class="section" id="section-12"&gt;
&lt;h3&gt;&lt;a class="toc-backref" href="#toc-entry-15"&gt;Переменные&amp;nbsp;класса&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Что такое переменная&amp;nbsp;класса?&lt;/p&gt;
&lt;p&gt;Даны следующие&amp;nbsp;классы:&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;Parent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;object&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Child1&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Parent&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;pass&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Child2&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Parent&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;pass&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Что выведут следующие&amp;nbsp;строки?&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Parent&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Child1&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Child2&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="c1"&gt;# 1 1 1&lt;/span&gt;

&lt;span class="n"&gt;Child1&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;
&lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Parent&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Child1&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Child2&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="c1"&gt;# 1 2 1&lt;/span&gt;

&lt;span class="n"&gt;Parent&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;
&lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Parent&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Child1&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Child2&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="c1"&gt;# 3 2 3&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;div class="section" id="section-13"&gt;
&lt;h3&gt;&lt;a class="toc-backref" href="#toc-entry-16"&gt;Слайсы в&amp;nbsp;списках&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Как исполнятся следующие&amp;nbsp;строки?&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="n"&gt;l&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

&lt;span class="n"&gt;l&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;  &lt;span class="c1"&gt;# [2]&lt;/span&gt;
&lt;span class="n"&gt;l&lt;/span&gt;&lt;span class="p"&gt;[:&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;  &lt;span class="c1"&gt;# [1, 2, 3, 4]&lt;/span&gt;
&lt;span class="n"&gt;l&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;:]&lt;/span&gt;  &lt;span class="c1"&gt;# []&lt;/span&gt;
&lt;span class="n"&gt;l&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;  &lt;span class="c1"&gt;# IndexError&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;div class="section" id="list-dict-comprehensions"&gt;
&lt;h3&gt;&lt;a class="toc-backref" href="#toc-entry-17"&gt;Что такое list/dict&amp;nbsp;comprehensions?&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Создай список квадрат четных чисел от [0, 10), используя list&amp;nbsp;comprehension&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nb"&gt;range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;То же самое, используя map и&amp;nbsp;filter?&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="nb"&gt;map&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;x&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;x&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="k"&gt;lambda&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;div class="section" id="generator-expressions"&gt;
&lt;h3&gt;&lt;a class="toc-backref" href="#toc-entry-18"&gt;Generator&amp;nbsp;expressions&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Как превратить первый пример в generator (generator&amp;nbsp;expression)?&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nb"&gt;range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;div class="section" id="functools"&gt;
&lt;h3&gt;&lt;a class="toc-backref" href="#toc-entry-19"&gt;Какие полезные функции из модуля functools ты&amp;nbsp;знаешь?&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Этот вопрос можно задавать после вопроса про декораторы и
functools.wraps. В functools много всякого интересного.  Хотелось бы
услышать про reduce и особенно про&amp;nbsp;partial.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="asyncio-multithreading-multiprocessing"&gt;
&lt;h3&gt;&lt;a class="toc-backref" href="#toc-entry-20"&gt;Asyncio, multithreading,&amp;nbsp;multiprocessing&lt;/a&gt;&lt;/h3&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;В чем разница между процессом и&amp;nbsp;трэдом?&lt;/li&gt;
&lt;li&gt;Есть лист из 1к урлов, по каждому из них нужно сделать &lt;span class="caps"&gt;GET&lt;/span&gt;
запрос. Как это сделать быстрее и дешевле? (&lt;span class="caps"&gt;IO&lt;/span&gt;-bound tasks -&amp;gt;
asyncio, Tornado etc или&amp;nbsp;multithreading)&lt;/li&gt;
&lt;li&gt;Есть 1к видеофайлов, которые надо отрендерить, или 1к программ,
которые надо скомпилировать. Как это сделать быстрее? (&lt;span class="caps"&gt;CPU&lt;/span&gt;-bound
tasks -&amp;gt;&amp;nbsp;multiprocessing)&lt;/li&gt;
&lt;li&gt;Как устроен asyncio или аналогичные библиотеки для асинхронного
программирования в Python? (огромный вопрос, который можно превратить
в целое собеседование. Хочется как минимум понимания на уровне
&lt;a class="reference external" href="https://trio.readthedocs.io/en/latest/tutorial.html"&gt;туториала Trio&lt;/a&gt;)&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class="section" id="section-14"&gt;
&lt;h3&gt;&lt;a class="toc-backref" href="#toc-entry-21"&gt;Экосистема&amp;nbsp;языка&lt;/a&gt;&lt;/h3&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;Какие решения существуют для выполнения фоновых отложенных задач? (&lt;cite&gt;celery&lt;/cite&gt;)&lt;/li&gt;
&lt;li&gt;Как сделать package из Python-кода? (wheels, eggs, в чем&amp;nbsp;отличия?)&lt;/li&gt;
&lt;li&gt;Какие есть решения для документирования кода на Python? (&lt;cite&gt;sphinx-doc&lt;/cite&gt;)&lt;/li&gt;
&lt;li&gt;Какие есть решения для документирования &lt;span class="caps"&gt;REST&lt;/span&gt; &lt;span class="caps"&gt;API&lt;/span&gt; (&lt;cite&gt;swagger&lt;/cite&gt;)&lt;/li&gt;
&lt;li&gt;Назови &lt;span class="caps"&gt;ORM&lt;/span&gt;, с которыми ты работал (&lt;cite&gt;SQLAlchemy&lt;/cite&gt;, &lt;cite&gt;Django &lt;span class="caps"&gt;ORM&lt;/span&gt;&lt;/cite&gt;)&lt;/li&gt;
&lt;li&gt;Какие статические анализаторы кода, линтеры для Python ты знаешь? (&lt;cite&gt;mypy&lt;/cite&gt;, &lt;cite&gt;flake8&lt;/cite&gt;)&lt;/li&gt;
&lt;li&gt;Какие web-фреймворки тебе известны? (&lt;cite&gt;Flask&lt;/cite&gt;, &lt;cite&gt;Django&lt;/cite&gt;, &lt;cite&gt;aiohttp&lt;/cite&gt;, &lt;cite&gt;sanic&lt;/cite&gt;,&amp;nbsp;etc.)&lt;/li&gt;
&lt;li&gt;Какие package management tools ты знаешь? (&lt;cite&gt;pip&lt;/cite&gt;, &lt;cite&gt;pipenv&lt;/cite&gt;, &lt;cite&gt;poetry&lt;/cite&gt;,&amp;nbsp;etc.)&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="section" id="section-15"&gt;
&lt;h2&gt;&lt;a class="toc-backref" href="#toc-entry-22"&gt;Алгоритмы&lt;/a&gt;&lt;/h2&gt;
&lt;div class="section" id="section-16"&gt;
&lt;h3&gt;&lt;a class="toc-backref" href="#toc-entry-23"&gt;Временная&amp;nbsp;сложность&lt;/a&gt;&lt;/h3&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;Что такое временная сложность (time&amp;nbsp;complexity)?&lt;/li&gt;
&lt;li&gt;Что такое большая&amp;nbsp;O?&lt;/li&gt;
&lt;li&gt;Какая временная сложность вот такой&amp;nbsp;операции:&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="c1"&gt;# O(n^2)&lt;/span&gt;
&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;n&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;y&lt;/span&gt; &lt;span class="ow"&gt;in&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;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Какова временная сложность для следующих&amp;nbsp;стейтментов?&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt; &lt;span class="c1"&gt;# где type(s) is list/tuple # O(n)&lt;/span&gt;
&lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt; &lt;span class="c1"&gt;# где type(s) is set        # O(1)&lt;/span&gt;
&lt;span class="nb"&gt;list&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;append&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;                             &lt;span class="c1"&gt;# O(1)&lt;/span&gt;
&lt;span class="nb"&gt;list&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;insert&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;                             &lt;span class="c1"&gt;# O(n)&lt;/span&gt;
&lt;span class="nb"&gt;list&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;index&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;                               &lt;span class="c1"&gt;# O(1)&lt;/span&gt;
&lt;span class="nb"&gt;list&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sort&lt;/span&gt;                                 &lt;span class="c1"&gt;# O(n log n)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Можно еще поспрашивать временную сложность операций над &lt;a class="reference external" href="https://wiki.python.org/moin/TimeComplexity"&gt;другими
структурами данных&lt;/a&gt;.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="lisp"&gt;
&lt;h3&gt;&lt;a class="toc-backref" href="#toc-entry-24"&gt;Проверка сбалансированности скобок в&amp;nbsp;Lisp-программе&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Я обожаю программировать на Racket – это диалект Lisp, который
славится большим &lt;a class="reference external" href="https://blog.pilosus.org/images/git-the-princess.png"&gt;количеством скобочек&lt;/a&gt;. Напиши функцию на Python,
которая будет получать на вход текст (строку) и возвращать &lt;cite&gt;True&lt;/cite&gt;,
если скобки в тексте сбалансированы и &lt;cite&gt;False&lt;/cite&gt; в противном&amp;nbsp;случае.&lt;/p&gt;
&lt;p&gt;Примеры сбалансированных&amp;nbsp;скобочек:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;()&amp;quot;&lt;/span&gt;
&lt;span class="s2"&gt;&amp;quot;(())&amp;quot;&lt;/span&gt;
&lt;span class="s2"&gt;&amp;quot;(custom-set-variables &amp;#39;(inhibit-startup-screen t) &amp;#39;(package-selected-packages (quote&lt;/span&gt;
&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sql&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;indent&lt;/span&gt; &lt;span class="n"&gt;magit&lt;/span&gt; &lt;span class="n"&gt;dockerfile&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;mode&lt;/span&gt; &lt;span class="n"&gt;yaml&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;mode&lt;/span&gt; &lt;span class="n"&gt;gitlab&lt;/span&gt; &lt;span class="n"&gt;markdown&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;mode&lt;/span&gt; &lt;span class="n"&gt;lorem&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;ipsum&lt;/span&gt; &lt;span class="n"&gt;jedi&lt;/span&gt;
&lt;span class="n"&gt;elpy&lt;/span&gt; &lt;span class="n"&gt;anaconda&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;mode&lt;/span&gt; &lt;span class="n"&gt;afternoon&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;theme&lt;/span&gt;&lt;span class="p"&gt;))))&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;
&lt;span class="s2"&gt;&amp;quot;(hello [world {!}])&amp;quot;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Примеры несбалансированных&amp;nbsp;скобочек:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;(&amp;quot;&lt;/span&gt;
&lt;span class="s2"&gt;&amp;quot;)(&amp;quot;&lt;/span&gt;
&lt;span class="s2"&gt;&amp;quot;())&amp;quot;&lt;/span&gt;
&lt;span class="s2"&gt;&amp;quot;({))&amp;quot;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;balance&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="sd"&gt;&amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;
&lt;span class="sd"&gt;  Return True if string&amp;#39;s parentheses are balanced, False otherwise&lt;/span&gt;

&lt;span class="sd"&gt;  &amp;gt;&amp;gt;&amp;gt; balance(&amp;quot;&amp;quot;)&lt;/span&gt;
&lt;span class="sd"&gt;  True&lt;/span&gt;
&lt;span class="sd"&gt;  &amp;gt;&amp;gt;&amp;gt; balance(&amp;quot;(())&amp;quot;)&lt;/span&gt;
&lt;span class="sd"&gt;  True&lt;/span&gt;
&lt;span class="sd"&gt;  &amp;gt;&amp;gt;&amp;gt; balance(&amp;quot;))((&amp;quot;)&lt;/span&gt;
&lt;span class="sd"&gt;  False&lt;/span&gt;
&lt;span class="sd"&gt;  &amp;gt;&amp;gt;&amp;gt; balance(&amp;quot;()()(())(()())&amp;quot;)&lt;/span&gt;
&lt;span class="sd"&gt;  True&lt;/span&gt;
&lt;span class="sd"&gt;  &amp;gt;&amp;gt;&amp;gt; balance(&amp;quot;(]&amp;quot;)&lt;/span&gt;
&lt;span class="sd"&gt;  False&lt;/span&gt;
&lt;span class="sd"&gt;  &amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;
  &lt;span class="n"&gt;opening&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;(&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;[&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;{&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="n"&gt;closing&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;)&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;]&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;}&amp;quot;&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="nb"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;zip&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;opening&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;closing&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
  &lt;span class="n"&gt;stack&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
  &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;ch&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
      &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;ch&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;opening&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
          &lt;span class="n"&gt;stack&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;mapping&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;ch&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
      &lt;span class="k"&gt;elif&lt;/span&gt; &lt;span class="n"&gt;ch&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;closing&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
          &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;stack&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ch&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="n"&gt;stack&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;pop&lt;/span&gt;&lt;span class="p"&gt;()):&lt;/span&gt;
              &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;False&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;stack&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Если кандидату трудно придумать решение для скобочек разных типов,
можно упростить задание до поверки, например, только круглых&amp;nbsp;скобочек.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="section" id="section-17"&gt;
&lt;h2&gt;&lt;a class="toc-backref" href="#toc-entry-25"&gt;Базы&amp;nbsp;данных&lt;/a&gt;&lt;/h2&gt;
&lt;div class="section" id="rdbms"&gt;
&lt;h3&gt;&lt;a class="toc-backref" href="#toc-entry-26"&gt;Зачем нужна &lt;span class="caps"&gt;RDBMS&lt;/span&gt;?&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;У нас стартап из &lt;span class="caps"&gt;CTO&lt;/span&gt;, &lt;span class="caps"&gt;CEO&lt;/span&gt; и одного разработчика. Админов нету. Мы
пишем сервис аутентификации – регистрация пользотелей, проверка прав –
на Python. Зачем нам нужна БД? Почему бы не использовать текстовый
файл?  Стандартная библиотека языка имеет все необходимое для работы с
файлами. Записываем логин и хэш пароля при регистрации, читаем при
запросе доступа. Это просто. Не требует админитрирования, избавляет от
зависимостей.  При сложностях, можно переформулировать вопрос так –
какие существуют механизмы реляционной БД, достаточно сложные для их
самостоятельно реализации, но необходимые для нашего&amp;nbsp;случая?&lt;/p&gt;
&lt;p&gt;От кандидата хочется услышать упоминания следующих&amp;nbsp;механизмов:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;индекс&lt;/li&gt;
&lt;li&gt;связи&amp;nbsp;таблиц&lt;/li&gt;
&lt;li&gt;транзакции&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Про каждый механизм можно поспрашивать отдельно. Например, про индекс
можно&amp;nbsp;спросить:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;временная сложность для поиска по&amp;nbsp;индексу&lt;/li&gt;
&lt;li&gt;в таблице &lt;cite&gt;candidates&lt;/cite&gt; есть колонка &lt;cite&gt;is_interviewed&lt;/cite&gt; с возможными
значениями 0 и 1. какой прирост произодительности для поиска по этой
колонке даст индекс для&amp;nbsp;нее?&lt;/li&gt;
&lt;li&gt;что такое составные индексы? есть ли какие-то правила для запросов
по колонкам, участвующим в составном&amp;nbsp;индексе?&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class="section" id="mysql-postgresql"&gt;
&lt;h3&gt;&lt;a class="toc-backref" href="#toc-entry-27"&gt;Запросы в&amp;nbsp;MySQL/PostgreSQL&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;У нас есть сервис по типу iTunes, где можно арендовать/покупать
видео. Вот схема таблиц реляционной базы, положим, в&amp;nbsp;PostgreSQL&lt;/p&gt;
&lt;div class="figure"&gt;
&lt;a class="reference external image-reference" href="https://blog.pilosus.org/images/sql-tables.pdf"&gt;&lt;img alt="Схема таблиц реляционной базы данных для технического собеседования" class="responsive" src="https://blog.pilosus.org/images/sql-tables.jpg" /&gt;&lt;/a&gt;
&lt;/div&gt;
&lt;p&gt;Напиши запрос, который выведет топ-20 фильмов, в которых играет актер John Doe. Запрос должен&amp;nbsp;выводить:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;Название&amp;nbsp;фильма&lt;/li&gt;
&lt;li&gt;Язык&amp;nbsp;издания&lt;/li&gt;
&lt;li&gt;Год&amp;nbsp;выпуска&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Результаты нужно упорядочить по убыванию года&amp;nbsp;выпуска.&lt;/p&gt;
&lt;p&gt;(Если нужна подсказка) на основе таблиц: &lt;cite&gt;film&lt;/cite&gt;, &lt;cite&gt;language&lt;/cite&gt;, &lt;cite&gt;film_actor&lt;/cite&gt;, &lt;cite&gt;actor&lt;/cite&gt;&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;f&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;title&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;l&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;f&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;release_year&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;film&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;AS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;
&lt;span class="k"&gt;JOIN&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;language&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;AS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;l&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;ON&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;l&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;language_id&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;f&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;language_id&lt;/span&gt;
&lt;span class="k"&gt;JOIN&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;film_actor&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;AS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;fa&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;ON&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;fa&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;film_id&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;f&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;film_id&lt;/span&gt;
&lt;span class="k"&gt;JOIN&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;actor&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;AS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;ON&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;actor_id&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;fa&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;actor_id&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;a&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;first_name&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;John&amp;#39;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;AND&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;last_name&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;Doe&amp;#39;&lt;/span&gt;
&lt;span class="k"&gt;ORDER&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;BY&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;release_year&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;DESC&lt;/span&gt;
&lt;span class="k"&gt;LIMIT&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Напиши запрос, который выведет топ-10 клиентов, которые оставили в
сервисе больше всего денег, но не менее 100 у.е. Запрос должен&amp;nbsp;выводить:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;span class="caps"&gt;ID&lt;/span&gt; и полное имя&amp;nbsp;клиента&lt;/li&gt;
&lt;li&gt;сумму всех заказов&amp;nbsp;юзера&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;(Если нужна подсказка) на основе таблиц: &lt;cite&gt;payment&lt;/cite&gt;, &lt;cite&gt;customer&lt;/cite&gt;&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="k"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;customer_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;first_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;last_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;SUM&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;amount&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;AS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;money&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;payment&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;AS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt;
&lt;span class="k"&gt;JOIN&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;customer&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;AS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;c&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;ON&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;customer_id&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;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;customer_id&lt;/span&gt;
&lt;span class="k"&gt;GROUP&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;BY&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;customer_id&lt;/span&gt;
&lt;span class="k"&gt;HAVING&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;SUM&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;amount&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;
&lt;span class="k"&gt;ORDER&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;BY&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;money&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;DESC&lt;/span&gt;
&lt;span class="k"&gt;LIMIT&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;div class="section" id="nosql"&gt;
&lt;h3&gt;&lt;a class="toc-backref" href="#toc-entry-28"&gt;Общие представления о&amp;nbsp;NoSQL&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Что такое NoSQL БД? Какие типы NoSQL БД выделяют?&amp;nbsp;Примеры&lt;/p&gt;
&lt;p&gt;Хочется услышать хотя бы самые общие вещи: NoSQL БД &amp;#8212; коллекция
данных в одном из форматов (см. типы ниже). Данные денормализованы,
&lt;span class="caps"&gt;JOIN&lt;/span&gt; реализуют обычно на уровне&amp;nbsp;приложения.&lt;/p&gt;
&lt;p&gt;Типы NoSQL&amp;nbsp;БД:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;key-value store (реализация hash table: Redis,&amp;nbsp;AeroSpike)&lt;/li&gt;
&lt;li&gt;document-store (аналогично key-value, но в качестве значения документ, например, &lt;span class="caps"&gt;JSON&lt;/span&gt;:&amp;nbsp;MongoDB)&lt;/li&gt;
&lt;li&gt;wide column store (HBase,&amp;nbsp;Cassandra)&lt;/li&gt;
&lt;li&gt;graph database&amp;nbsp;(Neo4j)&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class="section" id="redis"&gt;
&lt;h3&gt;&lt;a class="toc-backref" href="#toc-entry-29"&gt;Общие представления о&amp;nbsp;Redis&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Что такое Redis? Почему его так любят использовать для хранения кэшей?
Чем, с точки зрения хранения данных, он принципиально отличается от,
например, MySQL? Что произойдет с данными в Redis, если прозойдет
рестарт сервера?  Какие существуют механизмы для High Availability в&amp;nbsp;Redis?&lt;/p&gt;
&lt;p&gt;Ожидания от ответа: Redis – это in-memory key-value БД. У нее есть
механизм снапшотов данных из памяти на диск. Для &lt;span class="caps"&gt;HA&lt;/span&gt; используют,
например, Redis&amp;nbsp;Sentinel&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="section" id="section-18"&gt;
&lt;h2&gt;&lt;a class="toc-backref" href="#toc-entry-30"&gt;Тестирование&lt;/a&gt;&lt;/h2&gt;
&lt;div class="section" id="unit"&gt;
&lt;h3&gt;&lt;a class="toc-backref" href="#toc-entry-31"&gt;Общие представления о&amp;nbsp;unit-тестировании&lt;/a&gt;&lt;/h3&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;Что это&amp;nbsp;такое?&lt;/li&gt;
&lt;li&gt;Чем отличается от интеграционного, функционального&amp;nbsp;тестирования?&lt;/li&gt;
&lt;li&gt;Какие библиотеки для Python использовал для этого? (&lt;cite&gt;unittest&lt;/cite&gt;,
&lt;cite&gt;pytest&lt;/cite&gt;, в чем&amp;nbsp;разница?)&lt;/li&gt;
&lt;li&gt;Когда нужно писать unit-тест? (в существующем проекте, в новом
проекте, при починке бага, при реализации&amp;nbsp;фичи)&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class="section" id="mysql"&gt;
&lt;h3&gt;&lt;a class="toc-backref" href="#toc-entry-32"&gt;Как протестировать функцию, которая изменяет запись в&amp;nbsp;MySQL?&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Есть функция, которая выполняет &lt;cite&gt;&lt;span class="caps"&gt;INSERT&lt;/span&gt;&lt;/cite&gt; в тестовой базе данных MySQL,
которой пользуются все разработчики команды. Нужно написать тест на
эту функцию. Но не хочется вносить изменения в общую базу. Как можно
решить эту&amp;nbsp;проблему?&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;mock&lt;/li&gt;
&lt;li&gt;transaction&amp;nbsp;rollback&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="section" id="devops"&gt;
&lt;h2&gt;&lt;a class="toc-backref" href="#toc-entry-33"&gt;DevOps/Администрирование&lt;/a&gt;&lt;/h2&gt;
&lt;div class="section" id="google-com"&gt;
&lt;h3&gt;&lt;a class="toc-backref" href="#toc-entry-34"&gt;Проблема с нечитаемым ответом&amp;nbsp;google.com&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Я захожу на &lt;cite&gt;https://www.google.com&lt;/cite&gt; в браузере, открываю исходный код
и вижу вполне читаемый&amp;nbsp;текст.&lt;/p&gt;
&lt;p&gt;Но если я делаю такой запрос через &lt;cite&gt;curl&lt;/cite&gt; то вижу нечто нечитаемое,
похожее на бинарный&amp;nbsp;файл:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&amp;gt;&lt;span class="w"&gt; &lt;/span&gt;GET&lt;span class="w"&gt; &lt;/span&gt;/&lt;span class="w"&gt; &lt;/span&gt;HTTP/2
&amp;gt;&lt;span class="w"&gt; &lt;/span&gt;Host:&lt;span class="w"&gt; &lt;/span&gt;www.google.com
&amp;gt;&lt;span class="w"&gt; &lt;/span&gt;User-Agent:&lt;span class="w"&gt; &lt;/span&gt;curl/7.58.0
&amp;gt;&lt;span class="w"&gt; &lt;/span&gt;Accept:&lt;span class="w"&gt; &lt;/span&gt;*/*
&amp;gt;&lt;span class="w"&gt; &lt;/span&gt;Accept-Encoding:&lt;span class="w"&gt; &lt;/span&gt;gzip,&lt;span class="w"&gt; &lt;/span&gt;deflate

%�j��~d+.K�W��me&lt;span class="sb"&gt;`&lt;/span&gt;�,x�x�m�G�67��&lt;span class="sb"&gt;`&lt;/span&gt;&lt;span class="nv"&gt;$#&lt;/span&gt;������~&lt;span class="o"&gt;{&lt;/span&gt;d���&lt;span class="o"&gt;]&lt;/span&gt;�.&lt;span class="err"&gt;&amp;#39;&lt;/span&gt;����
����Q���Mʥu���&amp;lt;�O&lt;span class="o"&gt;)&lt;/span&gt;,�&lt;span class="p"&gt;&amp;amp;&lt;/span&gt;�����,���E�.���_�
&lt;span class="p"&gt;&amp;amp;&lt;/span&gt;�I&lt;span class="se"&gt;\�&lt;/span&gt;�p��#&lt;span class="o"&gt;]&lt;/span&gt;�,�����K+����p�vk
j���k&lt;span class="s2"&gt;&amp;quot;�fe�=�`�J4�I/��m6{�E���;3+�v�BT1T��(W��J��m�yO��&lt;/span&gt;
&lt;span class="s2"&gt;�j��&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Почему так&amp;nbsp;происходит?&lt;/p&gt;
&lt;p&gt;Для&amp;nbsp;интервьюера:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;curl&lt;span class="w"&gt; &lt;/span&gt;-v&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;https://www.google.com&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;--header&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Accept-Encoding: gzip, deflate&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;--output&lt;span class="w"&gt; &lt;/span&gt;-
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;div class="section" id="ip-nginx"&gt;
&lt;h3&gt;&lt;a class="toc-backref" href="#toc-entry-35"&gt;Топ &lt;span class="caps"&gt;IP&lt;/span&gt; адресов из лога &lt;span class="caps"&gt;NGINX&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Наш сервис парсят боты. Это создает большую нагрузку, мешающую
нормальным пользователям. Мы решаем забанить ботов по &lt;span class="caps"&gt;IP&lt;/span&gt;-адресам. Для
этого мы хотим составить топ &lt;span class="caps"&gt;IP&lt;/span&gt;-адресов, с которых приходили
запросы. Мы просим админов сделать выгрузку логов за 1&amp;nbsp;час.&lt;/p&gt;
&lt;p&gt;С помощью стандартных &lt;span class="caps"&gt;GNU&lt;/span&gt; utils создай файл с топом &lt;span class="caps"&gt;IP&lt;/span&gt;-адресов в
порядке убывания частоты запросов.  Предположим, что &lt;span class="caps"&gt;IP&lt;/span&gt;-адрес
находится в 9-й колонке лога, колонки отделяются через &lt;cite&gt;t&lt;/cite&gt;&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;awk&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;{print $9}&amp;#39;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;nginx.log&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;sort&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;uniq&lt;span class="w"&gt; &lt;/span&gt;-c&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;awk&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;{print $1&amp;quot;,&amp;quot;$2}&amp;#39;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;sort&lt;span class="w"&gt; &lt;/span&gt;-r&lt;span class="w"&gt; &lt;/span&gt;-t,&lt;span class="w"&gt; &lt;/span&gt;-nk1&lt;span class="w"&gt; &lt;/span&gt;&amp;gt;&lt;span class="w"&gt; &lt;/span&gt;bots.csv
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;div class="section" id="docker"&gt;
&lt;h3&gt;&lt;a class="toc-backref" href="#toc-entry-36"&gt;Docker&lt;/a&gt;&lt;/h3&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;Что такое докер&amp;nbsp;контейнер?&lt;/li&gt;
&lt;li&gt;В чем отличие от virtualbox и других виртуальных&amp;nbsp;машин?&lt;/li&gt;
&lt;li&gt;Зачем использовать докер, если можно просто скопировать файлы на&amp;nbsp;машину?&lt;/li&gt;
&lt;li&gt;Почему нельзя просто использовать&amp;nbsp;virtualenv?&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="section" id="section-19"&gt;
&lt;h2&gt;&lt;a class="toc-backref" href="#toc-entry-37"&gt;Архитектура&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Parental advisory: highly opinionated content&amp;nbsp;(35+)&lt;/strong&gt;&lt;/p&gt;
&lt;div class="section" id="pastebin"&gt;
&lt;h3&gt;&lt;a class="toc-backref" href="#toc-entry-38"&gt;Проектирование сервиса копипасты&amp;nbsp;pastebin&lt;/a&gt;&lt;/h3&gt;
&lt;div class="section" id="section-20"&gt;
&lt;h4&gt;&lt;a class="toc-backref" href="#toc-entry-39"&gt;Бизнес-описание&lt;/a&gt;&lt;/h4&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;Пользователь сабмитит&amp;nbsp;текст&lt;/li&gt;
&lt;li&gt;Текст сохраняется, ему присваивается уникальный &lt;span class="caps"&gt;URL&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;По переходу по сгенерированному &lt;span class="caps"&gt;URL&lt;/span&gt; мы видим&amp;nbsp;текст&lt;/li&gt;
&lt;li&gt;Текст хранится какой-то срок (в&amp;nbsp;минутах)&lt;/li&gt;
&lt;li&gt;Сервис удаляет просроченные&amp;nbsp;тексты&lt;/li&gt;
&lt;li&gt;Сервис&amp;nbsp;высоконагруженый&lt;/li&gt;
&lt;li&gt;Нужна статистика для посещения различных урлов по&amp;nbsp;месяцам&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class="section" id="section-21"&gt;
&lt;h4&gt;&lt;a class="toc-backref" href="#toc-entry-40"&gt;Ограничения и&amp;nbsp;допущения&lt;/a&gt;&lt;/h4&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;Пасты только&amp;nbsp;текстовые&lt;/li&gt;
&lt;li&gt;Аналитика может быть не&amp;nbsp;реалтаймовой&lt;/li&gt;
&lt;li&gt;10 миллионов записей в месяц
(3.85 &lt;span class="caps"&gt;RPS&lt;/span&gt;)&lt;/li&gt;
&lt;li&gt;100 миллионов чтений в месяц
(38.5 &lt;span class="caps"&gt;RPS&lt;/span&gt;)&lt;/li&gt;
&lt;li&gt;Железо планируем на 3 года&amp;nbsp;вперед&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class="section" id="section-22"&gt;
&lt;h4&gt;&lt;a class="toc-backref" href="#toc-entry-41"&gt;Ответить соискателю, если&amp;nbsp;спросит&lt;/a&gt;&lt;/h4&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;Средний размер пасты&amp;nbsp;1Kb&lt;/li&gt;
&lt;li&gt;Короткая ссылка длиной 7 байт в&amp;nbsp;БД&lt;/li&gt;
&lt;li&gt;&lt;span class="caps"&gt;TTL&lt;/span&gt; ссылки в минутах 4 байта в&amp;nbsp;БД&lt;/li&gt;
&lt;li&gt;Время создания ссылки 5&amp;nbsp;байт&lt;/li&gt;
&lt;li&gt;Путь хранения файла пасты - 255&amp;nbsp;байт&lt;/li&gt;
&lt;li&gt;Итого: 1.27 Кбайт на 1&amp;nbsp;пасту&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;1.27 Гб пасты в месяц
За 3 года:
- 36 * 12.7 = 457 Гб пасты
- 36 * 10 = 360 млн&amp;nbsp;урлов&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="section-23"&gt;
&lt;h4&gt;&lt;a class="toc-backref" href="#toc-entry-42"&gt;Крупноблочная схема&amp;nbsp;архитектуры&lt;/a&gt;&lt;/h4&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;  Client
    |
    V
Load Balancer
    |
    V
 Web Server (Reverse Proxy, i.e. Nginx)
  |      |
  V      V
Write   Read                                Analytics (MapReduce)
API     API---------------------------       |         |
 |       |        |                  |       |         |
 |       V        |                  |       |         |
 |      Cache     |                  |       |         V
 ------------------------------      |       |       SQL for Analytics
 V                |            |     |       |
SQL-----------    |            V     V       V
 |_Master    |    |           Object (File) Store
 |_Slave     V    |
            SQL   V
 ^              Read-only Replica
 |
 |
Periodic Task
(remove expired)
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;div class="section" id="write-api"&gt;
&lt;h4&gt;&lt;a class="toc-backref" href="#toc-entry-43"&gt;Write &lt;span class="caps"&gt;API&lt;/span&gt;&lt;/a&gt;&lt;/h4&gt;
&lt;ol class="arabic simple"&gt;
&lt;li&gt;Генерит &lt;span class="caps"&gt;URL&lt;/span&gt;, проверяет уникальность в &lt;span class="caps"&gt;SQL&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;Если уникальное, делает &lt;span class="caps"&gt;INSERT&lt;/span&gt; в таблицу, иначе идет в п.&amp;nbsp;1&lt;/li&gt;
&lt;/ol&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="n"&gt;shortlink&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;char&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;7&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;NOT&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;NULL&lt;/span&gt;
&lt;span class="n"&gt;expiration_in_minutes&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;NOT&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;NULL&lt;/span&gt;
&lt;span class="n"&gt;created_at&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;datetime&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;NOT&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;NULL&lt;/span&gt;
&lt;span class="n"&gt;paste_path&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;varchar&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;255&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;NOT&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;NULL&lt;/span&gt;
&lt;span class="k"&gt;PRIMARY&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;KEY&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;shortlink&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;CREATE&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;INDEX&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;shortlink_idx&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;ON&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;pastes&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;shortlink&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;USING&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;BTREE&lt;/span&gt;&lt;span class="p"&gt;;;&lt;/span&gt;
&lt;span class="k"&gt;CREATE&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;INDEX&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;created_at_idx&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;ON&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;pastes&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;created_at&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;USING&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;BTREE&lt;/span&gt;&lt;span class="p"&gt;;;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;div class="section" id="section-24"&gt;
&lt;h4&gt;&lt;a class="toc-backref" href="#toc-entry-44"&gt;Создание уникального&amp;nbsp;урла&lt;/a&gt;&lt;/h4&gt;
&lt;ol class="arabic simple"&gt;
&lt;li&gt;Берем рандомную строку (или &lt;span class="caps"&gt;IP&lt;/span&gt; address юзера + timestamp) и вычисляем &lt;span class="caps"&gt;MD5&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;Кодируем &lt;cite&gt;Base 62&lt;/cite&gt; (как Base 64 но без&amp;nbsp;escape-символов)&lt;/li&gt;
&lt;/ol&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;base_encode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;num&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="mi"&gt;62&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;digits&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
    &lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="n"&gt;num&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
      &lt;span class="n"&gt;remainder&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;modulo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;num&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;digits&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;remainder&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="n"&gt;num&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;divide&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;num&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;digits&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;digits&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;reverse&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;От строки в Base 62 можем взять 7 символов это даст 62**7 возможных
значений (это намного больше чем 360 миллионов урлов, которые мы
планируем на 3&amp;nbsp;года)&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;base_encode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;md5&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ip_address&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="n"&gt;timestamp&lt;/span&gt;&lt;span class="p"&gt;))[:&lt;/span&gt;&lt;span class="n"&gt;URL_LENGTH&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;div class="section" id="write-api-1"&gt;
&lt;h4&gt;&lt;a class="toc-backref" href="#toc-entry-45"&gt;Запрос к Write &lt;span class="caps"&gt;API&lt;/span&gt;&lt;/a&gt;&lt;/h4&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;$&lt;span class="w"&gt; &lt;/span&gt;curl&lt;span class="w"&gt; &lt;/span&gt;-X&lt;span class="w"&gt; &lt;/span&gt;POST&lt;span class="w"&gt; &lt;/span&gt;https://pastebin.com/api/v1.0/paste&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="w"&gt;       &lt;/span&gt;--data&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;{ &amp;quot;expiration_in_minutes&amp;quot;: &amp;quot;60&amp;quot;, &amp;quot;paste_contents&amp;quot;: &amp;quot;My paste&amp;quot; }&amp;#39;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;div class="section" id="write-api-2"&gt;
&lt;h4&gt;&lt;a class="toc-backref" href="#toc-entry-46"&gt;Ответ Write &lt;span class="caps"&gt;API&lt;/span&gt;&lt;/a&gt;&lt;/h4&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;shortlink&amp;quot;&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;xJlsmeT&amp;quot;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;div class="section" id="read-api"&gt;
&lt;h4&gt;&lt;a class="toc-backref" href="#toc-entry-47"&gt;Запрос к Read &lt;span class="caps"&gt;API&lt;/span&gt;&lt;/a&gt;&lt;/h4&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;$&lt;span class="w"&gt; &lt;/span&gt;curl&lt;span class="w"&gt; &lt;/span&gt;https://pastebin.com/api/v1.0/paste?shortlink&lt;span class="o"&gt;=&lt;/span&gt;xJlsmeT
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;div class="section" id="read-api-1"&gt;
&lt;h4&gt;&lt;a class="toc-backref" href="#toc-entry-48"&gt;Ответ Read &lt;span class="caps"&gt;API&lt;/span&gt;&lt;/a&gt;&lt;/h4&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;paste&amp;quot;&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;My paste&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;created_at&amp;quot;&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;YYYY-MM-DD HH:MM:SS&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;expiration_in_minutes&amp;quot;&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;60&amp;quot;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;div class="section" id="section-25"&gt;
&lt;h4&gt;&lt;a class="toc-backref" href="#toc-entry-49"&gt;Аналитика&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;Поскольку не нужен реалтайм, будем брать логи с реверс прокси и
скартимливать их в&amp;nbsp;MapReduce&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;HitCounts&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;MRJob&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;extract_url&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;line&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;Extract the generated url from the log line.&amp;quot;&amp;quot;&amp;quot;&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;extract_year_month&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;line&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;Return the year and month portions of the timestamp.&amp;quot;&amp;quot;&amp;quot;&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;mapper&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;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;line&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="sd"&gt;&amp;quot;&amp;quot;&amp;quot;Parse each log line, extract and transform relevant lines.&lt;/span&gt;

&lt;span class="sd"&gt;        Emit key value pairs of the form:&lt;/span&gt;

&lt;span class="sd"&gt;        (2016-01, url0), 1&lt;/span&gt;
&lt;span class="sd"&gt;        (2016-01, url0), 1&lt;/span&gt;
&lt;span class="sd"&gt;        (2016-01, url1), 1&lt;/span&gt;
&lt;span class="sd"&gt;        &amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;
        &lt;span class="n"&gt;url&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;extract_url&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;line&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;period&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;extract_year_month&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;line&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;yield&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;period&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;reducer&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;key&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="w"&gt;        &lt;/span&gt;&lt;span class="sd"&gt;&amp;quot;&amp;quot;&amp;quot;Sum values for each key.&lt;/span&gt;

&lt;span class="sd"&gt;        (2016-01, url0), 2&lt;/span&gt;
&lt;span class="sd"&gt;        (2016-01, url1), 1&lt;/span&gt;
&lt;span class="sd"&gt;        &amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;
        &lt;span class="k"&gt;yield&lt;/span&gt; &lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;sum&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;values&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="section" id="section-26"&gt;
&lt;h2&gt;&lt;a class="toc-backref" href="#toc-entry-50"&gt;Полезные&amp;nbsp;ссылки&lt;/a&gt;&lt;/h2&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;a class="reference external" href="https://www.toptal.com/python/interview-questions"&gt;Toptal Python Interview&amp;nbsp;Questions&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Python Interview Questions (&lt;a class="reference external" href="https://luminousmen.com/post/6"&gt;junior&lt;/a&gt;, &lt;a class="reference external" href="https://luminousmen.com/post/7"&gt;middle&lt;/a&gt;, &lt;a class="reference external" href="https://luminousmen.com/post/8"&gt;senior&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;&lt;a class="reference external" href="https://github.com/checkcheckzz/system-design-interview"&gt;System design&amp;nbsp;interview&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="reference external" href="https://docs.python.org/3.8/reference/index.html"&gt;The Python Language&amp;nbsp;Reference&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="reference external" href="http://www.postgresqltutorial.com/"&gt;PostgreSQL&amp;nbsp;Tutorial&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class="section" id="errata"&gt;
&lt;h2&gt;&lt;a class="toc-backref" href="#toc-entry-51"&gt;Errata&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;2020-04-18. В функции проверки сбалансированности скобок исправлена
ошибка в маппинге. Для хранения ключей и значений использовались
структуры типа &lt;cite&gt;set&lt;/cite&gt; (не гарантируют сохранения порядка вставки
элементов). Исправлено на &lt;cite&gt;tuple&lt;/cite&gt;. Нашел ошибку Егор&amp;nbsp;Моренко.&lt;/p&gt;
&lt;/div&gt;
</content><category term="Blog"></category><category term="python"></category><category term="interview"></category><category term="ru"></category></entry><entry><title>PostgreSQL: update by spliting result set into evenly sized chunks using window function</title><link href="https://blog.pilosus.org/posts/2019/12/07/postgresql-update-rows-in-chunks/" rel="alternate"></link><published>2019-12-07T14:58:00+01:00</published><updated>2019-12-07T14:58:00+01:00</updated><author><name>Vitaly Samigullin</name></author><id>tag:blog.pilosus.org,2019-12-07:/posts/2019/12/07/postgresql-update-rows-in-chunks/</id><summary type="html">&lt;p class="first last"&gt;How do you update result set by spliting it into evenly
sized chunks using window function&amp;nbsp;row_number()&lt;/p&gt;
</summary><content type="html">&lt;p&gt;Sometimes you need to process your database data in batches. It may be
absolutely necessary to retrieve the rows from the database in your
application first, and then process it in the code iterating over the
results. In this case you split the result set from the database query
into chunks in you code like in the following Python&amp;nbsp;snippet:&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;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="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="n"&gt;Iterator&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;get_chunks&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="n"&gt;Sequence&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;chunk_size&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="n"&gt;Iterator&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="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nb"&gt;range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;len&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="n"&gt;chunk_size&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;yield&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;i&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;chunk_size&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;ids_chunk&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;get_chunks&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sequence&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;account_ids_from_db&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;chunk_size&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="c1"&gt;# do something with a chunk here&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;account_id&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;ids_chunk&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="o"&gt;...&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Depending on the number of rows to be retrieved from the database, the
&lt;span class="caps"&gt;SQL&lt;/span&gt; query plan and some other factors, you may need to use &lt;cite&gt;&lt;span class="caps"&gt;LIMIT&lt;/span&gt;&lt;/cite&gt; and
&lt;cite&gt;&lt;span class="caps"&gt;OFFSET&lt;/span&gt;&lt;/cite&gt; clauses in your &lt;span class="caps"&gt;SQL&lt;/span&gt; and make multiple queries, instead of
selecting all the rows at once. It saves you some memory in the&amp;nbsp;application.&lt;/p&gt;
&lt;p&gt;But sometimes you may fully rely on the &lt;span class="caps"&gt;DBMS&lt;/span&gt; features to process data
in batches. Let&amp;#8217;s take a look at the&amp;nbsp;example.&lt;/p&gt;
&lt;div class="section" id="problem-update-blacklist-expiration-timestamps-in-chunks"&gt;
&lt;h2&gt;Problem: update blacklist expiration timestamps in&amp;nbsp;chunks&lt;/h2&gt;
&lt;p&gt;We&amp;#8217;ve got a black list of customers. Each item in the blacklist table
in the database may have an optional value in expiration timestamp
column. If the value is &lt;cite&gt;&lt;span class="caps"&gt;NULL&lt;/span&gt;&lt;/cite&gt; then a client considered permanentely
blacklisted. Otherwise a row will be removed from the blacklist table
(possibly by a cronjob task), once an expiration timestamp is less
than current&amp;nbsp;time.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Problem:&lt;/strong&gt; we want to set an expiration date to all permanently
blacklisted customers so that they get removed from the list in
batches of size 10 a day. When splitting into chunks, we want to order
the rows so that the customers with the most recent suspicious
activity come first. The activity log defined in another database&amp;nbsp;table.&lt;/p&gt;
&lt;p&gt;In other words we want to update all rows in the table where some
conditions met (&lt;cite&gt;expiry&lt;/cite&gt; value equals to &lt;cite&gt;&lt;span class="caps"&gt;NULL&lt;/span&gt;&lt;/cite&gt; in this case). But we
have to do it in chunks of the constant&amp;nbsp;size.&lt;/p&gt;
&lt;p&gt;We can do it like in the example above: select all the rows that
satisfy the condition, then iterate over batches of size 10 and
update. But in this case there&amp;#8217;s nothing that a modern &lt;span class="caps"&gt;RDBMS&lt;/span&gt; couldn&amp;#8217;t
achieve by itself, without writing any extra code. So let&amp;#8217;s see how to
do it using pure PostgreSQL 9+&amp;nbsp;query.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="solution-postgresql-query-for-updating-rows-in-chunks-using-row-number-function"&gt;
&lt;h2&gt;Solution: PostgreSQL query for updating rows in chunks using row_number()&amp;nbsp;function&lt;/h2&gt;
&lt;p&gt;First, let&amp;#8217;s see how the table&amp;#8217;s schema may look&amp;nbsp;like.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="k"&gt;CREATE&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;TABLE&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;balcklist&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;customer_id&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;INTEGER&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;PRIMARY&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;KEY&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;expiry&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;TIMESTAMP&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;WITHOUT&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;TIME&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;ZONE&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;default&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;timestamp&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;TIMESTAMP&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;WITHOUT&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;TIME&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;ZONE&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;default&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;now&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;at&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;time&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;zone&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;utc&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;A log of suspicious activity is stored in &lt;cite&gt;suspicious_activity&lt;/cite&gt;
database table that we can join using &lt;cite&gt;customer_id&lt;/cite&gt; column.&lt;/p&gt;
&lt;p&gt;The algorithm we are going to use when writing final &lt;span class="caps"&gt;SQL&lt;/span&gt; query is the&amp;nbsp;following:&lt;/p&gt;
&lt;ol class="arabic simple"&gt;
&lt;li&gt;Join activity table, filter and select the data to be&amp;nbsp;updated&lt;/li&gt;
&lt;li&gt;Get row numbers using PostgreSQL&amp;#8217;s window function &lt;a class="reference external" href="http://www.postgresqltutorial.com/postgresql-row_number/"&gt;row_number()&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Get batch numbers using simple arithmetic operations (row number
integer division by chunk&amp;nbsp;size)&lt;/li&gt;
&lt;li&gt;Get new expiry timestamps using current batch number and
PostgreSQL&amp;#8217;s &lt;a class="reference external" href="http://www.postgresqltutorial.com/postgresql-interval/"&gt;interval type&lt;/a&gt; arithmetic&amp;nbsp;operations&lt;/li&gt;
&lt;li&gt;Wrap up all previous steps into a subquery; update &lt;cite&gt;expiry&lt;/cite&gt; column
for customers selected in the&amp;nbsp;subquery.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;The final &lt;span class="caps"&gt;SQL&lt;/span&gt; query then is the&amp;nbsp;following:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="c1"&gt;-- main: update expiry timestamp column in chunks&lt;/span&gt;
&lt;span class="k"&gt;UPDATE&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;blacklist&lt;/span&gt;
&lt;span class="k"&gt;SET&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;expiry&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;subquery&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;new_expiry_timestamp&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="c1"&gt;-- subquery 3: get new expiration timestamps based on a chunk number&lt;/span&gt;
&lt;span class="w"&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;row_num&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;row_batched&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;customer_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;NOW&lt;/span&gt;&lt;span class="p"&gt;()::&lt;/span&gt;&lt;span class="k"&gt;TIMESTAMP&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;row_batched&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="nb"&gt;INTERVAL&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;1 day&amp;#39;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;AS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;new_expiry_timestamp&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;latest_suspicious_activity&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;blacklist_timestamp&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="c1"&gt;-- subquery 2: get chunk numbers based on a row number&lt;/span&gt;
&lt;span class="w"&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;row_num&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="n"&gt;row_num&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;AS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;row_batched&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="n"&gt;customer_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="n"&gt;latest_suspicious_activity&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="n"&gt;blacklist_timestamp&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="c1"&gt;-- subquery 1: get row numbers&lt;/span&gt;
&lt;span class="w"&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;row_number&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;OVER&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;ORDER&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;BY&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;latest_suspicious_activity&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;DESC&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;NULLS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;LAST&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;AS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;row_num&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="n"&gt;customer_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="n"&gt;latest_suspicious_activity&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="n"&gt;blacklist_timestamp&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="c1"&gt;-- subquery 0: order blacklist&lt;/span&gt;
&lt;span class="w"&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;blacklist&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;customer_id&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;AS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;customer_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;          &lt;/span&gt;&lt;span class="k"&gt;MAX&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;suspicious_activity&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;created&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;AS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;latest_suspicious_activity&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;          &lt;/span&gt;&lt;span class="n"&gt;blacklist&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;timestamp&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;AS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;blacklist_timestamp&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;blacklist&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="k"&gt;INNER&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;JOIN&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;suspicious_activity&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;ON&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;blacklist&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;customer_id&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;suspicious_activity&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;customer_id&lt;/span&gt;
&lt;span class="w"&gt;        &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;blacklist&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;expiry_timestamp&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;is&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;NULL&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="k"&gt;GROUP&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;BY&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;blacklist&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;customer_id&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="k"&gt;ORDER&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;BY&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;latest_suspicious_activity&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;DESC&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;blacklist_timestamp&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;DESC&lt;/span&gt;
&lt;span class="w"&gt;       &lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;AS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;ordered&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;AS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;numbered&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;AS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;offsetted&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;AS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;subquery&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;blacklist&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;customer_id&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;subquery&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;customer_id&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;The &lt;span class="caps"&gt;SQL&lt;/span&gt; above may not be &lt;span class="caps"&gt;SQL&lt;/span&gt;-standard compatible and is guaranteed to
work on PostgreSQL 9.6 or&amp;nbsp;later.&lt;/p&gt;
&lt;p&gt;Comments, labels and the algorithm above should make the query
self-explanatory, although some basic knowledge of &lt;span class="caps"&gt;SQL&lt;/span&gt; is needed to
understand what the query really&amp;nbsp;does.&lt;/p&gt;
&lt;p&gt;Basically, we need only &lt;cite&gt;new_expiry_timestamp&lt;/cite&gt; and &lt;cite&gt;customer_id&lt;/cite&gt;
columns from the subquery. But some extra columns are passed from the
deepest subquery all the way to the top
(e.g. &lt;cite&gt;latest_suspicious_activity&lt;/cite&gt;, &lt;cite&gt;row_num&lt;/cite&gt;, etc), so that you can
run the subquery only and see how new expiry timestamps are calculated
before doing any updates to the data. &lt;cite&gt;&lt;span class="caps"&gt;BEGIN&lt;/span&gt;/&lt;span class="caps"&gt;ROLLBACK&lt;/span&gt;&lt;/cite&gt; may also be of
great help&amp;nbsp;here.&lt;/p&gt;
&lt;p&gt;All in all, the query&amp;nbsp;above:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;Easy to&amp;nbsp;read&lt;/li&gt;
&lt;li&gt;Requires no extra code and does all the batch processing using PostgreSQL query&amp;nbsp;language&lt;/li&gt;
&lt;li&gt;Executed much faster than when doing extra processing in your app&amp;#8217;s&amp;nbsp;code&lt;/li&gt;
&lt;li&gt;Can easily take advantage of PostgreSQL transaction and &lt;a class="reference external" href="https://www.2ndquadrant.com/en/blog/postgresql-anti-patterns-read-modify-write-cycles/"&gt;atomic&amp;nbsp;updates&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
</content><category term="Blog"></category><category term="sql"></category><category term="postgres"></category><category term="python"></category></entry><entry><title>Switching between Kubernetes clusters and namespaces: kubectx and kubens</title><link href="https://blog.pilosus.org/posts/2019/09/28/kubernetes-context-switch/" rel="alternate"></link><published>2019-09-28T14:12:00+02:00</published><updated>2019-09-28T14:12:00+02:00</updated><author><name>Vitaly Samigullin</name></author><id>tag:blog.pilosus.org,2019-09-28:/posts/2019/09/28/kubernetes-context-switch/</id><summary type="html">&lt;p class="first last"&gt;Save your time by using command line tools kubectx and
kubens in multi-cluster environment with more than one
default namespace&amp;nbsp;each.&lt;/p&gt;
</summary><content type="html">&lt;p&gt;Having more than one Kubernetes cluster is a common pattern. You may
have one cluster dedicated to production environment, another one for
staging and the third one for testing&amp;nbsp;environment.&lt;/p&gt;
&lt;p&gt;The same thing with Kubernetes namespaces: it&amp;#8217;s great abstraction
allowing you to separate workloads by their purpose. You may have
multiple namespaces for different test environments or a dedicated
namespace for app-related pods and another namespace for logging and
monitoring&amp;nbsp;services.&lt;/p&gt;
&lt;p&gt;Configuring access to multiple Kubernetes clusters is well documented
in official &lt;a class="reference external" href="https://kubernetes.io/docs/tasks/access-application-cluster/configure-access-multiple-clusters/"&gt;k8s documentation&lt;/a&gt;. Still switching context and
specifying namespace for each &lt;tt class="docutils literal"&gt;kubectl&lt;/tt&gt; invocation is tedious. This
is where &lt;a class="reference external" href="https://github.com/ahmetb/kubectx/"&gt;kubectx and kubens&lt;/a&gt; come into play. But let&amp;#8217;s configure
multiple clusters&amp;nbsp;first.&lt;/p&gt;
&lt;div class="section" id="kubernetes-configuration-file-multiple-clusters"&gt;
&lt;h2&gt;Kubernetes configuration file: multiple&amp;nbsp;clusters&lt;/h2&gt;
&lt;p&gt;You find an extensive guide in the &lt;a class="reference external" href="https://kubernetes.io/docs/tasks/access-application-cluster/configure-access-multiple-clusters/"&gt;k8s documentation&lt;/a&gt;. All in all,
your &lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;~/.kube/config&lt;/span&gt;&lt;/tt&gt; should include three&amp;nbsp;lists:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;Clusters&lt;/li&gt;
&lt;li&gt;Users&lt;/li&gt;
&lt;li&gt;Contexts&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Context defines a cluster, it&amp;#8217;s namespace and a user. When the user
switches to the context, he or she is working with the cluster and the
namespaces of the&amp;nbsp;context.&lt;/p&gt;
&lt;p&gt;Each cluster may define master node&amp;#8217;s server hostname and optionaly
other entities like base64-encoded&amp;nbsp;certificates.&lt;/p&gt;
&lt;p&gt;Users may also be defined by autherntication options that covered in
&lt;a class="reference external" href="https://kubernetes.io/docs/reference/access-authn-authz/authentication/"&gt;Kubernetes authentication guide&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;An example configuration file for two Kubernetes clusters and two
users with different authentication types may look like&amp;nbsp;this:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="nt"&gt;apiVersion&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;v1&lt;/span&gt;
&lt;span class="nt"&gt;clusters&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;cluster&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;certificate-authority-data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;YOUR-CERT-AUTHORITY-DATA-CLUSTER-1&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;server&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;https://YOUR-SERVER-ADDRESS-1&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;cluster1&lt;/span&gt;
&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;cluster&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;certificate-authority-data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;YOUR-CERT-AUTHORITY-DATA-CLUSTER-2&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;server&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;https://YOUR-SERVER-ADDRESS-2&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;cluster2&lt;/span&gt;
&lt;span class="nt"&gt;contexts&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;context&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;cluster&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;cluster1&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;namespace&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;production&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;user&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;user1&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;context1&lt;/span&gt;
&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;context&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;cluster&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;cluster2&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;namespace&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;test&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;user&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;user2&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;context2&lt;/span&gt;
&lt;span class="nt"&gt;current-context&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;context1&lt;/span&gt;
&lt;span class="nt"&gt;kind&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;Config&lt;/span&gt;
&lt;span class="nt"&gt;preferences&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p p-Indicator"&gt;{}&lt;/span&gt;
&lt;span class="nt"&gt;users&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;user1&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;user&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;client-certificate-data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;YOUR-CLIENT-CERT-DATA-1&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;client-key-data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;YOUR-CLIENT-KEY-DATA-1&lt;/span&gt;
&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;user2&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;user&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;auth-provider&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nt"&gt;config&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nt"&gt;access-token&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;YOUR-ACCESS-TOKEN&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nt"&gt;cmd-args&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;config config-helper --format=json&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nt"&gt;cmd-path&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;/PATH/TO/google-cloud-sdk/bin/gcloud&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nt"&gt;expiry&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;2019-09-28T00:00:00Z&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nt"&gt;expiry-key&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;#39;{.credential.token_expiry}&amp;#39;&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nt"&gt;token-key&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;#39;{.credential.access_token}&amp;#39;&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;gcp&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;div class="section" id="kubectx-and-kubens"&gt;
&lt;h2&gt;kubectx and&amp;nbsp;kubens&lt;/h2&gt;
&lt;p&gt;Although switching context is easy with &lt;tt class="docutils literal"&gt;kubectl config &lt;span class="pre"&gt;use-context&lt;/span&gt;&lt;/tt&gt;,
working with both contexts and namespaces require way too many key strokes!
&lt;a class="reference external" href="https://github.com/ahmetb/kubectx/"&gt;kubectx and kubens&lt;/a&gt; command line tools allow you to switch context this&amp;nbsp;easy:&lt;/p&gt;
&lt;pre class="literal-block"&gt;
$ kubectx context2  # the same as kubectl config use-context context2
&lt;/pre&gt;
&lt;p&gt;Chaning context with &lt;tt class="docutils literal"&gt;kubectx&lt;/tt&gt; changes your config file&amp;#8217;s
&lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;current-context&lt;/span&gt;&lt;/tt&gt; field the same way the &lt;tt class="docutils literal"&gt;kubectl config
&lt;span class="pre"&gt;use-context&lt;/span&gt;&lt;/tt&gt; does.&lt;/p&gt;
&lt;p&gt;Now, instead of typing &lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;--namespace&lt;/span&gt; &lt;span class="pre"&gt;your-namespace&lt;/span&gt;&lt;/tt&gt; or &lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;-n&lt;/span&gt;
&lt;span class="pre"&gt;your-namespace&lt;/span&gt;&lt;/tt&gt; in every &lt;tt class="docutils literal"&gt;kubectl&lt;/tt&gt; invokation, change namespace
once with &lt;tt class="docutils literal"&gt;kubens&lt;/tt&gt;:&lt;/p&gt;
&lt;pre class="literal-block"&gt;
$ kubens test2     # your current k8s namespace is test2 now
$ kubectl get po   # the same as invoking with --namespace test2
# kubens test1     # your current k8s namespace is test1 now
&lt;/pre&gt;
&lt;p&gt;Using &lt;tt class="docutils literal"&gt;kubectx&lt;/tt&gt; and &lt;tt class="docutils literal"&gt;kubens&lt;/tt&gt; allow you to save some key strokes
and really come in handy when you switch over multiple Kubernetes
clusters with more than one default&amp;nbsp;namespace.&lt;/p&gt;
&lt;/div&gt;
</content><category term="Blog"></category><category term="k8s"></category><category term="kubectx"></category><category term="kubens"></category></entry><entry><title>Application configs: files or environment variables? Actually both!</title><link href="https://blog.pilosus.org/posts/2019/06/07/application-configs-files-or-environment-variables-actually-both/" rel="alternate"></link><published>2019-06-07T20:30:00+02:00</published><updated>2019-06-07T20:30:00+02:00</updated><author><name>Vitaly Samigullin</name></author><id>tag:blog.pilosus.org,2019-06-07:/posts/2019/06/07/application-configs-files-or-environment-variables-actually-both/</id><summary type="html">&lt;p&gt;App configuration is highly opinionated topic. It&amp;#8217;s technology stack-
and infrastructure-dependent. When it comes to configs there are a few
parties with a possible conflict of interests. Software developers
want an easy access to app&amp;#8217;s configuration, versioning, readability,
language support for the config format. Security guys want security …&lt;/p&gt;</summary><content type="html">&lt;p&gt;App configuration is highly opinionated topic. It&amp;#8217;s technology stack-
and infrastructure-dependent. When it comes to configs there are a few
parties with a possible conflict of interests. Software developers
want an easy access to app&amp;#8217;s configuration, versioning, readability,
language support for the config format. Security guys want security!
&lt;span class="caps"&gt;SRE&lt;/span&gt;/Administrators want an easy deployment and scaling as well as
infrastructure native support for configuration format of
choice. Managers, well, they probably want to smile at conferences and
don&amp;#8217;t want you all to spend too much time on such a miserable question
like configuration&amp;nbsp;architecture!&lt;/p&gt;
&lt;p&gt;In software development some things change rapidly. Ideas, manifests
and frameworks rise and fall. So lets review what&amp;#8217;s application
configuration today and what it could&amp;nbsp;be.&lt;/p&gt;
&lt;div class="section" id="environment-variables-good-and-bad-parts"&gt;
&lt;h2&gt;Environment variables: good and bad&amp;nbsp;parts&lt;/h2&gt;
&lt;p&gt;&lt;a class="reference external" href="https://en.wikipedia.org/wiki/Environment_variable"&gt;Environment variables&lt;/a&gt; (&lt;em&gt;env vars&lt;/em&gt; or &lt;em&gt;env&lt;/em&gt;) are often recommended
as the only way to store application configs (see &lt;a class="reference external" href="https://12factor.net/config"&gt;The Twelve-Factor
App&lt;/a&gt; rule &lt;span class="caps"&gt;III&lt;/span&gt;). Envs are &lt;strong&gt;good&lt;/strong&gt; bacause they&amp;nbsp;are:&lt;/p&gt;
&lt;div class="section" id="secure"&gt;
&lt;h3&gt;Secure&lt;/h3&gt;
&lt;p&gt;Environment variables are set by who&amp;#8217;s responsible for the deployment
environment: by developer on local machines, by
&lt;span class="caps"&gt;SRE&lt;/span&gt;/Administrator/DevOps in production. With env you won&amp;#8217;t compromise
your production credentials even if your code&amp;#8217;s leaked to the
public. It&amp;#8217;s also harder for developers to break something in
production, because they simply don&amp;#8217;t have access to production&amp;nbsp;credentials.&lt;/p&gt;
&lt;p&gt;Messing up with envs in the runtime may also be tricky, as you cannot
change environment variables for the parent process in *nix&amp;nbsp;systems.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="universal"&gt;
&lt;h3&gt;Universal&lt;/h3&gt;
&lt;p&gt;Environment variables are everywhere! On &lt;span class="caps"&gt;GNU&lt;/span&gt;/Linux, MacOS,
Windows. Every production-ready programming language supports envs out
of&amp;nbsp;box.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="easy-to-use"&gt;
&lt;h3&gt;Easy to&amp;nbsp;use&lt;/h3&gt;
&lt;p&gt;Env is flat. Basically it&amp;#8217;s a key-value pair with string value. So
loading envs is&amp;nbsp;easy.&lt;/p&gt;
&lt;p&gt;There are some &lt;strong&gt;bad&lt;/strong&gt; things about envs too. Envs&amp;nbsp;are:&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="unexpressive"&gt;
&lt;h3&gt;Unexpressive&lt;/h3&gt;
&lt;p&gt;The flip side of the &amp;#8220;easy&amp;#8221; coin is unexpressiveness. Envs are flat
key-value strings. You cannot store the whole config as en env. So you
need to read envs in your code and then construct some sort of the
final configuration, possible overly&amp;nbsp;complex.&lt;/p&gt;
&lt;p&gt;Flat env structure also get you into a grey area of bad variable
names. Consider the following &lt;span class="caps"&gt;YAML&lt;/span&gt;&amp;nbsp;config:&lt;/p&gt;
&lt;pre class="literal-block"&gt;
cluster:
  host_weight:
    node00.example.com: 0.8
    node01.example.com: 0.8
    node02.example.com: 1.0
    node03.example.com: 0.5
db: 1
user: app
password: my_password
&lt;/pre&gt;
&lt;p&gt;It may translate to the following envs to substitute values in the
config&amp;nbsp;above:&lt;/p&gt;
&lt;pre class="literal-block"&gt;
CLUSTER_HOST_NODE_00=0.8
CLUSTER_HOST_NODE_01=0.8
CLUSTER_HOST_NODE_02=1.0
CLUSTER_HOST_NODE_03=0.5
CLUSTER_DB=1
CLUSTER_USER=app
CLUSTER_PASSWORD=my_password
&lt;/pre&gt;
&lt;p&gt;So, you still store some kind of configuration file in your app&amp;#8217;s
version control system (&lt;span class="caps"&gt;VCS&lt;/span&gt;). Usually it&amp;#8217;s a module in you programming
language (e.g. &lt;tt class="docutils literal"&gt;config.py&lt;/tt&gt; in Python) with some language-specific
data structures and even some logic. Values (at least some of them)
are stored as envs with quite awkward names. These configs are much
less readable as the &lt;span class="caps"&gt;YAML&lt;/span&gt;&amp;nbsp;above.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="need-to-be-stored-somewhere-eventually"&gt;
&lt;h3&gt;Need to be stored somewhere&amp;nbsp;eventually&lt;/h3&gt;
&lt;p&gt;Envrironment variables are stored somewhere eventually: in your admins
repository, orchestration system&amp;#8217;s manifest files that also checked in
the &lt;span class="caps"&gt;VCS&lt;/span&gt;. So even separated from the app&amp;#8217;s code repo, envs are still
stored in files that potentially could be accessed by more people than
they&amp;nbsp;should.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="may-break-release-determinism"&gt;
&lt;h3&gt;May break release&amp;nbsp;determinism&lt;/h3&gt;
&lt;p&gt;If you use config files checked into your app&amp;#8217;s repository, your
config is also a part of the app&amp;#8217;s releases. You can effectively
rollback to a stable release whenever&amp;nbsp;needed.&lt;/p&gt;
&lt;p&gt;In the case of envs your app&amp;#8217;s releases are separated from the
configs. Ideally environment variables should be versioned too. Both
app&amp;#8217;s and config&amp;#8217;s releases should be synchornized somehow. Otherwise
your release determinism may break, rollback to previous versions with
zero downtime may become&amp;nbsp;difficult.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="section" id="configuration-files-good-and-bad-parts"&gt;
&lt;h2&gt;Configuration files: good and bad&amp;nbsp;parts&lt;/h2&gt;
&lt;p&gt;Configuration files have their strengths and weakness too. First, the
&lt;strong&gt;good&lt;/strong&gt; things. Files&amp;nbsp;are:&lt;/p&gt;
&lt;div class="section" id="readable"&gt;
&lt;h3&gt;Readable&lt;/h3&gt;
&lt;p&gt;You don&amp;#8217;t write config files as program&amp;#8217;s module (e.g. &lt;tt class="docutils literal"&gt;config.py&lt;/tt&gt;
in Python). Instead you use some language-agnostic format for humans
like &lt;tt class="docutils literal"&gt;&lt;span class="caps"&gt;YAML&lt;/span&gt;&lt;/tt&gt;. Then you have both readability and (to some extent)
separation code and&amp;nbsp;configs.&lt;/p&gt;
&lt;p&gt;You write clean, self-sufficient configs. No more ugly things&amp;nbsp;like:&lt;/p&gt;
&lt;pre class="literal-block"&gt;
HOSTNAME = os.environ.get('DB_HOSTNAME', 'db.example.com')
&lt;/pre&gt;
&lt;p&gt;Developers often group config files by deployment enviroment:
&lt;tt class="docutils literal"&gt;testing.yaml&lt;/tt&gt;, &lt;tt class="docutils literal"&gt;development.yaml&lt;/tt&gt;, &lt;tt class="docutils literal"&gt;production.yaml&lt;/tt&gt;. Sometimes
you cannot reach 100% &lt;a class="reference external" href="https://12factor.net/dev-prod-parity"&gt;deploy environments parity&lt;/a&gt;. So having all
configs at hand may help when fixing a&amp;nbsp;bug.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="part-of-release"&gt;
&lt;h3&gt;Part of&amp;nbsp;release&lt;/h3&gt;
&lt;p&gt;As I said earlier, if a config is checked into app&amp;#8217;s repository it a
part of app release history. It makes your app releases more
deterministic allowing to rollback to earlier releases if necessary
with no extra&amp;nbsp;efforts.&lt;/p&gt;
&lt;p&gt;There are &lt;strong&gt;bad&lt;/strong&gt; things about files&amp;nbsp;too:&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="formats-hell"&gt;
&lt;h3&gt;Formats&amp;nbsp;hell&lt;/h3&gt;
&lt;p&gt;&lt;tt class="docutils literal"&gt;&lt;span class="caps"&gt;XML&lt;/span&gt;&lt;/tt&gt;, &lt;tt class="docutils literal"&gt;&lt;span class="caps"&gt;YAML&lt;/span&gt;&lt;/tt&gt;, &lt;tt class="docutils literal"&gt;ini&lt;/tt&gt;, &lt;tt class="docutils literal"&gt;&lt;span class="caps"&gt;TOML&lt;/span&gt;&lt;/tt&gt;, &lt;tt class="docutils literal"&gt;&lt;span class="caps"&gt;JSON&lt;/span&gt;&lt;/tt&gt;, you name it! Probably
you don&amp;#8217;t have a luxuary of having the only one way to do it in your
language. Even in &lt;tt class="docutils literal"&gt;JavaScript&lt;/tt&gt; &lt;span class="caps"&gt;JSON&lt;/span&gt; is not the only configuration
format in use. But in popular general-purpose languages like
&lt;tt class="docutils literal"&gt;Python&lt;/tt&gt; it&amp;#8217;s even&amp;nbsp;worse.&lt;/p&gt;
&lt;p&gt;In a complex project with legacy components you may find all sorts of
configuration file formats. It&amp;#8217;s&amp;nbsp;annoying.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="insecure"&gt;
&lt;h3&gt;Insecure&lt;/h3&gt;
&lt;p&gt;Junior developers, people on probation period, QAs, admins, maybe even
developers from other teams and departments may have access to your
app&amp;#8217;s secrets when they are in the config files checked into a
&lt;span class="caps"&gt;VCS&lt;/span&gt;. That&amp;#8217;s a huge security problem, even if your network operation
center engineers, admins and devops do a great job isolating
production services and&amp;nbsp;networks.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="section" id="taking-good-parts-from-both-envs-and-files"&gt;
&lt;h2&gt;Taking good parts from both envs and&amp;nbsp;files&lt;/h2&gt;
&lt;p&gt;But what if we take the good parts from both enrionment variables and
config files, combine them and throw away the bad parts? Sounds great,
doesn&amp;#8217;t&amp;nbsp;it?&lt;/p&gt;
&lt;p&gt;First, we need to take a closer look at what&amp;#8217;s really important when
it comes to &lt;em&gt;security&lt;/em&gt;. Actually, it&amp;#8217;s credentials (login/password,
private &lt;span class="caps"&gt;API&lt;/span&gt; keys, cryptographic certificates, etc.) that if kept in
secret solve most of the problems that your config files stored in the
&lt;span class="caps"&gt;VCS&lt;/span&gt; have. If your production database hostname leaks but username and
password are kept in secret you are probably okay. Brutforce attacks,
unpatched software vulnerabilities are still real. But your customer
database isn&amp;#8217;t leaked immediately even if bad guys know your database
&lt;span class="caps"&gt;URL&lt;/span&gt;.&lt;/p&gt;
&lt;p&gt;Second, we really want to have nice language-agnostic non-flat file
format for our configs. It&amp;#8217;s opinionated, but I personally think that
&lt;a class="reference external" href="https://yaml.org/"&gt;&lt;span class="caps"&gt;YAML&lt;/span&gt;&lt;/a&gt; is superior to all other formats. It&amp;#8217;s&amp;nbsp;beautiful!&lt;/p&gt;
&lt;p&gt;So what if we use a &lt;span class="caps"&gt;YAML&lt;/span&gt; file and substitute our secrets with
environment variables&amp;nbsp;tokens?&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="nt"&gt;db&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;login&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;user&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;password&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;${DB_PASSWORD}&lt;/span&gt;
&lt;span class="nt"&gt;mail&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;login&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;user&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;password&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;${MAIL_PASSWORD:-my_default_password}&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Then our app loads the &lt;span class="caps"&gt;YAML&lt;/span&gt; and &lt;strong&gt;interpolates environment variables&lt;/strong&gt;
in&amp;nbsp;it:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;db&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;login&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;user&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;password&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;my_db_password&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="s1"&gt;&amp;#39;mail&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;login&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;user&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;password&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;my_default_password&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;}}&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;In the above snippet a database password has been loaded from the
&lt;tt class="docutils literal"&gt;$DB_PASSWORD&lt;/tt&gt; environment variable. Email password had to be loaded
from &lt;tt class="docutils literal"&gt;$MAIL_PASSWORD&lt;/tt&gt;. It&amp;#8217;s defined as Bash-style env with default
value. It seems that at the time of config loading the
&lt;tt class="docutils literal"&gt;$MAIL_PASSWORD&lt;/tt&gt; wasn&amp;#8217;t set so a default value
&lt;tt class="docutils literal"&gt;'my_default_password'&lt;/tt&gt; is&amp;nbsp;used.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="proof-of-concept"&gt;
&lt;h2&gt;Proof of&amp;nbsp;concept&lt;/h2&gt;
&lt;p&gt;As a proof of concept I&amp;#8217;ve just released a Python library called
&lt;a class="reference external" href="https://github.com/pilosus/piny"&gt;Piny&lt;/a&gt;. It does exactly&amp;nbsp;this:&lt;/p&gt;
&lt;blockquote&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;Reads your &lt;span class="caps"&gt;YAML&lt;/span&gt;-config file marked up with environments&amp;nbsp;variables&lt;/li&gt;
&lt;li&gt;Interpolates environment variables with their&amp;nbsp;values&lt;/li&gt;
&lt;li&gt;Creates a Python object with configuration ready for use in your&amp;nbsp;app&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;
&lt;p&gt;When using &lt;a class="reference external" href="https://github.com/pilosus/piny"&gt;Piny&lt;/a&gt; consider best practices for your configuration&amp;nbsp;files:&lt;/p&gt;
&lt;blockquote&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;Maintain healthy security/convenience balance for your&amp;nbsp;config&lt;/li&gt;
&lt;li&gt;Mark up entity as an environment variable in your &lt;span class="caps"&gt;YAML&lt;/span&gt; if and only
if it really is a &lt;em&gt;secret&lt;/em&gt; (login/passwords, private &lt;span class="caps"&gt;API&lt;/span&gt; keys,
crypto keys, certificates, or maybe &lt;span class="caps"&gt;DB&lt;/span&gt; hostname too? You&amp;nbsp;decide)&lt;/li&gt;
&lt;li&gt;Once config loaded by Piny, validate it using your favourite
validation tool (some integrations are coming in the future&amp;nbsp;releases)&lt;/li&gt;
&lt;li&gt;Store your config files in the version control systems along with
you app&amp;#8217;s&amp;nbsp;code.&lt;/li&gt;
&lt;li&gt;Environment variables are set by whomever is responsible for the
deployment. Modern orchestration systems like &lt;a class="reference external" href="https://kubernetes.io/"&gt;Kubernetes&lt;/a&gt; make
it easy to keep envs secure (see &lt;a class="reference external" href="https://kubernetes.io/docs/concepts/configuration/secret/"&gt;Secrets&lt;/a&gt; and &lt;a class="reference external" href="https://www.vaultproject.io/"&gt;Vault&lt;/a&gt;).&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;
&lt;p&gt;Piny stands for &lt;em&gt;Piny is not &lt;span class="caps"&gt;YAML&lt;/span&gt;&lt;/em&gt;. It&amp;#8217;s not only a library name, but
also a name for &lt;span class="caps"&gt;YAML&lt;/span&gt; marked up with environment&amp;nbsp;variables.&lt;/p&gt;
&lt;p&gt;You can download Piny at &lt;a class="reference external" href="https://pypi.org/project/piny/"&gt;PyPi&lt;/a&gt;.&lt;/p&gt;
&lt;/div&gt;
</content><category term="Blog"></category><category term="architecture"></category><category term="config"></category><category term="k8s"></category></entry><entry><title>Helm stable/prometheus-operator: adding new scraping targets and troubleshooting</title><link href="https://blog.pilosus.org/posts/2019/06/01/prometheus-operator-no-active-targets/" rel="alternate"></link><published>2019-06-01T14:12:00+02:00</published><updated>2019-06-01T14:12:00+02:00</updated><author><name>Vitaly Samigullin</name></author><id>tag:blog.pilosus.org,2019-06-01:/posts/2019/06/01/prometheus-operator-no-active-targets/</id><summary type="html">&lt;p class="first last"&gt;How to add new services for Prometheus to scrape and what to
do if no active targets found. Post covers Prometheus
deployed in Kubernetes cluster using Helm
stable/prometheus-operator&amp;nbsp;chart.&lt;/p&gt;
</summary><content type="html">&lt;p&gt;Today I Learned (&lt;span class="caps"&gt;TIL&lt;/span&gt;) that Kubernetes named ports usage may be quite&amp;nbsp;frustrating.&lt;/p&gt;
&lt;p&gt;Recently I&amp;#8217;ve deployed monitoring stack (Prometheus, Grafana) in
Kubernetes using Helm &lt;a class="reference external" href="https://github.com/helm/charts/tree/master/stable/prometheus-operator"&gt;stable/prometheus-operator&lt;/a&gt;. Setting up
monitoring for Kubernetes cluster itself is covered by &lt;a class="reference external" href="https://www.digitalocean.com/community/tutorials/how-to-set-up-digitalocean-kubernetes-cluster-monitoring-with-helm-and-prometheus-operator"&gt;this&lt;/a&gt;
wonderful guide. But for setting up monitoring for other services one
need to learn what &lt;a class="reference external" href="https://cloud.google.com/blog/products/containers-kubernetes/best-practices-for-building-kubernetes-operators-and-stateful-apps"&gt;Kubernetes Operator&lt;/a&gt; is, and create his/her own
&lt;tt class="docutils literal"&gt;ServiceMonitor&lt;/tt&gt; for &lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;Prometheus-Operator&lt;/span&gt;&lt;/tt&gt; (see
&lt;a class="reference external" href="https://github.com/helm/charts/tree/master/stable/prometheus-operator"&gt;stable/prometheus-operator&lt;/a&gt;).&lt;/p&gt;
&lt;p&gt;&lt;tt class="docutils literal"&gt;ServiceMotitor&lt;/tt&gt; is a custom resource. It tells Prometheus what k8s
&lt;tt class="docutils literal"&gt;Service&lt;/tt&gt; exposes metrics and where: service label selectors, its
namespace, path, port, etc. Say, we&amp;#8217;ve got a web application
&lt;tt class="docutils literal"&gt;Service&lt;/tt&gt; in the &lt;tt class="docutils literal"&gt;default&lt;/tt&gt; namespace with label &lt;tt class="docutils literal"&gt;app: pili&lt;/tt&gt; that
exposes &lt;tt class="docutils literal"&gt;/metrics&lt;/tt&gt; endpoint on the named port called &lt;tt class="docutils literal"&gt;uwsgi&lt;/tt&gt; . We
are going to deploy &lt;tt class="docutils literal"&gt;ServiceMonitor&lt;/tt&gt; for Prometheus to scrape the
metrics every 15 seconds in &lt;tt class="docutils literal"&gt;monitoring&lt;/tt&gt; namespace and with certain
labels. Then we apply a&amp;nbsp;manifest:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="nt"&gt;apiVersion&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;monitoring.coreos.com/v1&lt;/span&gt;
&lt;span class="nt"&gt;kind&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;ServiceMonitor&lt;/span&gt;
&lt;span class="nt"&gt;metadata&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;monitoring-pili&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;namespace&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;monitoring&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;labels&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;app&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;pili-service-monitor&lt;/span&gt;
&lt;span class="nt"&gt;spec&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;selector&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;matchLabels&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="c1"&gt;# Target app service&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nt"&gt;app&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;pili&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;endpoints&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;interval&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;15s&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;path&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;/metrics&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;port&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;uwsgi&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;namespaceSelector&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;matchNames&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;default&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Following this steps I&amp;#8217;m sucessfully deployed a few service monitors:
for a PostgreSQL cluster, RabbitMQ, ElasticSearch, etc. All of them
allowed Prometheus to scrape metrics just as expected. But my own
application still showed 0 active targets in Prometheus. I could
manually &lt;tt class="docutils literal"&gt;curl&lt;/tt&gt; my app service&amp;#8217;s &lt;tt class="docutils literal"&gt;/metrics&lt;/tt&gt; endpoint and see that
all the metrics are exposed correctly. Still Prometheus was unable to
scrape&amp;nbsp;it.&lt;/p&gt;
&lt;p&gt;I doublechecked label selectors as they often happen to be a culrpit
(see this &lt;a class="reference external" href="https://stackoverflow.com/questions/52991038/how-to-create-a-servicemonitor-for-prometheus-operator"&gt;StackOverflow&lt;/a&gt; question). Everything was fine. I could see
that my app&amp;#8217;s &lt;tt class="docutils literal"&gt;Service&lt;/tt&gt; exposed Kubernetes endpoints&amp;nbsp;correctly:&lt;/p&gt;
&lt;pre class="literal-block"&gt;
$ kubectl get endpoint --namespace default
...
pili  10.244.0.136:8080,10.244.0.144:8080,10.244.0.185:8080  6d16h
...
&lt;/pre&gt;
&lt;p&gt;Eventually, it turned out that &lt;tt class="docutils literal"&gt;ServiceMonitor&lt;/tt&gt; didn&amp;#8217;t see my app
service&amp;#8217;s &lt;strong&gt;named port&lt;/strong&gt;!&lt;/p&gt;
&lt;p&gt;My &lt;tt class="docutils literal"&gt;Service&lt;/tt&gt; manifest look like&amp;nbsp;this:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="nt"&gt;apiVersion&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;v1&lt;/span&gt;
&lt;span class="nt"&gt;kind&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;Service&lt;/span&gt;
&lt;span class="nt"&gt;metadata&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;pili&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;labels&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;app&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;pili&lt;/span&gt;
&lt;span class="nt"&gt;spec&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;ClusterIP&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;ports&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;protocol&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;TCP&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;port&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;8080&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;targetPort&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;uwsgi&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;selector&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;app&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;pili-web&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;tier&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;backend&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;In that case &lt;tt class="docutils literal"&gt;Service&lt;/tt&gt; targeted named port &lt;tt class="docutils literal"&gt;uwsgi&lt;/tt&gt; from app&amp;#8217;s
&lt;tt class="docutils literal"&gt;Deployment&lt;/tt&gt;. The port was also used in &lt;tt class="docutils literal"&gt;Ingress&lt;/tt&gt; sucessfully:&lt;/p&gt;
&lt;pre class="literal-block"&gt;
...
- backend:
    serviceName: pili
    servicePort: uwsgi
...
&lt;/pre&gt;
&lt;p&gt;It wasn&amp;#8217;t untill I &lt;em&gt;explicitly&lt;/em&gt; named the port (with the same name) in
&lt;tt class="docutils literal"&gt;Service&lt;/tt&gt; that &lt;tt class="docutils literal"&gt;ServiceMonitor&lt;/tt&gt; could discover my target. So I
rewrote my &lt;tt class="docutils literal"&gt;Service&lt;/tt&gt; manifest:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="nt"&gt;apiVersion&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;v1&lt;/span&gt;
&lt;span class="nt"&gt;kind&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;Service&lt;/span&gt;
&lt;span class="nt"&gt;metadata&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;pili&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;labels&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;app&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;pili&lt;/span&gt;
&lt;span class="nt"&gt;spec&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;ClusterIP&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;ports&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;uwsgi&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;protocol&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;TCP&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;port&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;8080&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;targetPort&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;uwsgi&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;selector&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;app&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;pili-web&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;tier&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;backend&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;So the only change that really helped was this&amp;nbsp;one:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;  ports:
&lt;span class="gd"&gt;-  - protocol: TCP&lt;/span&gt;
&lt;span class="gi"&gt;+  - name: uwsgi&lt;/span&gt;
&lt;span class="gi"&gt;+    protocol: TCP&lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;    port: 8080
&lt;span class="w"&gt; &lt;/span&gt;    targetPort: uwsgi
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Service&amp;#8217;s port should be explicitly named even if has the same name as
target port&amp;#8217;s name. &lt;tt class="docutils literal"&gt;ServiceMonitor&lt;/tt&gt; understands only explicitly
named &lt;tt class="docutils literal"&gt;Service&lt;/tt&gt; ports, although other entities, e.g. an &lt;tt class="docutils literal"&gt;Ingress&lt;/tt&gt;,
work well without explicit naming.&amp;nbsp;Beware!&lt;/p&gt;
&lt;p&gt;&lt;span class="caps"&gt;P.S.&lt;/span&gt; At the time of writing I used &lt;tt class="docutils literal"&gt;kubectl v1.14.2&lt;/tt&gt; and &lt;tt class="docutils literal"&gt;helm v2.14.0&lt;/tt&gt;.&lt;/p&gt;
</content><category term="Blog"></category><category term="k8s"></category><category term="helm"></category><category term="prometheus"></category><category term="kubernetes-operator"></category><category term="til"></category></entry><entry><title>Grafana Dashboard for Prometheus official Python client with Flask App metrics</title><link href="https://blog.pilosus.org/posts/2019/06/01/grafana-dashboard-flask-app/" rel="alternate"></link><published>2019-06-01T11:00:00+02:00</published><updated>2019-06-01T20:50:00+02:00</updated><author><name>Vitaly Samigullin</name></author><id>tag:blog.pilosus.org,2019-06-01:/posts/2019/06/01/grafana-dashboard-flask-app/</id><summary type="html">&lt;p class="first last"&gt;A dashboard with 12 panels showing &lt;span class="caps"&gt;RPS&lt;/span&gt;, latency, errors for
web app&amp;#8217;s endpoints as well as host and app&amp;#8217;s resource usage
and some extra&amp;nbsp;details.&lt;/p&gt;
</summary><content type="html">&lt;p&gt;I&amp;#8217;ve just &lt;a class="reference external" href="https://github.com/pilosus/prometheus-client-python-app-grafana-dashboard"&gt;open-sourced&lt;/a&gt; my Grafana dashboard. The dashboard designed
for a Flask web application that exposes metrics with
my &lt;a class="reference external" href="https://github.com/pilosus/flask_prometheus_metrics"&gt;flask_prometheus_metrics&lt;/a&gt; Flask&amp;nbsp;extension.&lt;/p&gt;
&lt;p&gt;Dashboard aimed at the apps deployed with Kubernetes, although it can
be easily tweaked to be&amp;nbsp;infrastructure-agnostic.&lt;/p&gt;
&lt;a class="reference external image-reference" href="https://raw.githubusercontent.com/pilosus/prometheus-client-python-app-grafana-dashboard/master/docs/flask-app-1.png"&gt;&lt;img alt="Grfana dashboard: RPS and latency panels" class="responsive" src="https://raw.githubusercontent.com/pilosus/prometheus-client-python-app-grafana-dashboard/master/docs/flask-app-1.png" /&gt;&lt;/a&gt;
&lt;p&gt;12 dasboard&amp;#8217;s panels covers the following&amp;nbsp;metrics:&lt;/p&gt;
&lt;blockquote&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;Requests per second per host, endpoint, &lt;span class="caps"&gt;HTTP&lt;/span&gt; method&amp;nbsp;etc.&lt;/li&gt;
&lt;li&gt;Latency&lt;/li&gt;
&lt;li&gt;Percentiles (latency within which certain percent of requests&amp;nbsp;served)&lt;/li&gt;
&lt;li&gt;Number of 4xx, 5xx errors per&amp;nbsp;second&lt;/li&gt;
&lt;li&gt;Error count by&amp;nbsp;endpoint&lt;/li&gt;
&lt;li&gt;&lt;span class="caps"&gt;CPU&lt;/span&gt; usage per&amp;nbsp;host&lt;/li&gt;
&lt;li&gt;Memory usage per&amp;nbsp;host&lt;/li&gt;
&lt;li&gt;Open file descriptors per&amp;nbsp;host&lt;/li&gt;
&lt;li&gt;App&amp;#8217;s uptime per&amp;nbsp;host&lt;/li&gt;
&lt;li&gt;App&amp;#8217;s version per&amp;nbsp;host&lt;/li&gt;
&lt;li&gt;App&amp;#8217;s deployment environment (e.g. development, staging, production) per&amp;nbsp;host&lt;/li&gt;
&lt;li&gt;Python interpretor version per&amp;nbsp;host&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;
&lt;a class="reference external image-reference" href="https://raw.githubusercontent.com/pilosus/prometheus-client-python-app-grafana-dashboard/master/docs/flask-app-2.png"&gt;&lt;img alt="Grfana dashboard: errors and resource usage" class="responsive" src="https://raw.githubusercontent.com/pilosus/prometheus-client-python-app-grafana-dashboard/master/docs/flask-app-2.png" /&gt;&lt;/a&gt;
&lt;p&gt;Dashboard also provides&amp;nbsp;variables:&lt;/p&gt;
&lt;blockquote&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;Prometheus&amp;nbsp;datasource&lt;/li&gt;
&lt;li&gt;Time&amp;nbsp;interval&lt;/li&gt;
&lt;li&gt;Kubernetes pod&amp;nbsp;name&lt;/li&gt;
&lt;li&gt;App&amp;#8217;s&amp;nbsp;endpoint&lt;/li&gt;
&lt;li&gt;&lt;span class="caps"&gt;HTTP&lt;/span&gt;&amp;nbsp;method&lt;/li&gt;
&lt;li&gt;Percentile&lt;/li&gt;
&lt;li&gt;&lt;span class="caps"&gt;HTTP&lt;/span&gt; status code for&amp;nbsp;errors&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;
&lt;a class="reference external image-reference" href="https://raw.githubusercontent.com/pilosus/prometheus-client-python-app-grafana-dashboard/master/docs/flask-app-3.png"&gt;&lt;img alt="Grfana dashboard: app's information" class="responsive" src="https://raw.githubusercontent.com/pilosus/prometheus-client-python-app-grafana-dashboard/master/docs/flask-app-3.png" /&gt;&lt;/a&gt;
&lt;p&gt;The variables allow to change some panels charts grouing, intervals,
labels&amp;nbsp;selection.&lt;/p&gt;
&lt;p&gt;Usage is&amp;nbsp;easy:&lt;/p&gt;
&lt;ol class="arabic simple"&gt;
&lt;li&gt;Install &lt;a class="reference external" href="https://github.com/pilosus/flask_prometheus_metrics"&gt;flask_prometheus_metrics&lt;/a&gt; exporter to your Flask&amp;nbsp;application&lt;/li&gt;
&lt;li&gt;Make Prometheus scraping your app&amp;#8217;s &lt;tt class="docutils literal"&gt;/metrics&lt;/tt&gt; endpoint&lt;/li&gt;
&lt;li&gt;Import &lt;a class="reference external" href="https://github.com/pilosus/prometheus-client-python-app-grafana-dashboard/blob/master/flask-web-app.json"&gt;flask-web-app.json&lt;/a&gt; at &lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;https://&amp;lt;your-grafana-domain&amp;gt;.tld/dashboard/import&lt;/span&gt;&lt;/tt&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;If you do not deploy your app in Kubernetes you may need to tweak
&lt;a class="reference external" href="https://github.com/pilosus/prometheus-client-python-app-grafana-dashboard/blob/master/flask-web-app.json"&gt;flask-web-app.json&lt;/a&gt; &lt;tt class="docutils literal"&gt;pod&lt;/tt&gt; labels to meet your needs. Instead of
&lt;tt class="docutils literal"&gt;pod&lt;/tt&gt; you may use &lt;tt class="docutils literal"&gt;instance&lt;/tt&gt; or &lt;tt class="docutils literal"&gt;hostname&lt;/tt&gt; or other label name
depending on how your Prometheus handle your app&amp;#8217;s&amp;nbsp;hostname.&lt;/p&gt;
</content><category term="Blog"></category><category term="prometheus"></category><category term="grafana"></category><category term="visualization"></category><category term="metrics"></category></entry><entry><title>Pelican plugin: word count and reading time statistics</title><link href="https://blog.pilosus.org/posts/2019/05/28/pelican-plugin-word-count/" rel="alternate"></link><published>2019-05-28T12:03:00+02:00</published><updated>2019-05-28T12:03:00+02:00</updated><author><name>Vitaly Samigullin</name></author><id>tag:blog.pilosus.org,2019-05-28:/posts/2019/05/28/pelican-plugin-word-count/</id><summary type="html">&lt;p class="first last"&gt;An open-source plugin for Python Pelican blog platform. It
extends blog posts with reading statistics: number of words
and reading time in&amp;nbsp;minutes.&lt;/p&gt;
</summary><content type="html">&lt;p&gt;I&amp;#8217;ve just released a tiny library for &lt;a class="reference external" href="https://docs.getpelican.com/en/stable/index.html"&gt;Pelican&lt;/a&gt; blog platform called
&lt;a class="reference external" href="https://github.com/pilosus/pilosus_pelican_word_count/"&gt;word count&lt;/a&gt;. It&amp;#8217;s a plugin that extends articles and pages with the
basic text statistics: number of words and approximate reading time in
minutes. You can use it then in your &lt;tt class="docutils literal"&gt;&lt;span class="caps"&gt;HTML&lt;/span&gt;&lt;/tt&gt; template. My own
&lt;a class="reference external" href="https://github.com/pilosus/pilosus-pelican-theme/"&gt;theme&lt;/a&gt; supports it&amp;nbsp;too.&lt;/p&gt;
&lt;p&gt;It turned out that there is another plugin called &lt;a class="reference external" href="https://github.com/getpelican/pelican-plugins/tree/master/post_stats"&gt;post_stats&lt;/a&gt;. It
provides even more extensive stats. But I think it&amp;#8217;s an overkill for
my purposes: I don&amp;#8217;t need text readability features, they won&amp;#8217;t work
for non-English text, they slow down site generation. I also suspect
that &lt;tt class="docutils literal"&gt;post_stats&lt;/tt&gt; is run against all the contents, including
non-text ones, which is also not so good for plugin&amp;nbsp;performance.&lt;/p&gt;
&lt;p&gt;My &lt;a class="reference external" href="https://github.com/pilosus/pilosus_pelican_word_count/"&gt;word count&lt;/a&gt; plugin explicitly runs against text entities
(articles, pages, drafts and translations). It calculates basic
statistics I need for my blog. And that&amp;#8217;s&amp;nbsp;it.&lt;/p&gt;
&lt;p&gt;I prefer to add Pelican plugins as &lt;tt class="docutils literal"&gt;git submodule&lt;/tt&gt;. It makes it
easier to &lt;tt class="docutils literal"&gt;git clone&lt;/tt&gt; the entire blog with all its themes and
plugins on the new machine. It&amp;#8217;s also make it easier to version
plugins and themes you use. Just check out your submodule to the
version needed. Check out my article &lt;a class="reference external" href="https://blog.pilosus.org/posts/2019/05/01/pelican-blog-up-and-running/"&gt;Pelican blog: up and running&lt;/a&gt; for more&amp;nbsp;details.&lt;/p&gt;
&lt;p&gt;You can donwload (or better &lt;tt class="docutils literal"&gt;git clone&lt;/tt&gt;) &lt;tt class="docutils literal"&gt;word_count&lt;/tt&gt; plugin on
the &lt;a class="reference external" href="https://github.com/pilosus/pilosus_pelican_word_count/"&gt;GitHub&lt;/a&gt;.&lt;/p&gt;
</content><category term="Blog"></category><category term="pelican"></category><category term="blog"></category><category term="python"></category><category term="plugin"></category></entry><entry><title>Kubernetes Ingress Troubleshooting: Error Obtaining Endpoints for Service</title><link href="https://blog.pilosus.org/posts/2019/05/26/k8s-ingress-troubleshooting/" rel="alternate"></link><published>2019-05-26T12:00:00+02:00</published><updated>2019-05-26T12:00:00+02:00</updated><author><name>Vitaly Samigullin</name></author><id>tag:blog.pilosus.org,2019-05-26:/posts/2019/05/26/k8s-ingress-troubleshooting/</id><summary type="html">&lt;p class="first last"&gt;Kubernetes introduces a bunch of abstractions. It&amp;#8217;s easy to
mess up with them in your manifest files. Yet Kubernetes
provides great tools for troubleshooting. Here is how you
can tackle problems with Ingress and Service&amp;nbsp;resources.&lt;/p&gt;
</summary><content type="html">&lt;p&gt;Recently I&amp;#8217;ve set up an &lt;a class="reference external" href="https://www.digitalocean.com/community/tutorials/how-to-set-up-an-nginx-ingress-with-cert-manager-on-digitalocean-kubernetes"&gt;Nginx Ingress Controller&lt;/a&gt; on my DigitalOcean
Kubernetes cluster. It&amp;#8217;s make up of a replica set of pods that run an
Nginx web server and watch for &lt;tt class="docutils literal"&gt;Ingress&lt;/tt&gt; resource
deployment. Controller also fires up a &lt;tt class="docutils literal"&gt;LoadBalancer&lt;/tt&gt; service that
routes and balances external traffic to the Nginx pods. In turn Nginx
pods route traffic to your app pods in accordance with rules from
app&amp;#8217;s Ingress manifest. All in all, the whole topology is the&amp;nbsp;following:&lt;/p&gt;
&lt;div class="figure"&gt;
&lt;a class="reference external image-reference" href="https://blog.pilosus.org/images/ingress.png"&gt;&lt;img alt="Kubernetes cluster with Nginx Ingress Controller" class="responsive" src="https://blog.pilosus.org/images/ingress.png" /&gt;&lt;/a&gt;
&lt;/div&gt;
&lt;p&gt;The problem is Kubernetes uses quite a few abstractions (Pods,
Deployments, Services, Ingress, Roles, etc.) so that it&amp;#8217;s easy to make
a mistake. The good news is Kubernetes gives you great tools to
troubleshoot problems you have bumped into. In my case the first
response I&amp;#8217;ve got after I set up an Ingress Controller was Nginx&amp;#8217;s 503
error code (service temporarily unavailable). Here is how I&amp;#8217;ve fixed&amp;nbsp;it.&lt;/p&gt;
&lt;div class="section" id="kubernetes-nginx-ingress-controller-troubleshooting"&gt;
&lt;h2&gt;Kubernetes Nginx Ingress Controller&amp;nbsp;Troubleshooting&lt;/h2&gt;
&lt;p&gt;Let&amp;#8217;s assume we are using &lt;a class="reference external" href="https://github.com/kubernetes/ingress-nginx"&gt;Kubernetes Nginx Ingress Controller&lt;/a&gt; as
there are other implementations too. Its components get deployed into
their own &lt;tt class="docutils literal"&gt;Namespace&lt;/tt&gt; called &lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;ingress-nginx&lt;/span&gt;&lt;/tt&gt;.&lt;/p&gt;
&lt;p&gt;The first thing you are going to see to find out why a service
responds with 503 status code is Nginx logs. Let&amp;#8217;s see a list of pods
with&amp;nbsp;Nginx:&lt;/p&gt;
&lt;pre class="literal-block"&gt;
$ ~ kubectl get pods --namespace ingress-nginx
NAME                                        READY   STATUS    RESTARTS   AGE
nginx-ingress-controller-5694ccb578-l82hc   1/1     Running   0          21h
&lt;/pre&gt;
&lt;p&gt;Having only a signle pod it&amp;#8217;s easier to skim through the logs with
&lt;tt class="docutils literal"&gt;kubectl logs&lt;/tt&gt;. If there were multiple pods it would be much more
convenient to have &lt;span class="caps"&gt;ELK&lt;/span&gt; (or &lt;a class="reference external" href="https://www.digitalocean.com/community/tutorials/how-to-set-up-an-elasticsearch-fluentd-and-kibana-efk-logging-stack-on-kubernetes"&gt;&lt;span class="caps"&gt;EFK&lt;/span&gt;&lt;/a&gt;) stack running in the&amp;nbsp;cluster.&lt;/p&gt;
&lt;p&gt;Looking up the log we&amp;nbsp;see:&lt;/p&gt;
&lt;pre class="literal-block"&gt;
$ kubectl logs -f nginx-ingress-controller-5694ccb578-l82hc --namespace ingress-nginx
...
Error obtaining Endpoints for Service &amp;quot;&amp;lt;namespace&amp;gt;/&amp;lt;your-app-service&amp;gt;&amp;quot;:
no object matching key &amp;quot;&amp;lt;namespace&amp;gt;/&amp;lt;your-app-service&amp;gt;&amp;quot; in local store
...
&lt;/pre&gt;
&lt;p&gt;Let&amp;#8217;s see what endpoints we&amp;nbsp;have:&lt;/p&gt;
&lt;pre class="literal-block"&gt;
$ kubectl get endpoints --namespace &amp;lt;namespace&amp;gt;
...
&amp;lt;namespace&amp;gt; &amp;lt;your-app-service&amp;gt;  &amp;lt;none&amp;gt; 21h
...
&lt;/pre&gt;
&lt;p&gt;Indeed, our service have no endpoints. That means that a &lt;a class="reference external" href="https://kubernetes.io/docs/concepts/services-networking/service/"&gt;Service&lt;/a&gt;
deployed to expose your app&amp;#8217;s pods doesn&amp;#8217;t actually have a virtual &lt;span class="caps"&gt;IP&lt;/span&gt;
address. There are two cases when a service doesn&amp;#8217;t have an &lt;span class="caps"&gt;IP&lt;/span&gt;: it&amp;#8217;s
either &lt;a class="reference external" href="https://kubernetes.io/docs/concepts/services-networking/service/#headless-services"&gt;headless&lt;/a&gt; or you have messed up with &lt;a class="reference external" href="https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/#label-selectors"&gt;label selectors&lt;/a&gt;.
&lt;strong&gt;Check your label selectors carefully!&lt;/strong&gt; You know what you&amp;#8217;re doing
when using headless services. So most likely it&amp;#8217;s a wrong label name
or value that doesn&amp;#8217;t match your app&amp;#8217;s pods! So was in my own case, by
the&amp;nbsp;way.&lt;/p&gt;
&lt;p&gt;Once you fixed your labels reapply your app&amp;#8217;s service and check
endpoints once&amp;nbsp;again:&lt;/p&gt;
&lt;pre class="literal-block"&gt;
$ kubectl get endpoints --namespace &amp;lt;namespace&amp;gt;
...
&amp;lt;namespace&amp;gt;  &amp;lt;your-app-service&amp;gt;  10.244.0.136:8080,10.244.0.144:8080,10.244.0.185:8080  21h
...
&lt;/pre&gt;
&lt;p&gt;Now our service exposes three local &lt;span class="caps"&gt;IP&lt;/span&gt;:port pairs of type
&lt;tt class="docutils literal"&gt;ClusterIP&lt;/tt&gt;! &lt;tt class="docutils literal"&gt;ClusterIP&lt;/tt&gt; is a &lt;a class="reference external" href="https://kubernetes.io/docs/concepts/services-networking/service/#publishing-services-service-types"&gt;service type&lt;/a&gt; that fits best to
the setup with an Ingress Controller and a Load Balancer routing
external traffic to&amp;nbsp;it.&lt;/p&gt;
&lt;p&gt;Request to a service now returns 200 &lt;span class="caps"&gt;OK&lt;/span&gt;.&lt;/p&gt;
&lt;/div&gt;
</content><category term="Blog"></category><category term="k8s"></category><category term="ingress"></category><category term="digitalocean"></category></entry><entry><title>Kubernetes Persistent Volumes: How to List and Copy Files and Directories</title><link href="https://blog.pilosus.org/posts/2019/05/24/k8s-volumes-list-copy/" rel="alternate"></link><published>2019-05-24T20:15:00+02:00</published><updated>2019-05-24T20:15:00+02:00</updated><author><name>Vitaly Samigullin</name></author><id>tag:blog.pilosus.org,2019-05-24:/posts/2019/05/24/k8s-volumes-list-copy/</id><summary type="html">&lt;p&gt;You have created a Kubernetes workload, e.g. &lt;tt class="docutils literal"&gt;Deployment&lt;/tt&gt;, that uses
a &lt;tt class="docutils literal"&gt;PersistentVolume&lt;/tt&gt; and a &lt;tt class="docutils literal"&gt;PersistentVolumeClaim&lt;/tt&gt;. It&amp;#8217;s
provisioned by your cloud platform, say, DigitalOcean. You want to
know what space is available to you, you want to browse files and
directories on your Volume, and you want to copy …&lt;/p&gt;</summary><content type="html">&lt;p&gt;You have created a Kubernetes workload, e.g. &lt;tt class="docutils literal"&gt;Deployment&lt;/tt&gt;, that uses
a &lt;tt class="docutils literal"&gt;PersistentVolume&lt;/tt&gt; and a &lt;tt class="docutils literal"&gt;PersistentVolumeClaim&lt;/tt&gt;. It&amp;#8217;s
provisioned by your cloud platform, say, DigitalOcean. You want to
know what space is available to you, you want to browse files and
directories on your Volume, and you want to copy something from the
Volume to your host machine. How do you do&amp;nbsp;it?&lt;/p&gt;
&lt;p&gt;First, find out your pvc&amp;#8217;s &lt;tt class="docutils literal"&gt;mountPath&lt;/tt&gt;. Your data sits
there. Second, you can access it from the pod that uses the
&lt;tt class="docutils literal"&gt;PersistentVolumeClaim&lt;/tt&gt;. Fire up a terminal on the pod and use your
favourite tools like &lt;tt class="docutils literal"&gt;ls&lt;/tt&gt; and &lt;tt class="docutils literal"&gt;df&lt;/tt&gt; to list files or see stats of
the volume usage. Just make sure that an image your pod container is
using has all the tools you&amp;nbsp;need.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="c1"&gt;# open bash on the pod&lt;/span&gt;
$&lt;span class="w"&gt; &lt;/span&gt;kubectl&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;exec&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;-it&lt;span class="w"&gt; &lt;/span&gt;redis-master-0&lt;span class="w"&gt; &lt;/span&gt;bash

&lt;span class="c1"&gt;# see disk usage stats&lt;/span&gt;
&lt;span class="c1"&gt;# volume is mounted under /data&lt;/span&gt;
I&lt;span class="w"&gt; &lt;/span&gt;have&lt;span class="w"&gt; &lt;/span&gt;no&lt;span class="w"&gt; &lt;/span&gt;name!@redis-master-0:/$&lt;span class="w"&gt; &lt;/span&gt;df&lt;span class="w"&gt; &lt;/span&gt;-h
Filesystem&lt;span class="w"&gt;                                                                &lt;/span&gt;Size&lt;span class="w"&gt;  &lt;/span&gt;Used&lt;span class="w"&gt; &lt;/span&gt;Avail&lt;span class="w"&gt; &lt;/span&gt;Use%&lt;span class="w"&gt; &lt;/span&gt;Mounted&lt;span class="w"&gt; &lt;/span&gt;on
overlay&lt;span class="w"&gt;                                                                   &lt;/span&gt;158G&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="m"&gt;6&lt;/span&gt;.5G&lt;span class="w"&gt;  &lt;/span&gt;145G&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="m"&gt;5&lt;/span&gt;%&lt;span class="w"&gt; &lt;/span&gt;/
tmpfs&lt;span class="w"&gt;                                                                      &lt;/span&gt;64M&lt;span class="w"&gt;     &lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;64M&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;%&lt;span class="w"&gt; &lt;/span&gt;/dev
tmpfs&lt;span class="w"&gt;                                                                     &lt;/span&gt;&lt;span class="m"&gt;3&lt;/span&gt;.9G&lt;span class="w"&gt;     &lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="m"&gt;3&lt;/span&gt;.9G&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;%&lt;span class="w"&gt; &lt;/span&gt;/sys/fs/cgroup
/dev/vda1&lt;span class="w"&gt;                                                                 &lt;/span&gt;158G&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="m"&gt;6&lt;/span&gt;.5G&lt;span class="w"&gt;  &lt;/span&gt;145G&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="m"&gt;5&lt;/span&gt;%&lt;span class="w"&gt; &lt;/span&gt;/health
/dev/disk/by-id/scsi-0DO_Volume_pvc-ae31a80d-7b23-11e9-bffb-0ab3124b7b1c&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="m"&gt;7&lt;/span&gt;.9G&lt;span class="w"&gt;   &lt;/span&gt;36M&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="m"&gt;7&lt;/span&gt;.4G&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;%&lt;span class="w"&gt; &lt;/span&gt;/data
shm&lt;span class="w"&gt;                                                                        &lt;/span&gt;64M&lt;span class="w"&gt;     &lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;64M&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;%&lt;span class="w"&gt; &lt;/span&gt;/dev/shm
tmpfs&lt;span class="w"&gt;                                                                     &lt;/span&gt;&lt;span class="m"&gt;3&lt;/span&gt;.9G&lt;span class="w"&gt;   &lt;/span&gt;12K&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="m"&gt;3&lt;/span&gt;.9G&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;%&lt;span class="w"&gt; &lt;/span&gt;/run/secrets/kubernetes.io/serviceaccount
tmpfs&lt;span class="w"&gt;                                                                     &lt;/span&gt;&lt;span class="m"&gt;3&lt;/span&gt;.9G&lt;span class="w"&gt;     &lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="m"&gt;3&lt;/span&gt;.9G&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;%&lt;span class="w"&gt; &lt;/span&gt;/proc/acpi
tmpfs&lt;span class="w"&gt;                                                                     &lt;/span&gt;&lt;span class="m"&gt;3&lt;/span&gt;.9G&lt;span class="w"&gt;     &lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="m"&gt;3&lt;/span&gt;.9G&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;%&lt;span class="w"&gt; &lt;/span&gt;/sys/firmware

&lt;span class="c1"&gt;# list files&lt;/span&gt;
$&lt;span class="w"&gt; &lt;/span&gt;ls&lt;span class="w"&gt; &lt;/span&gt;-l&lt;span class="w"&gt; &lt;/span&gt;/data
total&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;24&lt;/span&gt;
-rw-r--r--&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;1001&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;1001&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="m"&gt;130&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;May&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;20&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;18&lt;/span&gt;:23&lt;span class="w"&gt; &lt;/span&gt;appendonly.aof
-rw-r--r--&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;1001&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;1001&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="m"&gt;175&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;May&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;20&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;17&lt;/span&gt;:22&lt;span class="w"&gt; &lt;/span&gt;dump.rdb
drwxrws---&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;2&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;root&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;1001&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;16384&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;May&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;20&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;17&lt;/span&gt;:22&lt;span class="w"&gt; &lt;/span&gt;lost+found
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Third, you can copy files or directories from or to a Kubernetes pod using &lt;tt class="docutils literal"&gt;kubectl cp&lt;/tt&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;kubectl&lt;span class="w"&gt; &lt;/span&gt;cp&lt;span class="w"&gt; &lt;/span&gt;default/redis-master-0:/data/dump.rdb&lt;span class="w"&gt; &lt;/span&gt;/home/vitaly/Downloads/dump.rdb
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;See &lt;tt class="docutils literal"&gt;kubectl cp &lt;span class="pre"&gt;--help&lt;/span&gt;&lt;/tt&gt; for more&amp;nbsp;details.&lt;/p&gt;
</content><category term="Blog"></category><category term="k8s"></category><category term="kubectl"></category><category term="digitalocean"></category></entry><entry><title>Helm stable/redis chart: slave unable to connect to master</title><link href="https://blog.pilosus.org/posts/2019/05/20/helm-stable-redis-slave-crashloopbackoff/" rel="alternate"></link><published>2019-05-20T19:20:00+02:00</published><updated>2019-05-20T19:20:00+02:00</updated><author><name>Vitaly Samigullin</name></author><id>tag:blog.pilosus.org,2019-05-20:/posts/2019/05/20/helm-stable-redis-slave-crashloopbackoff/</id><summary type="html">&lt;p class="first last"&gt;Things to take care of when installing Helm&amp;#8217;s stable/redis
chart with master-slave topology&amp;nbsp;enabled&lt;/p&gt;
</summary><content type="html">&lt;p&gt;Recently I&amp;#8217;ve tried to install &lt;a class="reference external" href="https://github.com/helm/charts/tree/master/stable/redis"&gt;stable/redis&lt;/a&gt; Helm chart version
8.0.1. I used production-ready values taken from the &lt;a class="reference external" href="https://github.com/helm/charts/blob/master/stable/redis/values-production.yaml"&gt;repo&lt;/a&gt; and
slightly modified for needs. I used enabled by default clusterisation,
but with only one slave node instead of&amp;nbsp;two.&lt;/p&gt;
&lt;p&gt;The release deployed sucessfully, but the slave node kept having
problem with liveness probe that eventually led to a
CrashLoopBackoff. The logs showed that my slave node is &amp;#8220;Unable to
connect to &lt;span class="caps"&gt;MASTER&lt;/span&gt;&amp;#8221;, although I could easily connect to the master from
the &lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;redis-client&lt;/span&gt;&lt;/tt&gt; node with &lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;redis-cli&lt;/span&gt;&lt;/tt&gt;:&lt;/p&gt;
&lt;pre class="literal-block"&gt;
$ kubectl run --namespace default redis-client --rm --tty -i --restart='Never' \
  --env REDIS_PASSWORD=my-redis-password \
 --image docker.io/bitnami/redis:5.0.5 -- bash

# redis-cli -h redis-master -a my-redis-password
redis-master:6379&amp;gt;
&lt;/pre&gt;
&lt;p&gt;It turned out that a &lt;tt class="docutils literal"&gt;networkPolicy&lt;/tt&gt; section in
&lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;values-production.yaml&lt;/span&gt;&lt;/tt&gt; was a culprit. &lt;tt class="docutils literal"&gt;networkPolicy.enable&lt;/tt&gt; is
set to &lt;tt class="docutils literal"&gt;true&lt;/tt&gt;, but &lt;tt class="docutils literal"&gt;networkPolicy.allowExternal: true&lt;/tt&gt; is
commented evaluating to &lt;tt class="docutils literal"&gt;false&lt;/tt&gt; by default. That makes redis nodes
only accessible from the pods with a label &lt;tt class="docutils literal"&gt;{{ template
&amp;quot;redis.fullname&amp;quot; . &lt;span class="pre"&gt;}}-client:&lt;/span&gt; &amp;quot;true&amp;quot;&lt;/tt&gt;.&lt;/p&gt;
&lt;p&gt;So make master accessible to slave you&amp;nbsp;either:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;set &lt;tt class="docutils literal"&gt;networkPolicy.enable: false&lt;/tt&gt; to turn off &lt;a class="reference external" href="https://kubernetes.io/docs/tasks/administer-cluster/declare-network-policy/"&gt;NetworkPolicy&lt;/a&gt;,&amp;nbsp;or&lt;/li&gt;
&lt;li&gt;set &lt;tt class="docutils literal"&gt;networkPolicy.allowExternal: true&lt;/tt&gt; (i.e. by commenting it out
in the &lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;values-production.yaml&lt;/span&gt;&lt;/tt&gt;)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;That&amp;#8217;s it. No more failed liveness probes or CrashLoopBackoff. It&amp;nbsp;works.&lt;/p&gt;
&lt;p&gt;&lt;span class="caps"&gt;P.S.&lt;/span&gt; I think that production values example in &lt;tt class="docutils literal"&gt;stable&lt;/tt&gt; should
probably be consistent. For example,
&lt;a class="reference external" href="https://github.com/helm/charts/blob/master/stable/postgresql/values-production.yaml"&gt;stable/postgresql production values file&lt;/a&gt;&amp;nbsp;contains:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="nt"&gt;networkPolicy&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;enabled&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;false&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;allowExternal&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;true&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;while &lt;a class="reference external" href="https://github.com/helm/charts/blob/master/stable/redis/values-production.yaml"&gt;stable/redis production values file&lt;/a&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="nt"&gt;networkPolicy&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;enabled&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;true&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="c1"&gt;#allowExternal: true&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
</content><category term="Blog"></category><category term="k8s"></category><category term="helm"></category><category term="chart"></category><category term="redis"></category></entry><entry><title>Helm Cheat Sheet: Personal Top of the Little Known Commands and Features</title><link href="https://blog.pilosus.org/posts/2019/05/20/helm-cheat-sheet-little-known-commands/" rel="alternate"></link><published>2019-05-20T11:12:00+02:00</published><updated>2019-05-20T15:50:00+02:00</updated><author><name>Vitaly Samigullin</name></author><id>tag:blog.pilosus.org,2019-05-20:/posts/2019/05/20/helm-cheat-sheet-little-known-commands/</id><summary type="html">&lt;p&gt;Helm is a package manager for Kubernetes. It&amp;#8217;s got an outstanding
&lt;a class="reference external" href="https://helm.sh/"&gt;official documentation&lt;/a&gt; and tons of tutorials on the
internet. Nonetheless Helm has some helpful commands and features that
are easily overlooked when reading the documentation. I won&amp;#8217;t even try
to cover them all, as it&amp;#8217;s quite …&lt;/p&gt;</summary><content type="html">&lt;p&gt;Helm is a package manager for Kubernetes. It&amp;#8217;s got an outstanding
&lt;a class="reference external" href="https://helm.sh/"&gt;official documentation&lt;/a&gt; and tons of tutorials on the
internet. Nonetheless Helm has some helpful commands and features that
are easily overlooked when reading the documentation. I won&amp;#8217;t even try
to cover them all, as it&amp;#8217;s quite subjective what to count as
&amp;#8220;helpful&amp;#8221;. What I will try to do though is to give a direction on how
to find such features by&amp;nbsp;yourself.&lt;/p&gt;
&lt;div class="section" id="cli-help"&gt;
&lt;h2&gt;&lt;span class="caps"&gt;CLI&lt;/span&gt;&amp;nbsp;Help&lt;/h2&gt;
&lt;p&gt;Just like &lt;tt class="docutils literal"&gt;kubectl&lt;/tt&gt; and &lt;a class="reference external" href="https://blog.pilosus.org/posts/2019/05/18/minikube-cheat-sheet/"&gt;minikube&lt;/a&gt; Helm&amp;#8217;s &lt;span class="caps"&gt;CLI&lt;/span&gt; tool follows
the same help patterns for commands and&amp;nbsp;subcommands:&lt;/p&gt;
&lt;pre class="literal-block"&gt;
$ helm --help
$ helm get --help
$ helm get values --help
&lt;/pre&gt;
&lt;/div&gt;
&lt;div class="section" id="shell-completion"&gt;
&lt;h2&gt;Shell&amp;nbsp;Completion&lt;/h2&gt;
&lt;p&gt;Again, just like &lt;tt class="docutils literal"&gt;kubectl&lt;/tt&gt; and &lt;tt class="docutils literal"&gt;minikube&lt;/tt&gt; Helm provides shell
commands completion for &lt;tt class="docutils literal"&gt;bash&lt;/tt&gt; and &lt;tt class="docutils literal"&gt;zsh&lt;/tt&gt;. See more in&amp;nbsp;help:&lt;/p&gt;
&lt;pre class="literal-block"&gt;
$ helm completion --help
&lt;/pre&gt;
&lt;p&gt;Completion helps a lot both everyday interaction with Helm and its &lt;span class="caps"&gt;CLI&lt;/span&gt;&amp;nbsp;exploration.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="commands-aliases"&gt;
&lt;h2&gt;Commands&amp;nbsp;Aliases&lt;/h2&gt;
&lt;p&gt;Some Helm commands have aliases. You can see then in the corresponding
section in help. As an example these two commands are the&amp;nbsp;same:&lt;/p&gt;
&lt;pre class="literal-block"&gt;
$ helm list
$ helm ls
&lt;/pre&gt;
&lt;/div&gt;
&lt;div class="section" id="chart-values"&gt;
&lt;h2&gt;Chart&amp;nbsp;Values&lt;/h2&gt;
&lt;p&gt;Helm charts usually get initialized with some default values. It&amp;#8217;s
worth inspect them before&amp;nbsp;installation:&lt;/p&gt;
&lt;pre class="literal-block"&gt;
$ helm inspect values stable/postgresql
&lt;/pre&gt;
&lt;p&gt;Sometimes it&amp;#8217;s also very helpful to get values from the deployed
release, both explicitly set by user and taken from the&amp;nbsp;defaults:&lt;/p&gt;
&lt;pre class="literal-block"&gt;
$ helm get values --all &amp;lt;your-release-name&amp;gt;
&lt;/pre&gt;
&lt;/div&gt;
&lt;div class="section" id="chart-versions"&gt;
&lt;h2&gt;Chart&amp;nbsp;Versions&lt;/h2&gt;
&lt;p&gt;Every chart must have a &lt;a class="reference external" href="https://helm.sh/docs/charts/#charts-and-versioning"&gt;version number&lt;/a&gt;. Charts are versioned under
version control systems (&lt;span class="caps"&gt;VSC&lt;/span&gt;) and installed from the &lt;a class="reference external" href="https://helm.sh/docs/chart_repository/#the-chart-repository-guide"&gt;chart
repositories&lt;/a&gt;. You can list existing repos&amp;nbsp;with:&lt;/p&gt;
&lt;pre class="literal-block"&gt;
$ helm repo list
&lt;/pre&gt;
&lt;p&gt;Don&amp;#8217;t forget to update packages information for your repos&amp;nbsp;with:&lt;/p&gt;
&lt;pre class="literal-block"&gt;
$ helm repo update
&lt;/pre&gt;
&lt;p&gt;Sometimes you may want to install specific version of chart. List versions&amp;nbsp;first:&lt;/p&gt;
&lt;pre class="literal-block"&gt;
$ helm search --versions stable/postgresql
&lt;/pre&gt;
&lt;p&gt;Now install the chart with a &lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;--version&lt;/span&gt;&lt;/tt&gt; argument:&lt;/p&gt;
&lt;pre class="literal-block"&gt;
$ helm intall --version 3.17.0 stable/postgresql
&lt;/pre&gt;
&lt;/div&gt;
&lt;div class="section" id="chart-deletion"&gt;
&lt;h2&gt;Chart&amp;nbsp;deletion&lt;/h2&gt;
&lt;p&gt;If you ommit &lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;--name&lt;/span&gt; &lt;span class="pre"&gt;&amp;lt;your-release-name&amp;gt;&lt;/span&gt;&lt;/tt&gt; argument when doing &lt;tt class="docutils literal"&gt;helm
install&lt;/tt&gt; you get a random release name. Release names are reserved
even after your release is deleted, so that you can do a&amp;nbsp;rollback.&lt;/p&gt;
&lt;p&gt;If you are using a your own release name you can get into trouble when
reinstalling chart with the same name. In order to avoid name clash
delete your release like&amp;nbsp;so:&lt;/p&gt;
&lt;pre class="literal-block"&gt;
$ helm delete --purge &amp;lt;your-release-name&amp;gt;
&lt;/pre&gt;
&lt;p&gt;In this case you won&amp;#8217;t be able to do a rollback&amp;nbsp;though.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="emulate-installation"&gt;
&lt;h2&gt;Emulate&amp;nbsp;Installation&lt;/h2&gt;
&lt;p&gt;We&amp;#8217;ve already covered &lt;tt class="docutils literal"&gt;inspect&lt;/tt&gt; a little bit. It allows you to see
chart&amp;#8217;s info, values and a &lt;span class="caps"&gt;README&lt;/span&gt; file. But sometimes you may want to
get dirty in chart&amp;#8217;s guts (actually, I always do as I don&amp;#8217;t really
like the idea of installing something I don&amp;#8217;t understand in my k8s
cluster). In this case you may want to emulate installation with
verbose&amp;nbsp;output:&lt;/p&gt;
&lt;pre class="literal-block"&gt;
$ helm install --dry-run --debug --version 3.17.0 stable/postgresql
&lt;/pre&gt;
&lt;p&gt;Having chart manifests at your fingertips is a great way to save time
and effort browsing for the source&amp;nbsp;code.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="conclusion"&gt;
&lt;h2&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;Helm&amp;#8217;s got almost everything needed to start working with it in its
help. It&amp;#8217;s consistent and mimics other k8s &lt;span class="caps"&gt;CLI&lt;/span&gt; tools like &lt;tt class="docutils literal"&gt;kubectl&lt;/tt&gt;
and &lt;tt class="docutils literal"&gt;minikube&lt;/tt&gt;. Take your time to explore the help! I didn&amp;#8217;t really
try to cover everything here. I just scratched the surface with less
known things that you actually could found out by exploring the&amp;nbsp;help.&lt;/p&gt;
&lt;p&gt;I&amp;#8217;m going to update this post as I find new features worth&amp;nbsp;sharing.&lt;/p&gt;
&lt;/div&gt;
</content><category term="Blog"></category><category term="k8s"></category><category term="helm"></category></entry><entry><title>Minikube Cheat Sheet: most helpful commands and features I wish I knew from the start</title><link href="https://blog.pilosus.org/posts/2019/05/18/minikube-cheat-sheet/" rel="alternate"></link><published>2019-05-18T19:00:00+02:00</published><updated>2019-05-18T19:00:00+02:00</updated><author><name>Vitaly Samigullin</name></author><id>tag:blog.pilosus.org,2019-05-18:/posts/2019/05/18/minikube-cheat-sheet/</id><summary type="html">&lt;p class="first last"&gt;My personal top of Minikube&amp;#8217;s features that can save you&amp;nbsp;time&lt;/p&gt;
</summary><content type="html">&lt;p&gt;Minikube is a tool for running Kubernetes cluster on your local
machine. Recently I&amp;#8217;ve posted about Minikube&amp;#8217;s &lt;a class="reference external" href="https://blog.pilosus.org/posts/2019/05/18/minikube-hight-cpu-usage-linux/"&gt;ugly parts&lt;/a&gt;. Now
it&amp;#8217;s time for the good&amp;nbsp;parts!&lt;/p&gt;
&lt;div class="section" id="disclaimer"&gt;
&lt;h2&gt;Disclaimer&lt;/h2&gt;
&lt;p&gt;At the time of writing I used Minikube version&amp;nbsp;1.0.0&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;$&lt;span class="w"&gt; &lt;/span&gt;minikube&lt;span class="w"&gt; &lt;/span&gt;version
minikube&lt;span class="w"&gt; &lt;/span&gt;version:&lt;span class="w"&gt; &lt;/span&gt;v1.0.0
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;It seems that Minikube is still under active development. So some
things may break for your version. You may also want to check every
now and then that you are using the latest version&amp;nbsp;available:&lt;/p&gt;
&lt;pre class="literal-block"&gt;
$ minikube update-check
&lt;/pre&gt;
&lt;/div&gt;
&lt;div class="section" id="help"&gt;
&lt;h2&gt;Help&lt;/h2&gt;
&lt;p&gt;Minikube&amp;#8217;s online presence is strange. You can find some mentions in
&lt;a class="reference external" href="https://kubernetes.io/docs/home/"&gt;Kubernetes Documentation&lt;/a&gt;. You can find a bunch of markdown files
with &lt;a class="reference external" href="https://github.com/kubernetes/minikube/blob/master/docs/README.md"&gt;Advanced Topics&lt;/a&gt; in Minikube&amp;#8217;s GitHub repo. And that&amp;#8217;s it about
documentation. But Minikube&amp;#8217;s help is a great source of
knowledge. It&amp;#8217;s consistently written for every command and&amp;nbsp;subcommand:&lt;/p&gt;
&lt;pre class="literal-block"&gt;
$ minikube --help
$ minikube start --help
$ minikube addons configure --help
&lt;/pre&gt;
&lt;/div&gt;
&lt;div class="section" id="minikube-shell-completion"&gt;
&lt;h2&gt;Minikube Shell&amp;nbsp;Completion&lt;/h2&gt;
&lt;p&gt;Minikube has a great &lt;span class="caps"&gt;CLI&lt;/span&gt;. But you can&amp;#8217;t really enjoy a &lt;span class="caps"&gt;CLI&lt;/span&gt; without
&lt;a class="reference external" href="https://github.com/scop/bash-completion"&gt;shell commands completion&lt;/a&gt;. That&amp;#8217;s why Minikube supports commands
completion for &lt;tt class="docutils literal"&gt;bash&lt;/tt&gt; and &lt;tt class="docutils literal"&gt;zsh&lt;/tt&gt; shells. Again, everything you need
is in help&amp;nbsp;already:&lt;/p&gt;
&lt;pre class="literal-block"&gt;
$ minikube completion --help
&lt;/pre&gt;
&lt;/div&gt;
&lt;div class="section" id="resources"&gt;
&lt;h2&gt;Resources&lt;/h2&gt;
&lt;p&gt;By default you have got 2 &lt;span class="caps"&gt;CPU&lt;/span&gt;, 2048M &lt;span class="caps"&gt;RAM&lt;/span&gt;, 20G disk allocated for your
k8s cluster. It&amp;#8217;s okay for the most cases. Still you may need to tweak
it depending on what are you doing. Use &lt;tt class="docutils literal"&gt;start&lt;/tt&gt; arguments to get
what you&amp;nbsp;want:&lt;/p&gt;
&lt;pre class="literal-block"&gt;
$ minikube start --cpus 4 --memory 4096 --vm-driver kvm2
&lt;/pre&gt;
&lt;p&gt;By now you&amp;#8217;ve already got used to using &lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;--help&lt;/span&gt;&lt;/tt&gt;, haven&amp;#8217;t&amp;nbsp;you?&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="configuration"&gt;
&lt;h2&gt;Configuration&lt;/h2&gt;
&lt;p&gt;If you experiment with your local k8s cluster a lot, deleting it and
starting all over with new settings, you may want to make some
settings permanent. They are saved under
&lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;~/.minikube/config/config.json&lt;/span&gt;&lt;/tt&gt;. You can edit it manually, but &lt;span class="caps"&gt;CLI&lt;/span&gt;
provides a better&amp;nbsp;alternative:&lt;/p&gt;
&lt;pre class="literal-block"&gt;
# get list of settings
$ minikube config view

# set cpus; changes will take effect upon cluster delete and start
$ minikube config set cpus 4
&lt;/pre&gt;
&lt;/div&gt;
&lt;div class="section" id="services"&gt;
&lt;h2&gt;Services&lt;/h2&gt;
&lt;p&gt;Your Kubernetes &lt;tt class="docutils literal"&gt;Service&lt;/tt&gt; that&amp;#8217;s provisioned to be available outside
of the cluster&amp;#8217;s network can be opened in your default browser from
the command&amp;nbsp;line:&lt;/p&gt;
&lt;pre class="literal-block"&gt;
$ minikube service &amp;lt;your-service-name&amp;gt;
&lt;/pre&gt;
&lt;p&gt;To see the full list of services&amp;nbsp;do:&lt;/p&gt;
&lt;pre class="literal-block"&gt;
$ minikube service list
&lt;/pre&gt;
&lt;/div&gt;
&lt;div class="section" id="monitoring"&gt;
&lt;h2&gt;Monitoring&lt;/h2&gt;
&lt;p&gt;Minikube&amp;#8217;s shipped with a bunch of great addons. You can list them,
configure, enable, disable and open&amp;nbsp;up:&lt;/p&gt;
&lt;pre class="literal-block"&gt;
$ minikube addons list
$ minikube addons --help
&lt;/pre&gt;
&lt;p&gt;The most helpful addons I use daily to look up cluster&amp;#8217;s status are
&lt;tt class="docutils literal"&gt;dashboard&lt;/tt&gt; and &lt;tt class="docutils literal"&gt;heapster&lt;/tt&gt;. Dashboard is a web &lt;span class="caps"&gt;UI&lt;/span&gt; enabled by
default. It&amp;#8217;s a great tool for monitoring both your pods
(&lt;tt class="docutils literal"&gt;Workloads&lt;/tt&gt; section) and the whole cluster (&lt;tt class="docutils literal"&gt;Cluster&lt;/tt&gt;
section). Fire up you dashboard&amp;nbsp;with:&lt;/p&gt;
&lt;pre class="literal-block"&gt;
$ minikube dashboard
&lt;/pre&gt;
&lt;p&gt;&lt;tt class="docutils literal"&gt;Heapster&lt;/tt&gt; is Grafana-based monitoring tool. Enable and open it&amp;nbsp;with:&lt;/p&gt;
&lt;pre class="literal-block"&gt;
$ minikube addons enable heapster
$ minikube addons open heapster
&lt;/pre&gt;
&lt;p&gt;Sign in with &lt;tt class="docutils literal"&gt;admin/admin&lt;/tt&gt; credentials and go over &lt;tt class="docutils literal"&gt;Pods&lt;/tt&gt; or
&lt;tt class="docutils literal"&gt;Cluster&lt;/tt&gt; dashboards for super cool statistics&amp;nbsp;charts!&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="ssh"&gt;
&lt;h2&gt;&lt;span class="caps"&gt;SSH&lt;/span&gt;&lt;/h2&gt;
&lt;p&gt;When working with &lt;a class="reference external" href="https://kubernetes.io/docs/concepts/storage/persistent-volumes/"&gt;Persistent Volumes&lt;/a&gt; you may need to do some ops on
your cluster&amp;#8217;s file system. Use secure shell to do&amp;nbsp;it:&lt;/p&gt;
&lt;pre class="literal-block"&gt;
# log in `remotely`
$ minikube ssh

# set `docker` user password
&amp;gt; sudo passwd docker
&amp;gt; exit
&lt;/pre&gt;
&lt;p&gt;Now that you have logged in &amp;#8220;remotely&amp;#8221; you can create directories to
mount your Persistent Volume, copy files,&amp;nbsp;etc.:&lt;/p&gt;
&lt;pre class="literal-block"&gt;
# copy directory recursively from minikube cluster to the host machine
$ scp -i $(minikube ssh-key) -r docker&amp;#64;$(minikube ip):/mnt/data/ .
&lt;/pre&gt;
&lt;/div&gt;
&lt;div class="section" id="afterword"&gt;
&lt;h2&gt;Afterword&lt;/h2&gt;
&lt;p&gt;As with every complex piece of software in active development
Minikube&amp;#8217;s experience may be &lt;a class="reference external" href="https://blog.pilosus.org/posts/2019/05/18/minikube-hight-cpu-usage-linux/"&gt;frustrating&lt;/a&gt;. Still
Minikube is a great tool with a bunch of cool features. I wish I&amp;#8217;d
read an overview like this one when I first launched Minikube. Hope it
will help&amp;nbsp;someone.&lt;/p&gt;
&lt;/div&gt;
</content><category term="Blog"></category><category term="k8s"></category><category term="minikube"></category></entry><entry><title>Minikube high CPU usage even with no workload on Linux machine</title><link href="https://blog.pilosus.org/posts/2019/05/18/minikube-hight-cpu-usage-linux/" rel="alternate"></link><published>2019-05-18T11:00:00+02:00</published><updated>2019-05-18T11:00:00+02:00</updated><author><name>Vitaly Samigullin</name></author><id>tag:blog.pilosus.org,2019-05-18:/posts/2019/05/18/minikube-hight-cpu-usage-linux/</id><summary type="html">&lt;p&gt;Minikube is a tool that allows you to run Kubernetes for local
development. It&amp;#8217;s adviced to run all over the &lt;a class="reference external" href="https://kubernetes.io/docs/setup/minikube/"&gt;k8s documentation&lt;/a&gt;, in
the tech blogs. Still Minikube looks like a piece of software that is
not quite ready for a smooth development experience. At the time of
writing …&lt;/p&gt;</summary><content type="html">&lt;p&gt;Minikube is a tool that allows you to run Kubernetes for local
development. It&amp;#8217;s adviced to run all over the &lt;a class="reference external" href="https://kubernetes.io/docs/setup/minikube/"&gt;k8s documentation&lt;/a&gt;, in
the tech blogs. Still Minikube looks like a piece of software that is
not quite ready for a smooth development experience. At the time of
writing minikube&amp;#8217;s got a bunch of &lt;a class="reference external" href="https://github.com/kubernetes/minikube/issues"&gt;issues&lt;/a&gt;. Some of them are open for
a long time, hardly reproducible and quite frustrating. &lt;span class="caps"&gt;CPU&lt;/span&gt; usage with
no workload to services is one of&amp;nbsp;them.&lt;/p&gt;
&lt;p&gt;Recently I&amp;#8217;ve bumped into a problem when an idle minikube process
consumes up to 100% of &lt;span class="caps"&gt;CPU&lt;/span&gt;. After a while &lt;tt class="docutils literal"&gt;apiserver&lt;/tt&gt; stops
responding making your cluster not really functioning. It turned out
I&amp;#8217;m &lt;a class="reference external" href="https://github.com/kubernetes/minikube/issues/3207"&gt;not alone&lt;/a&gt;. I run minikube on my Lenovo Thinkpad X1 Carbon
machine with 16G &lt;span class="caps"&gt;RAM&lt;/span&gt; and 8 cores (i7 8th gen) on board. I use Ubuntu
18.04 with &lt;tt class="docutils literal"&gt;kvm2&lt;/tt&gt; &lt;span class="caps"&gt;VM&lt;/span&gt; driver. So I found this high &lt;span class="caps"&gt;CPU&lt;/span&gt; consumption as
quite unexpected and annoying. I&amp;#8217;ve solved this problem. But as it
sometimes happens in the software development, I&amp;#8217;m still not sure what
exactly helped. So taking into account some black magic, let&amp;#8217;s see
what witch brew&amp;#8217;s ingredients &lt;em&gt;may&lt;/em&gt;&amp;nbsp;help.&lt;/p&gt;
&lt;div class="section" id="before-you-begin-monitoring"&gt;
&lt;h2&gt;Before you begin:&amp;nbsp;monitoring&lt;/h2&gt;
&lt;p&gt;&lt;span class="dquo"&gt;&amp;#8220;&lt;/span&gt;If you can&amp;#8217;t measure it, you can&amp;#8217;t improve it&amp;#8221;. Before making any
tweaks start measuring &lt;span class="caps"&gt;CPU&lt;/span&gt; usage. Altough &lt;tt class="docutils literal"&gt;top&lt;/tt&gt; tool is cool, it
just cannot beat its successor &lt;tt class="docutils literal"&gt;htop&lt;/tt&gt;. It allows you to tag
processes, filter and sort quickly, see processes and threads in a
flat and a tree&amp;nbsp;modes.&lt;/p&gt;
&lt;a class="reference external image-reference" href="https://blog.pilosus.org/images/htop-minikube.png"&gt;&lt;img alt="htop tool" class="responsive" src="https://blog.pilosus.org/images/htop-minikube.png" /&gt;&lt;/a&gt;
&lt;p&gt;On &lt;span class="caps"&gt;GNU&lt;/span&gt;/Linux you most likely want to track &lt;a class="reference external" href="https://wiki.archlinux.org/index.php/QEMU"&gt;&lt;span class="caps"&gt;QEMU&lt;/span&gt; processes&lt;/a&gt; resource&amp;nbsp;usage.&lt;/p&gt;
&lt;p&gt;Minikube&amp;#8217;s &lt;tt class="docutils literal"&gt;dashboard&lt;/tt&gt; plugin is also a great tool for monitoing
resources &amp;#8212; per pod (Overview section) or for the whole cluster
(Cluster section). To use dashboard, enable the addon and open&amp;nbsp;it:&lt;/p&gt;
&lt;pre class="literal-block"&gt;
minikube addons enable dashboard
minikube dashboard
&lt;/pre&gt;
&lt;/div&gt;
&lt;div class="section" id="use-recommended-virtual-machine-drivers"&gt;
&lt;h2&gt;Use recommended Virtual Machine&amp;nbsp;drivers&lt;/h2&gt;
&lt;p&gt;Since I&amp;#8217;m on Ubuntu &lt;span class="caps"&gt;GNU&lt;/span&gt;/Linux I went for a &lt;tt class="docutils literal"&gt;kvm2&lt;/tt&gt; driver. Get the
recommended &lt;a class="reference external" href="https://github.com/kubernetes/minikube/blob/master/docs/drivers.md"&gt;&lt;span class="caps"&gt;VM&lt;/span&gt; driver&lt;/a&gt; for your specific&amp;nbsp;platform.&lt;/p&gt;
&lt;p&gt;Follow excellent Arch Linux Wiki &lt;a class="reference external" href="https://wiki.archlinux.org/index.php/KVM"&gt;&lt;span class="caps"&gt;KVM&lt;/span&gt; article&lt;/a&gt; to make sure you have
&lt;strong&gt;hardware and Linux kernel support for &lt;span class="caps"&gt;KVM&lt;/span&gt;&lt;/strong&gt;. You may enable
virtualisation support in your &lt;span class="caps"&gt;BIOS&lt;/span&gt; and/or load &lt;span class="caps"&gt;KVM&lt;/span&gt; kernel modules&amp;nbsp;needed.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="use-latest-os-virtualisation-libraries-qemu-tools-vm-drivers"&gt;
&lt;h2&gt;Use latest &lt;span class="caps"&gt;OS&lt;/span&gt; virtualisation libraries, &lt;span class="caps"&gt;QEMU&lt;/span&gt; tools, &lt;span class="caps"&gt;VM&lt;/span&gt;&amp;nbsp;drivers&lt;/h2&gt;
&lt;p&gt;This is what I believe really fixed my problem. Make sure&amp;nbsp;you:&lt;/p&gt;
&lt;ol class="arabic"&gt;
&lt;li&gt;&lt;p class="first"&gt;Update your &lt;span class="caps"&gt;OS&lt;/span&gt; package&amp;nbsp;information:&lt;/p&gt;
&lt;pre class="literal-block"&gt;
# in my case of Ubuntu 18.04 I use
sudo apt update
&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p class="first"&gt;Install the &lt;strong&gt;latest&lt;/strong&gt; libs and tools (like &lt;tt class="docutils literal"&gt;libvirt&lt;/tt&gt;, &lt;tt class="docutils literal"&gt;qemu&lt;/tt&gt;),
enable &lt;tt class="docutils literal"&gt;systemD&lt;/tt&gt; services needed as per &lt;a class="reference external" href="https://github.com/kubernetes/minikube/blob/master/docs/drivers.md#kvm2-driver"&gt;&lt;span class="caps"&gt;KVM&lt;/span&gt; drivers&amp;nbsp;guide&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p class="first"&gt;Install the &lt;strong&gt;latest&lt;/strong&gt;&amp;nbsp;driver&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;
&lt;div class="section" id="run-minikube-with-correct-vm-drivers-and-resources"&gt;
&lt;h2&gt;Run Minikube with correct &lt;span class="caps"&gt;VM&lt;/span&gt; drivers and&amp;nbsp;resources&lt;/h2&gt;
&lt;p&gt;If you had already run minikube with the default &lt;tt class="docutils literal"&gt;virtualbox&lt;/tt&gt;
drivers, remove your cluster&amp;nbsp;with:&lt;/p&gt;
&lt;pre class="literal-block"&gt;
# don't forget ``--p &amp;lt;your-profile-name&amp;gt;``
# if you had started minikube with custom profile name
minikube delete
&lt;/pre&gt;
&lt;p&gt;Make sure you start your new cluster with drivers needed and resources
allocation that meets your services requirements. In my case I used
more &lt;tt class="docutils literal"&gt;memory&lt;/tt&gt; and &lt;tt class="docutils literal"&gt;cpus&lt;/tt&gt; than it&amp;#8217;s set by&amp;nbsp;default:&lt;/p&gt;
&lt;pre class="literal-block"&gt;
minikube start --vm-driver kvm2 --memory 4096 --cpus 4
&lt;/pre&gt;
&lt;/div&gt;
&lt;div class="section" id="use-only-the-addons-you-really-need"&gt;
&lt;h2&gt;Use only the addons you really&amp;nbsp;need&lt;/h2&gt;
&lt;p&gt;Minikube&amp;#8217;s shipped with a dozen of addons like &lt;tt class="docutils literal"&gt;dashboard&lt;/tt&gt; for web
&lt;span class="caps"&gt;UI&lt;/span&gt; or &lt;tt class="docutils literal"&gt;heapster&lt;/tt&gt; for Grafana-based monitoring. They may be cool and
useful, but don&amp;#8217;t try to enable them all at&amp;nbsp;once.&lt;/p&gt;
&lt;p&gt;In my case enabling &lt;tt class="docutils literal"&gt;efk&lt;/tt&gt; addons has led to a high &lt;span class="caps"&gt;CPU&lt;/span&gt; usage, so
that I have to disable&amp;nbsp;it.&lt;/p&gt;
&lt;p&gt;Make sure you enabled only the addons you really&amp;nbsp;need:&lt;/p&gt;
&lt;pre class="literal-block"&gt;
# get list of addons
minikube addons list

# check for addons in the global config too
# for the case of recreating your cluster
minikube config view
&lt;/pre&gt;
&lt;/div&gt;
&lt;div class="section" id="conclusion"&gt;
&lt;h2&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;Minikube&amp;#8217;s idea is cool. You can really save some money by running k8s
cluster on you laptop instead of setting it up in a cloud service for
testing. But all the abstractions that Kubernetes introduces,
virtualisation with &lt;span class="caps"&gt;KVM&lt;/span&gt; and &lt;span class="caps"&gt;QEMU&lt;/span&gt; on &lt;span class="caps"&gt;GNU&lt;/span&gt;/Linux make Minikube
complex. Minikube&amp;#8217;s niche (a developer&amp;#8217;s local machine) prevents it
from being quickly fixed as, say, cloud production-ready solutions
that earn their owners piles of money. So let&amp;#8217;s hope that magic I&amp;#8217;ve
posted here will work out for you too. I also hope the issue with high
&lt;span class="caps"&gt;CPU&lt;/span&gt; usage will be fixed by someone with proper&amp;nbsp;skills.&lt;/p&gt;
&lt;/div&gt;
</content><category term="Blog"></category><category term="k8s"></category><category term="minikube"></category></entry><entry><title>Python MRO and Mixin Classes</title><link href="https://blog.pilosus.org/posts/2019/05/02/python-mixin/" rel="alternate"></link><published>2019-05-02T13:50:00+02:00</published><updated>2019-05-02T13:50:00+02:00</updated><author><name>Vitaly Samigullin</name></author><id>tag:blog.pilosus.org,2019-05-02:/posts/2019/05/02/python-mixin/</id><summary type="html">&lt;p class="first last"&gt;Understanding Python&amp;#8217;s Method Resolution Order may be
helpful when using mixin&amp;nbsp;classes&lt;/p&gt;
</summary><content type="html">&lt;p&gt;In practice we don&amp;#8217;t usually think about how Python&amp;#8217;s &lt;span class="caps"&gt;MRO&lt;/span&gt;
works. Complex multiple inheritance legitemately considered to be a
bad thing. Usually we have very straight forward hierarchies like&amp;nbsp;this:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="n"&gt;In&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;A&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
   &lt;span class="o"&gt;...&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;     &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;a&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;...&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;42&lt;/span&gt;
   &lt;span class="o"&gt;...&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
   &lt;span class="o"&gt;...&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
   &lt;span class="o"&gt;...&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;B&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;A&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
   &lt;span class="o"&gt;...&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;b&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;...&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;43&lt;/span&gt;
   &lt;span class="o"&gt;...&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;

&lt;span class="n"&gt;In&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;B&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="n"&gt;In&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="n"&gt;Out&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt; &lt;span class="mi"&gt;43&lt;/span&gt;

&lt;span class="n"&gt;In&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;b&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="n"&gt;Out&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt; &lt;span class="mi"&gt;42&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;But one application for relatively complex inheritance hierarchies is
&lt;a class="reference external" href="https://en.wikipedia.org/wiki/Mixin"&gt;mixin&lt;/a&gt; classes. We can use the
following so called &lt;em&gt;diamond inheritance&lt;/em&gt; to add some checks to the
method &lt;tt class="docutils literal"&gt;do&lt;/tt&gt; we are interested&amp;nbsp;in.&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;Top&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="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;Top class&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;do&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;value&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;Top.do&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;


&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Left&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Top&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="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;Left class&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="nb"&gt;super&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="fm"&gt;__init__&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;do&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;value&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;Left.do&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="nb"&gt;isinstance&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;Left.do expected integer values only&amp;#39;&lt;/span&gt;
        &lt;span class="c1"&gt;# We explicitly return parent&amp;#39;s do method.&lt;/span&gt;
        &lt;span class="c1"&gt;# If there was no such return, then Right.do would never be called&lt;/span&gt;
        &lt;span class="c1"&gt;# when Bottom.do() is called&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nb"&gt;super&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;do&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;


&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Right&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Top&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="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;Right class&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="nb"&gt;super&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="fm"&gt;__init__&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;do&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;value&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;Right.do&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;Right.do expected positive numbers only&amp;#39;&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nb"&gt;super&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;do&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;


&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Bottom&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Left&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Right&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="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;Bottom class&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="nb"&gt;super&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="fm"&gt;__init__&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;do&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;value&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;Bottom.do&amp;#39;&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;super&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;do&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;We can instantiate &lt;tt class="docutils literal"&gt;Top&lt;/tt&gt; class when no checks in &lt;tt class="docutils literal"&gt;do&lt;/tt&gt; method
needed, we can use class &lt;tt class="docutils literal"&gt;Left&lt;/tt&gt; when workgin with integers or we can
use &lt;tt class="docutils literal"&gt;Right&lt;/tt&gt; class when working with non-negative numbers. When both
checks are needed though, we use the &lt;tt class="docutils literal"&gt;Bottom&lt;/tt&gt; class.&lt;/p&gt;
&lt;p&gt;The initialization of the &lt;tt class="docutils literal"&gt;Bottom&lt;/tt&gt; class reveals it&amp;#8217;s &lt;span class="caps"&gt;MRO&lt;/span&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="n"&gt;In&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Bottom&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="n"&gt;Bottom&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt;
&lt;span class="nc"&gt;Left&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt;
&lt;span class="nc"&gt;Right&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt;
&lt;span class="nc"&gt;Top&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt;

&lt;span class="nc"&gt;In&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt; &lt;span class="n"&gt;Bottom&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;mro&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="n"&gt;Out&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;__main__&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Bottom&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;__main__&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Left&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;__main__&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Right&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;__main__&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Top&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;object&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Now let&amp;#8217;s see how multiple inheritance&amp;nbsp;works:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="n"&gt;In&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;b&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;do&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;Bottom&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;do&lt;/span&gt;
&lt;span class="n"&gt;Left&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;do&lt;/span&gt;
&lt;span class="n"&gt;Right&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;do&lt;/span&gt;
&lt;span class="n"&gt;Top&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;do&lt;/span&gt;

&lt;span class="n"&gt;In&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;do&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;10.5&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;Bottom&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;do&lt;/span&gt;
&lt;span class="n"&gt;Left&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;do&lt;/span&gt;
&lt;span class="o"&gt;---------------------------------------------------------------------------&lt;/span&gt;
&lt;span class="ne"&gt;AssertionError&lt;/span&gt;                            &lt;span class="n"&gt;Traceback&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;most&lt;/span&gt; &lt;span class="n"&gt;recent&lt;/span&gt; &lt;span class="n"&gt;call&lt;/span&gt; &lt;span class="n"&gt;last&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;ipython&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nb"&gt;input&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="n"&gt;eceeaaa850f&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;module&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="o"&gt;----&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;do&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;10.5&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;ipython&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nb"&gt;input&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;f358c005e4dd&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;do&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;value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
     &lt;span class="mi"&gt;39&lt;/span&gt;     &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;do&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;value&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
     &lt;span class="mi"&gt;40&lt;/span&gt;         &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;Bottom.do&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="o"&gt;---&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;41&lt;/span&gt;         &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nb"&gt;super&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;do&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
     &lt;span class="mi"&gt;42&lt;/span&gt;

&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;ipython&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nb"&gt;input&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;f358c005e4dd&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;do&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;value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
     &lt;span class="mi"&gt;14&lt;/span&gt;     &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;do&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;value&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
     &lt;span class="mi"&gt;15&lt;/span&gt;         &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;Left.do&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="o"&gt;---&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;16&lt;/span&gt;         &lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="nb"&gt;isinstance&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;Left.do expected integer values only&amp;#39;&lt;/span&gt;
     &lt;span class="mi"&gt;17&lt;/span&gt;         &lt;span class="c1"&gt;# We explicitly return parent&amp;#39;s do method.&lt;/span&gt;
     &lt;span class="mi"&gt;18&lt;/span&gt;         &lt;span class="c1"&gt;# If there was no such return, then Right.do would never be called&lt;/span&gt;

&lt;span class="ne"&gt;AssertionError&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Left&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;do&lt;/span&gt; &lt;span class="n"&gt;expected&lt;/span&gt; &lt;span class="n"&gt;integer&lt;/span&gt; &lt;span class="n"&gt;values&lt;/span&gt; &lt;span class="n"&gt;only&lt;/span&gt;

&lt;span class="n"&gt;In&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;b&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;do&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;Bottom&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;do&lt;/span&gt;
&lt;span class="n"&gt;Left&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;do&lt;/span&gt;
&lt;span class="n"&gt;Right&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;do&lt;/span&gt;
&lt;span class="o"&gt;---------------------------------------------------------------------------&lt;/span&gt;
&lt;span class="ne"&gt;AssertionError&lt;/span&gt;                            &lt;span class="n"&gt;Traceback&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;most&lt;/span&gt; &lt;span class="n"&gt;recent&lt;/span&gt; &lt;span class="n"&gt;call&lt;/span&gt; &lt;span class="n"&gt;last&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;ipython&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nb"&gt;input&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;f82b823fc4ec&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;module&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="o"&gt;----&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;do&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;ipython&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nb"&gt;input&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;f358c005e4dd&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;do&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;value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
     &lt;span class="mi"&gt;39&lt;/span&gt;     &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;do&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;value&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
     &lt;span class="mi"&gt;40&lt;/span&gt;         &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;Bottom.do&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="o"&gt;---&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;41&lt;/span&gt;         &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nb"&gt;super&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;do&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
     &lt;span class="mi"&gt;42&lt;/span&gt;

&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;ipython&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nb"&gt;input&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;f358c005e4dd&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;do&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;value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
     &lt;span class="mi"&gt;18&lt;/span&gt;         &lt;span class="c1"&gt;# If there was no such return, then Right.do would never be called&lt;/span&gt;
     &lt;span class="mi"&gt;19&lt;/span&gt;         &lt;span class="c1"&gt;# when Bottom.do() is called&lt;/span&gt;
&lt;span class="o"&gt;---&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;20&lt;/span&gt;         &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nb"&gt;super&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;do&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
     &lt;span class="mi"&gt;21&lt;/span&gt;
     &lt;span class="mi"&gt;22&lt;/span&gt;

&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;ipython&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nb"&gt;input&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;f358c005e4dd&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;do&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;value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
     &lt;span class="mi"&gt;28&lt;/span&gt;     &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;do&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;value&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
     &lt;span class="mi"&gt;29&lt;/span&gt;         &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;Right.do&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="o"&gt;---&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;30&lt;/span&gt;         &lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;Right.do expected positive numbers only&amp;#39;&lt;/span&gt;
     &lt;span class="mi"&gt;31&lt;/span&gt;         &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nb"&gt;super&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;do&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
     &lt;span class="mi"&gt;32&lt;/span&gt;

&lt;span class="ne"&gt;AssertionError&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Right&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;do&lt;/span&gt; &lt;span class="n"&gt;expected&lt;/span&gt; &lt;span class="n"&gt;positive&lt;/span&gt; &lt;span class="n"&gt;numbers&lt;/span&gt; &lt;span class="n"&gt;only&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;See more about the inner workings of the &lt;span class="caps"&gt;MRO&lt;/span&gt; in my article about &lt;a class="reference external" href="https://blog.pilosus.org/posts/2019/05/02/python-mro/"&gt;C3
linearisation algorithm&lt;/a&gt;.&lt;/p&gt;
</content><category term="Blog"></category><category term="python"></category><category term="mro"></category><category term="mixin"></category></entry><entry><title>Python Method Resolution Order and C3 linearization algorithm</title><link href="https://blog.pilosus.org/posts/2019/05/02/python-mro/" rel="alternate"></link><published>2019-05-02T12:10:00+02:00</published><updated>2019-05-02T12:10:00+02:00</updated><author><name>Vitaly Samigullin</name></author><id>tag:blog.pilosus.org,2019-05-02:/posts/2019/05/02/python-mro/</id><summary type="html">&lt;p class="first last"&gt;A naïve implementation of C3 linearization algorithm that
lies under the hood of Python&amp;#8217;s Method Resolution Order
(&lt;span class="caps"&gt;MRO&lt;/span&gt;)&lt;/p&gt;
</summary><content type="html">&lt;p&gt;Method Resolution Order (&lt;span class="caps"&gt;MRO&lt;/span&gt;) is a order in which methods should be
inherited in the case of multiple iheritance. &lt;a class="reference external" href="https://en.wikipedia.org/wiki/C3_linearization"&gt;C3 linearization
algorithm&lt;/a&gt; is how &lt;span class="caps"&gt;MRO&lt;/span&gt; works under the hood since &lt;a class="reference external" href="https://www.python.org/download/releases/2.3/mro/"&gt;version 2.3&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;a class="reference external" href="https://en.wikipedia.org/wiki/C3_linearization"&gt;Wikipedia&lt;/a&gt; does a great job explaining the algorithm. It can be
reduced to the following&amp;nbsp;steps:&lt;/p&gt;
&lt;ol class="arabic simple"&gt;
&lt;li&gt;Linearization (i.e. resolution order) is a &lt;em&gt;class itself&lt;/em&gt; and a
&lt;em&gt;merge&lt;/em&gt; of the linearizations of its parents and a list of the
parents&amp;nbsp;itself&lt;/li&gt;
&lt;li&gt;Linearization of the class with no parents equals to the class&amp;nbsp;itself.&lt;/li&gt;
&lt;li&gt;Merge process is done by selecting the first head of the lists
which does not appear in the tail of any of the lists. Where &lt;em&gt;head&lt;/em&gt;
is the first element of the list, and &lt;em&gt;tail&lt;/em&gt; is all but first
elements of the list. The heads are repeatedly selected and added
to the resulting &lt;span class="caps"&gt;MRO&lt;/span&gt; until all the lists are&amp;nbsp;exhausted.&lt;/li&gt;
&lt;li&gt;If a head cannot be selected while not all the lists are exhausted
merge is impossible to compute due to inconsistent orderings of
dependencies in the inheritance hierarchy and no linearization of
the original class&amp;nbsp;exists.&lt;/li&gt;
&lt;/ol&gt;
&lt;div class="figure"&gt;
&lt;img alt="Multiple Inheritance Example" class="responsive" src="https://blog.pilosus.org/images/c3_linearization.png" /&gt;
&lt;p class="caption"&gt;Complex multiple inheritance example, courtesy &lt;a class="reference external" href="https://commons.wikimedia.org/wiki/File:C3_linearization_example.svg"&gt;H2power&lt;/a&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;p&gt;Consider linearization process for a class &lt;tt class="docutils literal"&gt;K1&lt;/tt&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;// first, find the linearizations of K1&amp;#39;s parents, L(A), L(B), and L(C),
// and merge them with the parent list [A, B, C]
L(K1) := [K1] + merge(L(A), L(B), L(C), [A, B, C])
// class A is a good candidate for the first merge step, because it only
// appears as the head of the first and last lists
       = [K1] + merge([A, O], [B, O], [C, O], [A, B, C])
// class O is not a good candidate for the next merge step, because it also
// appears in the tails of list 2 and 3; but class B is a good candidate
       = [K1, A] + merge([O], [B, O], [C, O], [B, C])
// class C is a good candidate; class O still appears in the tail of list 3
       = [K1, A, B] + merge([O], [O], [C, O], [C])
// finally, class O is a valid candidate, which also exhausts all remaining lists
       = [K1, A, B, C] + merge([O], [O], [O])
       = [K1, A, B, C, O]
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;So at the higher level a naive implementation of the C3 linearization
algorith can be expressed as a simple&amp;nbsp;recursion:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;mro&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;cls&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;type&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;List&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="w"&gt;  &lt;/span&gt;&lt;span class="sd"&gt;&amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;
&lt;span class="sd"&gt;  Return a list of classes in order corresponding to Python&amp;#39;s MRO.&lt;/span&gt;
&lt;span class="sd"&gt;  &amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;
  &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="bp"&gt;cls&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="bp"&gt;cls&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="vm"&gt;__bases__&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;
  &lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;_merge&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;mro&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;kls&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;kls&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="bp"&gt;cls&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="vm"&gt;__bases__&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="bp"&gt;cls&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="vm"&gt;__bases__&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Then &lt;tt class="docutils literal"&gt;_merge&lt;/tt&gt; repeatedly checks if its lists are exhausted and
append appropriate heads to the resulting &lt;span class="caps"&gt;MRO&lt;/span&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;_merge&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;lists&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;list&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
  &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;List&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;Optional&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="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
  &lt;span class="n"&gt;linearizations&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;DependencyList&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;lists&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

  &lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
      &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;linearizations&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;exhausted&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
          &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;

      &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;head&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;linearizations&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;heads&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;head&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;head&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;linearizations&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;tails&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;  &lt;span class="c1"&gt;# type: ignore&lt;/span&gt;
              &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;head&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
              &lt;span class="n"&gt;linearizations&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;remove&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;head&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

              &lt;span class="c1"&gt;# Once candidate is found, continue iteration&lt;/span&gt;
              &lt;span class="c1"&gt;# from the first element of the list&lt;/span&gt;
              &lt;span class="k"&gt;break&lt;/span&gt;
      &lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
          &lt;span class="c1"&gt;# Loop never broke, no linearization could possibly be found&lt;/span&gt;
          &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="ne"&gt;ValueError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;Cannot compute linearization, a cycle found&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;In order to hide some lists internals &lt;tt class="docutils literal"&gt;Dependency&lt;/tt&gt; and
&lt;tt class="docutils literal"&gt;DependencyList&lt;/tt&gt; abstractions are&amp;nbsp;used:&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;itertools&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;islice&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;List&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Tuple&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Optional&lt;/span&gt;


&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Dependency&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;deque&lt;/span&gt;&lt;span class="p"&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;head&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="n"&gt;Optional&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="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
        &lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="ne"&gt;IndexError&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;None&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;tail&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="n"&gt;islice&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;  &lt;span class="c1"&gt;# type: ignore&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;        Return islice object, which is suffice for iteration or calling `in`&lt;/span&gt;
&lt;span class="sd"&gt;        &amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;
        &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;islice&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="mi"&gt;1&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="fm"&gt;__len__&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
        &lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ne"&gt;ValueError&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ne"&gt;IndexError&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;islice&lt;/span&gt;&lt;span class="p"&gt;([],&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;


&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;DependencyList&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;    A class represents list of linearizations (dependencies)&lt;/span&gt;

&lt;span class="sd"&gt;    The last element of DependencyList is a list of parents.&lt;/span&gt;
&lt;span class="sd"&gt;    It&amp;#39;s needed  to the merge process preserves the local&lt;/span&gt;
&lt;span class="sd"&gt;    precedence order of direct parent classes.&lt;/span&gt;
&lt;span class="sd"&gt;    &amp;quot;&amp;quot;&amp;quot;&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="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;lists&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Tuple&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;List&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="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;_lists&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;Dependency&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;lists&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;__contains__&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;item&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="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;bool&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;        Return True if any linearization&amp;#39;s tail contains an item&lt;/span&gt;
&lt;span class="sd"&gt;        &amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nb"&gt;any&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="n"&gt;item&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;l&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;tail&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;l&lt;/span&gt; &lt;span class="ow"&gt;in&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;_lists&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;  &lt;span class="c1"&gt;# type: ignore&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="fm"&gt;__len__&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;size&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_lists&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;size&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;size&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="fm"&gt;__repr__&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="k"&gt;return&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;_lists&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="fm"&gt;__repr__&lt;/span&gt;&lt;span class="p"&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;heads&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="n"&gt;List&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;Optional&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="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;h&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;head&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;h&lt;/span&gt; &lt;span class="ow"&gt;in&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;_lists&lt;/span&gt;&lt;span class="p"&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;tails&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="s1"&gt;&amp;#39;DependencyList&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;  &lt;span class="c1"&gt;# type: ignore&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;        Return self so that __contains__ could be called&lt;/span&gt;

&lt;span class="sd"&gt;        Used for readability reasons only&lt;/span&gt;
&lt;span class="sd"&gt;        &amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="bp"&gt;self&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;exhausted&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;bool&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;        Return True if all elements of the lists are exhausted&lt;/span&gt;
&lt;span class="sd"&gt;        &amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nb"&gt;all&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="k"&gt;lambda&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="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;_lists&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;remove&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;item&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Optional&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="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="w"&gt;        &lt;/span&gt;&lt;span class="sd"&gt;&amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;
&lt;span class="sd"&gt;        Remove an item from the lists&lt;/span&gt;

&lt;span class="sd"&gt;        Once an item removed from heads, the leftmost elements of the tails&lt;/span&gt;
&lt;span class="sd"&gt;        get promoted to become the new heads.&lt;/span&gt;
&lt;span class="sd"&gt;        &amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;
        &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_lists&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;head&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;popleft&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;The whole codebase can be found in my &lt;a class="reference external" href="https://github.com/pilosus/c3linear"&gt;c3linear repository&lt;/a&gt;. You can
install it from the source code or via&amp;nbsp;PyPI:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;python&lt;span class="w"&gt; &lt;/span&gt;setup.py&lt;span class="w"&gt; &lt;/span&gt;install&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="c1"&gt;# from the source code&lt;/span&gt;
pip&lt;span class="w"&gt; &lt;/span&gt;install&lt;span class="w"&gt; &lt;/span&gt;c3linear&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="c1"&gt;# from the Cheese Shop&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Then just import it and check against Python&amp;#8217;s object&amp;#8217;s &lt;tt class="docutils literal"&gt;mro&lt;/tt&gt; method:&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;c3linear.mro&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;mro&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;A&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;class&lt;/span&gt; &lt;span class="nc"&gt;B&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;A&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="k"&gt;pass&lt;/span&gt;
&lt;span class="n"&gt;mro&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;B&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;B&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;mro&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Take a look at the &lt;a class="reference external" href="https://github.com/pilosus/c3linear/blob/master/tests/test_mro.py"&gt;tests&lt;/a&gt; to dive into more complex multiple inheritance&amp;nbsp;examples.&lt;/p&gt;
</content><category term="Blog"></category><category term="python"></category><category term="cpython"></category><category term="mro"></category><category term="algorithms"></category></entry><entry><title>Pelican blog: up and running</title><link href="https://blog.pilosus.org/posts/2019/05/01/pelican-blog-up-and-running/" rel="alternate"></link><published>2019-05-01T11:30:00+02:00</published><updated>2019-05-01T11:30:00+02:00</updated><author><name>Vitaly Samigullin</name></author><id>tag:blog.pilosus.org,2019-05-01:/posts/2019/05/01/pelican-blog-up-and-running/</id><summary type="html">&lt;p&gt;&lt;a class="reference external" href="https://docs.getpelican.com/en/stable/"&gt;Pelican&lt;/a&gt; is a static site generator written in Python. I use it for
&lt;a class="reference external" href="https://blog.pilosus.org"&gt;my personal blog&lt;/a&gt;. Pelican is easy to install, configure and
customize with themes and plugins. I was able to set up the blog with
my own &lt;a class="reference external" href="https://github.com/pilosus/pilosus-pelican-theme"&gt;custom theme&lt;/a&gt; and an &lt;a class="reference external" href="https://github.com/pilosus/pilosus_pelican_og"&gt;Open Graph plugin&lt;/a&gt; in a few&amp;nbsp;hours …&lt;/p&gt;</summary><content type="html">&lt;p&gt;&lt;a class="reference external" href="https://docs.getpelican.com/en/stable/"&gt;Pelican&lt;/a&gt; is a static site generator written in Python. I use it for
&lt;a class="reference external" href="https://blog.pilosus.org"&gt;my personal blog&lt;/a&gt;. Pelican is easy to install, configure and
customize with themes and plugins. I was able to set up the blog with
my own &lt;a class="reference external" href="https://github.com/pilosus/pilosus-pelican-theme"&gt;custom theme&lt;/a&gt; and an &lt;a class="reference external" href="https://github.com/pilosus/pilosus_pelican_og"&gt;Open Graph plugin&lt;/a&gt; in a few&amp;nbsp;hours.&lt;/p&gt;
&lt;p&gt;Before Pelican I also used &lt;a class="reference external" href="https://jekyllrb.com/"&gt;Jekyll&lt;/a&gt; and &lt;a class="reference external" href="http://octopress.org/"&gt;Octopress&lt;/a&gt;. But since my
first language is Python and Ruby ecosystem seems to be in a decline,
I deciced to stick to a framework where I can express myself as
idiomatic as I can. Last but not least, having to write documentation
for Python code with &lt;a class="reference external" href="https://www.sphinx-doc.org/en/master/"&gt;Sphinx&lt;/a&gt; I also got used to &lt;a class="reference external" href="https://docutils.readthedocs.io/en/sphinx-docs/user/rst/quickstart.html"&gt;ReStructuredText&lt;/a&gt;
format, which is supported by Pelican out of the&amp;nbsp;box.&lt;/p&gt;
&lt;p&gt;In this post I&amp;#8217;m not going repeat the Pelican&amp;#8217;s great
documentation. Instead I will focus on the step that allows you&amp;nbsp;to:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;Set up your blog&amp;nbsp;quickly&lt;/li&gt;
&lt;li&gt;Add your own theme to&amp;nbsp;it&lt;/li&gt;
&lt;li&gt;Extend Pelican&amp;#8217;s basic functionality with a&amp;nbsp;plugin&lt;/li&gt;
&lt;li&gt;Automate build and deploy process,&amp;nbsp;and&lt;/li&gt;
&lt;li&gt;Host your static site with &lt;a class="reference external" href="https://pages.github.com/"&gt;GitHub Pages&lt;/a&gt; under your custom&amp;nbsp;domain&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I use Pelican verion 4.0.1, invoke 1.2.0 and ghp-import 0.5.5 in this&amp;nbsp;post.&lt;/p&gt;
&lt;div class="section" id="install-pelican"&gt;
&lt;h2&gt;Install&amp;nbsp;Pelican&lt;/h2&gt;
&lt;p&gt;Personally I use &lt;a class="reference external" href="https://github.com/pyenv/pyenv"&gt;pyenv&lt;/a&gt; for Python version management. I also use
&lt;a class="reference external" href="https://github.com/pyenv/pyenv-virtualenv"&gt;pyenv-virtualenv&lt;/a&gt; plugin to incorporate &lt;tt class="docutils literal"&gt;virtualenv&lt;/tt&gt; into it. So
my workflow for the Python projects that do not require
containerization is the&amp;nbsp;following:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="c1"&gt;# Create directory for the project, cd to it&lt;/span&gt;
$&lt;span class="w"&gt; &lt;/span&gt;mkdir&lt;span class="w"&gt; &lt;/span&gt;my-project&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;cd&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;my-project

&lt;span class="c1"&gt;# Set up virtual env with Python version needed&lt;/span&gt;
$&lt;span class="w"&gt; &lt;/span&gt;pyenv&lt;span class="w"&gt; &lt;/span&gt;virtualenv&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;3&lt;/span&gt;.7.2&lt;span class="w"&gt; &lt;/span&gt;venv

&lt;span class="c1"&gt;# Activate virtualenv&lt;/span&gt;
$&lt;span class="w"&gt; &lt;/span&gt;pyenv&lt;span class="w"&gt; &lt;/span&gt;activate&lt;span class="w"&gt; &lt;/span&gt;venv

&lt;span class="c1"&gt;# Initialize git reposotory, add README and basic .gitignore&lt;/span&gt;
$&lt;span class="w"&gt; &lt;/span&gt;git&lt;span class="w"&gt; &lt;/span&gt;init
$&lt;span class="w"&gt; &lt;/span&gt;touch&lt;span class="w"&gt; &lt;/span&gt;README.md
$&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;*~&amp;#39;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&amp;gt;&lt;span class="w"&gt; &lt;/span&gt;.gitignore
$&lt;span class="w"&gt; &lt;/span&gt;git&lt;span class="w"&gt; &lt;/span&gt;commit&lt;span class="w"&gt; &lt;/span&gt;-m&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;Initial commit&amp;#39;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Now that a repository for your project is ready, install &lt;tt class="docutils literal"&gt;pelican&lt;/tt&gt;
package, fix its version and initialize the&amp;nbsp;app:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;$&lt;span class="w"&gt; &lt;/span&gt;pip&lt;span class="w"&gt; &lt;/span&gt;install&lt;span class="w"&gt; &lt;/span&gt;pelican
$&lt;span class="w"&gt; &lt;/span&gt;pip&lt;span class="w"&gt; &lt;/span&gt;freeze&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;grep&lt;span class="w"&gt; &lt;/span&gt;pelican&lt;span class="w"&gt; &lt;/span&gt;&amp;gt;&lt;span class="w"&gt; &lt;/span&gt;requirements.txt
$&lt;span class="w"&gt; &lt;/span&gt;git&lt;span class="w"&gt; &lt;/span&gt;commit&lt;span class="w"&gt; &lt;/span&gt;-a&lt;span class="w"&gt; &lt;/span&gt;-m&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;Fix pelican version&amp;#39;&lt;/span&gt;
$&lt;span class="w"&gt; &lt;/span&gt;pelican-quickstart
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;All the questions are pretty straightforward. One thing I would advise
though is to confirm that you do want to &lt;strong&gt;automate site generation and
publication&lt;/strong&gt;. This step will create a &lt;tt class="docutils literal"&gt;Makefile&lt;/tt&gt; and invoke&amp;#8217;s
&lt;tt class="docutils literal"&gt;tasks.py&lt;/tt&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="custom-theme"&gt;
&lt;h2&gt;Custom&amp;nbsp;Theme&lt;/h2&gt;
&lt;p&gt;You can find a ton of Pelican themes in the wild. It&amp;#8217;s so
overwhelmingly huge amount of custom themes that I had just given up
on finding a theme that&amp;#8217;s just right for me. Instead I&amp;#8217;ve build my own
&lt;a class="reference external" href="https://github.com/pilosus/pilosus-pelican-theme"&gt;custom theme&lt;/a&gt;. It&amp;#8217;s responsive, lightweight and clean. For rapid
development I used &lt;a class="reference external" href="http://getskeleton.com/"&gt;Skeleton&lt;/a&gt;, which is a modern &lt;span class="caps"&gt;CSS&lt;/span&gt;&amp;nbsp;boilerplate.&lt;/p&gt;
&lt;p&gt;Although it&amp;#8217;s recommended to install your theme either by copying it
to the Pelican&amp;#8217;s theme path or by creating a symlink, I used &lt;tt class="docutils literal"&gt;git
submodule&lt;/tt&gt; add theme in my blog repo directory. Then I added a path
to the theme under &lt;tt class="docutils literal"&gt;&lt;span class="caps"&gt;THEME&lt;/span&gt;&lt;/tt&gt; variable in &lt;tt class="docutils literal"&gt;pelicanconf.py&lt;/tt&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;$&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;cd&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;my-project
$&lt;span class="w"&gt; &lt;/span&gt;git&lt;span class="w"&gt; &lt;/span&gt;submodule&lt;span class="w"&gt; &lt;/span&gt;add&lt;span class="w"&gt; &lt;/span&gt;https://github.com/pilosus/pilosus-pelican-theme.git&lt;span class="w"&gt; &lt;/span&gt;themes/pilosus-pelican-theme
$&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;THEME = &amp;#39;themes/pilosus-pelican-theme&amp;#39;&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&amp;gt;&amp;gt;&lt;span class="w"&gt; &lt;/span&gt;pelicanconf.py
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Now my custom theme is versioned and can be easily developed
separately from the blog. The blog may be generated with whatever
theme version I like. I will only need to checkout my submodule to the
version I&amp;nbsp;need.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="plugins"&gt;
&lt;h2&gt;Plugins&lt;/h2&gt;
&lt;p&gt;The first thing I was disappointed in Pelican&amp;#8217;s default theme is a
lack of &lt;a class="reference external" href="http://ogp.me/"&gt;Open Graph&lt;/a&gt; tags. You cannot just add custom metadata to
your content files and use it in the theme. The easiest way to tackle
this problem is &lt;a class="reference external" href="https://docs.getpelican.com/en/stable/plugins.html"&gt;plugins&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;I derived my &lt;a class="reference external" href="https://github.com/pilosus/pilosus_pelican_og"&gt;Open Graph plugin&lt;/a&gt; from &lt;a class="reference external" href="https://github.com/whiskyechobravo/pelican-open_graph"&gt;this one&lt;/a&gt; by tweaking the
things I didn&amp;#8217;t like. Say, the original plugin gets &lt;tt class="docutils literal"&gt;og:image&lt;/tt&gt; by
parsing images from the rendered &lt;tt class="docutils literal"&gt;&lt;span class="caps"&gt;HTML&lt;/span&gt;&lt;/tt&gt; content, which is strange,
ineffective and will certainly produce low-quality results in many
cases. That&amp;#8217;s why I decided to develop my own&amp;nbsp;plugin.&lt;/p&gt;
&lt;p&gt;Again, I find installing plugin as a &lt;tt class="docutils literal"&gt;git submodule&lt;/tt&gt; a great way to
keep the code versioned, site deployments deterministic and
maintanence predictable and&amp;nbsp;easy.&lt;/p&gt;
&lt;p&gt;I place plugins under &lt;tt class="docutils literal"&gt;plugins&lt;/tt&gt; directory in my blog repo, then
define &lt;tt class="docutils literal"&gt;PLUGIN_PATHS&lt;/tt&gt; and &lt;tt class="docutils literal"&gt;&lt;span class="caps"&gt;PLUGINS&lt;/span&gt;&lt;/tt&gt; variables in the&amp;nbsp;settings:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;$&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;cd&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;my-project
$&lt;span class="w"&gt; &lt;/span&gt;git&lt;span class="w"&gt; &lt;/span&gt;submodule&lt;span class="w"&gt; &lt;/span&gt;add&lt;span class="w"&gt; &lt;/span&gt;https://github.com/pilosus/pilosus_pelican_og&lt;span class="w"&gt; &lt;/span&gt;plugins/pilosus_pelican_og
$&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;PLUGIN_PATHS = [&amp;#39;plugins&amp;#39;]&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&amp;gt;&amp;gt;&lt;span class="w"&gt; &lt;/span&gt;pelicanconf.py
$&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;PLUGINS = [&amp;#39;pilosus_pelican_og&amp;#39;,]&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&amp;gt;&amp;gt;&lt;span class="w"&gt; &lt;/span&gt;pelicanconf.py
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;div class="section" id="build-and-deploy-automation"&gt;
&lt;h2&gt;Build and Deploy&amp;nbsp;Automation&lt;/h2&gt;
&lt;p&gt;Our goal is to generate a static site, that can be pushed and served
by the GitHub Pages with custom domain support. To make this process
less tedious some automation is&amp;nbsp;essential.&lt;/p&gt;
&lt;p&gt;If you have followed an advice in the &lt;a class="reference internal" href="#install-pelican"&gt;Install Pelican&lt;/a&gt; section to
opt for automation, then you&amp;#8217;ve got &lt;tt class="docutils literal"&gt;tasks.py&lt;/tt&gt; and &lt;tt class="docutils literal"&gt;Makefile&lt;/tt&gt;
installed. Although I do use &lt;tt class="docutils literal"&gt;Makefile&lt;/tt&gt; in some of my projects I
decided to give &lt;tt class="docutils literal"&gt;invoke&lt;/tt&gt; a try. So the following recipe is all about
&lt;tt class="docutils literal"&gt;tasks.py&lt;/tt&gt; that &lt;tt class="docutils literal"&gt;invoke&lt;/tt&gt; uses.&lt;/p&gt;
&lt;p&gt;We need to install &lt;tt class="docutils literal"&gt;invoke&lt;/tt&gt; package in your virtualenv, as well as
&lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;ghp-import&lt;/span&gt;&lt;/tt&gt; for GitHub Page push. Don&amp;#8217;t forget to fix all
dependencies in the &lt;tt class="docutils literal"&gt;requirements.txt&lt;/tt&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;$&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;cd&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;my-project
$&lt;span class="w"&gt; &lt;/span&gt;pyenv&lt;span class="w"&gt; &lt;/span&gt;activate&lt;span class="w"&gt; &lt;/span&gt;venv
$&lt;span class="w"&gt; &lt;/span&gt;pip&lt;span class="w"&gt; &lt;/span&gt;install&lt;span class="w"&gt; &lt;/span&gt;invoke
$&lt;span class="w"&gt; &lt;/span&gt;pip&lt;span class="w"&gt; &lt;/span&gt;freeze&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;grep&lt;span class="w"&gt; &lt;/span&gt;invoke&lt;span class="w"&gt; &lt;/span&gt;&amp;gt;&amp;gt;&lt;span class="w"&gt; &lt;/span&gt;requirements.txt
$&lt;span class="w"&gt; &lt;/span&gt;pip&lt;span class="w"&gt; &lt;/span&gt;install&lt;span class="w"&gt; &lt;/span&gt;ghp-import
$&lt;span class="w"&gt; &lt;/span&gt;pip&lt;span class="w"&gt; &lt;/span&gt;freeze&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;grep&lt;span class="w"&gt; &lt;/span&gt;invoke&lt;span class="w"&gt; &lt;/span&gt;&amp;gt;&amp;gt;&lt;span class="w"&gt; &lt;/span&gt;requirements.txt
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Now that we have all dependencies installed let&amp;#8217;s add a new task for
GitHub Pages&amp;nbsp;deployment:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="nd"&gt;@task&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;github&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;c&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;Publish to GitHub Pages&amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;
  &lt;span class="n"&gt;preview&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="n"&gt;cname&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;ghp-import -b &lt;/span&gt;&lt;span class="si"&gt;{github_pages_branch}&lt;/span&gt;&lt;span class="s1"&gt; &amp;#39;&lt;/span&gt;
        &lt;span class="s1"&gt;&amp;#39;-m &lt;/span&gt;&lt;span class="si"&gt;{commit_message}&lt;/span&gt;&lt;span class="s1"&gt; &amp;#39;&lt;/span&gt;
        &lt;span class="s1"&gt;&amp;#39;&lt;/span&gt;&lt;span class="si"&gt;{deploy_path}&lt;/span&gt;&lt;span class="s1"&gt; -p&amp;#39;&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;format&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;CONFIG&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
  &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;git push --force &lt;/span&gt;&lt;span class="si"&gt;{github_repo}&lt;/span&gt;&lt;span class="s1"&gt; &amp;#39;&lt;/span&gt;
        &lt;span class="s1"&gt;&amp;#39;&lt;/span&gt;&lt;span class="si"&gt;{github_pages_branch}&lt;/span&gt;&lt;span class="s1"&gt;:&lt;/span&gt;&lt;span class="si"&gt;{github_repo_target_branch}&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;format&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;CONFIG&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;&lt;tt class="docutils literal"&gt;preview(c)&lt;/tt&gt; is a predefined &lt;tt class="docutils literal"&gt;invoke&lt;/tt&gt; task that generates static
files for production environment (i.e. with &lt;tt class="docutils literal"&gt;publishconf.py&lt;/tt&gt;
settings&amp;nbsp;file).&lt;/p&gt;
&lt;p&gt;&lt;tt class="docutils literal"&gt;cname(c)&lt;/tt&gt; is a task that generates a file called &lt;tt class="docutils literal"&gt;&lt;span class="caps"&gt;CNAME&lt;/span&gt;&lt;/tt&gt; with a
hostname of your custom&amp;nbsp;domain:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="nd"&gt;@task&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;cname&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;c&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;Generate CNAME file with you custom domain name&lt;/span&gt;

&lt;span class="sd"&gt;  Its used in GitHub Pages. Otherwise custom domain name setting&lt;/span&gt;
&lt;span class="sd"&gt;  gets reset on each git push to GH Page repo.&lt;/span&gt;
&lt;span class="sd"&gt;  &amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;
  &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;echo &lt;/span&gt;&lt;span class="si"&gt;{custom_domain_name}&lt;/span&gt;&lt;span class="s1"&gt; &amp;gt; &lt;/span&gt;&lt;span class="si"&gt;{deploy_path}&lt;/span&gt;&lt;span class="s1"&gt;/CNAME&amp;#39;&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;format&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;CONFIG&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;&lt;tt class="docutils literal"&gt;cname&lt;/tt&gt; docstring says it all. You really need this&amp;nbsp;file!&lt;/p&gt;
&lt;p&gt;&lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;ghp-import&lt;/span&gt;&lt;/tt&gt; command checks out you output directory (i.e. the one
used for generated static files) to the GitHub Pages branch of your
project. Then &lt;tt class="docutils literal"&gt;git push &lt;span class="pre"&gt;--force&lt;/span&gt;&lt;/tt&gt; pushes this branch to the
repository for static GitHub Pages (it should be named as
&lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;your-github-login.github.io&lt;/span&gt;&lt;/tt&gt;).&lt;/p&gt;
&lt;p&gt;Configuration you use throughout the invoke&amp;#8217;s &lt;tt class="docutils literal"&gt;tasks.py&lt;/tt&gt; may look like&amp;nbsp;this:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="n"&gt;CONFIG&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;# Local path to content directory&lt;/span&gt;
    &lt;span class="s1"&gt;&amp;#39;content_path&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;content&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="c1"&gt;# Local path configuration (can be absolute or relative to tasks.py)&lt;/span&gt;
    &lt;span class="s1"&gt;&amp;#39;deploy_path&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;output&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="c1"&gt;# Github Pages configuration&lt;/span&gt;
    &lt;span class="s1"&gt;&amp;#39;github_pages_branch&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;gh-pages&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s1"&gt;&amp;#39;github_repo_target_branch&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;master&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s1"&gt;&amp;#39;github_repo&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;git@github.com:pilosus/pilosus.github.io.git&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s1"&gt;&amp;#39;custom_domain_name&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;blog.pilosus.org&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s1"&gt;&amp;#39;commit_message&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;&amp;#39;Publish site on &lt;/span&gt;&lt;span class="si"&gt;{}&lt;/span&gt;&lt;span class="s2"&gt;&amp;#39;&amp;quot;&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;format&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;datetime&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;date&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;today&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;isoformat&lt;/span&gt;&lt;span class="p"&gt;()),&lt;/span&gt;
    &lt;span class="c1"&gt;# Port for `serve`&lt;/span&gt;
    &lt;span class="s1"&gt;&amp;#39;port&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;8080&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Keeping two separate repositories (one for the blog and another one
for the static files it generates) allows you to make your blog
repository private. You can keep some secrets or some code you don&amp;#8217;t
want to share. But still your generated files will be accessible to
others in the second&amp;nbsp;repository.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="pelican-workflow"&gt;
&lt;h2&gt;Pelican&amp;nbsp;Workflow&lt;/h2&gt;
&lt;p&gt;Now that we have defined all the &lt;tt class="docutils literal"&gt;invoke&lt;/tt&gt; tasks, we can discuss
Pelican&amp;#8217;s workflow. It&amp;#8217;s very straight&amp;nbsp;forward:&lt;/p&gt;
&lt;ol class="arabic simple"&gt;
&lt;li&gt;Write your &lt;a class="reference external" href="https://docs.getpelican.com/en/stable/content.html"&gt;content&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Rebuild the site with development settings: &lt;tt class="docutils literal"&gt;invoke rebuild&lt;/tt&gt;&lt;/li&gt;
&lt;li&gt;Serve website at localhost: &lt;tt class="docutils literal"&gt;invoke serve&lt;/tt&gt;&lt;/li&gt;
&lt;li&gt;Go to &lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;http://localhost:8000&lt;/span&gt;&lt;/tt&gt; check if everything is&amp;nbsp;okay&lt;/li&gt;
&lt;li&gt;Clean output directory: &lt;tt class="docutils literal"&gt;invoke clean&lt;/tt&gt;&lt;/li&gt;
&lt;li&gt;Build website with production settings and upload to the GitHub Pages: &lt;tt class="docutils literal"&gt;invoke github&lt;/tt&gt;&lt;/li&gt;
&lt;li&gt;Enjoy your&amp;nbsp;blog!&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;
&lt;div class="section" id="custom-domain"&gt;
&lt;h2&gt;Custom&amp;nbsp;Domain&lt;/h2&gt;
&lt;p&gt;By now you already have your static site live on
&lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;your-github-login.github.io&lt;/span&gt;&lt;/tt&gt;. In order to serve it under custom
domain you need to add a &lt;tt class="docutils literal"&gt;&lt;span class="caps"&gt;CNAME&lt;/span&gt;&lt;/tt&gt; record in your &lt;tt class="docutils literal"&gt;&lt;span class="caps"&gt;DNS&lt;/span&gt;&lt;/tt&gt; and set
custom domain in the settings of &lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;your-github-login.github.io&lt;/span&gt;&lt;/tt&gt;
repository on GitHub. &lt;tt class="docutils literal"&gt;Enforce &lt;span class="caps"&gt;HTTPS&lt;/span&gt;&lt;/tt&gt; is also a great option to turn&amp;nbsp;on.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="what-s-next"&gt;
&lt;h2&gt;What&amp;#8217;s&amp;nbsp;next?&lt;/h2&gt;
&lt;p&gt;Althouh the set up and automation I described here are pretty
convenient, one thing could still be improved. We could use &lt;a class="reference external" href="https://travis-ci.org/"&gt;Travis &lt;span class="caps"&gt;CI&lt;/span&gt;&lt;/a&gt;
or &lt;a class="reference external" href="https://circleci.com/"&gt;Circle &lt;span class="caps"&gt;CI&lt;/span&gt;&lt;/a&gt; to generate static files and push to the proper
repository. &lt;span class="caps"&gt;CI&lt;/span&gt; pipeline should be triggered on each push to the remote
blog&amp;nbsp;repository.&lt;/p&gt;
&lt;/div&gt;
</content><category term="Blog"></category><category term="pelican"></category><category term="blog"></category><category term="python"></category></entry><entry><title>Method vs. Class Method Quirks in Python 3</title><link href="https://blog.pilosus.org/posts/2019/04/29/bound-class-method/" rel="alternate"></link><published>2019-04-29T17:30:00+02:00</published><updated>2019-04-29T17:30:00+02:00</updated><author><name>Vitaly Samigullin</name></author><id>tag:blog.pilosus.org,2019-04-29:/posts/2019/04/29/bound-class-method/</id><summary type="html">&lt;p class="first last"&gt;A little example of how bound/unbound methods behave in Python&amp;nbsp;3&lt;/p&gt;
</summary><content type="html">&lt;p&gt;A code snippet in Python 3 below has been discussed recently on the&amp;nbsp;internet:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="n"&gt;In&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;A&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
   &lt;span class="o"&gt;...&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;     &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;method&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;...&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;         &lt;span class="k"&gt;pass&lt;/span&gt;
   &lt;span class="o"&gt;...&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
   &lt;span class="o"&gt;...&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;B&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
   &lt;span class="o"&gt;...&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;     &lt;span class="nd"&gt;@classmethod&lt;/span&gt;
   &lt;span class="o"&gt;...&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;     &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;method&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;cls&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
   &lt;span class="o"&gt;...&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;         &lt;span class="k"&gt;pass&lt;/span&gt;


&lt;span class="n"&gt;In&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt; &lt;span class="n"&gt;A&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;method&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="n"&gt;A&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;method&lt;/span&gt;
&lt;span class="n"&gt;Out&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt; &lt;span class="kc"&gt;True&lt;/span&gt;

&lt;span class="n"&gt;In&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt; &lt;span class="n"&gt;B&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;method&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="n"&gt;B&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;method&lt;/span&gt;
&lt;span class="n"&gt;Out&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt; &lt;span class="kc"&gt;False&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;The question is why &lt;tt class="docutils literal"&gt;A.method&lt;/tt&gt; returns the same object on each call
while &lt;tt class="docutils literal"&gt;B.method&lt;/tt&gt; returns the new&amp;nbsp;one?&lt;/p&gt;
&lt;p&gt;Let&amp;#8217;s explore first what &lt;tt class="docutils literal"&gt;A.method&lt;/tt&gt; really&amp;nbsp;is:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="n"&gt;In&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;A&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;method&lt;/span&gt;
&lt;span class="n"&gt;Out&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;function&lt;/span&gt; &lt;span class="n"&gt;__main__&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;A&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;method&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="n"&gt;In&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt; &lt;span class="nb"&gt;type&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;A&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;method&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;Out&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt; &lt;span class="n"&gt;function&lt;/span&gt;

&lt;span class="n"&gt;In&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="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;inspect&lt;/span&gt;

&lt;span class="n"&gt;In&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;7&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt; &lt;span class="n"&gt;inspect&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ismethod&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;A&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;method&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;Out&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;7&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt; &lt;span class="kc"&gt;False&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Essentially, what that means is that &lt;tt class="docutils literal"&gt;A.method&lt;/tt&gt; is a &lt;em&gt;function&lt;/em&gt;,
i.e. &lt;tt class="docutils literal"&gt;unbound method&lt;/tt&gt; of the &lt;tt class="docutils literal"&gt;class A&lt;/tt&gt;. A great way to check if a
method is indeed unbound is to check if its function object and a
class instance&amp;nbsp;exist:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="n"&gt;In&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;13&lt;/span&gt;&lt;span class="p"&gt;]:&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;A&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;method&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;__func__&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;Out&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;13&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt; &lt;span class="kc"&gt;False&lt;/span&gt;

&lt;span class="n"&gt;In&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;14&lt;/span&gt;&lt;span class="p"&gt;]:&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;A&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;method&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;__self__&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;Out&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;14&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt; &lt;span class="kc"&gt;False&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;See Python documentation for more details, specifically an &lt;a class="reference external" href="https://docs.python.org/3/reference/datamodel.html#index-32"&gt;instance
method&lt;/a&gt; subsection of the &lt;tt class="docutils literal"&gt;Data model&lt;/tt&gt; section.&lt;/p&gt;
&lt;p&gt;On the other hand, &lt;tt class="docutils literal"&gt;B.method&lt;/tt&gt; is a &lt;tt class="docutils literal"&gt;class method&lt;/tt&gt;. It returns an
&lt;em&gt;instance of the bound method object&lt;/em&gt;. Let&amp;#8217;s check&amp;nbsp;it:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="n"&gt;In&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;15&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt; &lt;span class="n"&gt;B&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;method&lt;/span&gt;
&lt;span class="n"&gt;Out&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;15&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;bound&lt;/span&gt; &lt;span class="n"&gt;method&lt;/span&gt; &lt;span class="n"&gt;B&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;method&lt;/span&gt; &lt;span class="n"&gt;of&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="err"&gt;&amp;#39;&lt;/span&gt;&lt;span class="nc"&gt;__main__&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="sa"&gt;B&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;&amp;gt;&amp;gt;&lt;/span&gt;

&lt;span class="n"&gt;In&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;16&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt; &lt;span class="nb"&gt;type&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;B&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;method&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;Out&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;16&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt; &lt;span class="n"&gt;method&lt;/span&gt;

&lt;span class="n"&gt;In&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;17&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt; &lt;span class="n"&gt;inspect&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ismethod&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;B&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;method&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;Out&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;17&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt; &lt;span class="kc"&gt;True&lt;/span&gt;

&lt;span class="n"&gt;In&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;18&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt; &lt;span class="nb"&gt;hasattr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;B&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;method&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;__func__&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;Out&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;18&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt; &lt;span class="kc"&gt;True&lt;/span&gt;

&lt;span class="n"&gt;In&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;19&lt;/span&gt;&lt;span class="p"&gt;]:&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;B&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;method&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;__self__&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;Out&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;19&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt; &lt;span class="kc"&gt;True&lt;/span&gt;

&lt;span class="n"&gt;In&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt; &lt;span class="n"&gt;B&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;method&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="vm"&gt;__self__&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="n"&gt;B&lt;/span&gt;
&lt;span class="n"&gt;Out&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt; &lt;span class="kc"&gt;True&lt;/span&gt;

&lt;span class="n"&gt;In&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;21&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt; &lt;span class="n"&gt;B&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;method&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="vm"&gt;__func__&lt;/span&gt;
&lt;span class="n"&gt;Out&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;21&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;function&lt;/span&gt; &lt;span class="n"&gt;__main__&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;B&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;method&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;cls&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;That means on each &lt;tt class="docutils literal"&gt;B.method&lt;/tt&gt; invocation a new object is
created. You can achieve the same behaviour with &lt;tt class="docutils literal"&gt;A.method&lt;/tt&gt; once the
method gets&amp;nbsp;bound:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="n"&gt;In&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;22&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;A&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="n"&gt;In&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;23&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;a&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;method&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;Out&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;23&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt; &lt;span class="mi"&gt;140613204502216&lt;/span&gt;

&lt;span class="n"&gt;In&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;24&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;a&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;method&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;Out&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;24&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt; &lt;span class="mi"&gt;140613210350280&lt;/span&gt;

&lt;span class="n"&gt;In&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;25&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;a&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;method&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;Out&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;25&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt; &lt;span class="mi"&gt;140613209057096&lt;/span&gt;

&lt;span class="n"&gt;In&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;26&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;method&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;method&lt;/span&gt;
&lt;span class="n"&gt;Out&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;26&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt; &lt;span class="kc"&gt;False&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Again, every time you call &lt;tt class="docutils literal"&gt;a.method&lt;/tt&gt; a new bound method object is&amp;nbsp;created.&lt;/p&gt;
&lt;p&gt;The example above doesn&amp;#8217;t apply to Python 2.7 though. It seems that
Python 2.7 had different implementation, so that &lt;tt class="docutils literal"&gt;A.method&lt;/tt&gt; would
return different objects every time it&amp;#8217;s&amp;nbsp;called.&lt;/p&gt;
</content><category term="Blog"></category><category term="python"></category><category term="cpython"></category></entry><entry><title>Let’s get started!</title><link href="https://blog.pilosus.org/posts/2019/04/28/intro/" rel="alternate"></link><published>2019-04-28T16:30:00+02:00</published><updated>2019-04-28T16:45:00+02:00</updated><author><name>Vitaly Samigullin</name></author><id>tag:blog.pilosus.org,2019-04-28:/posts/2019/04/28/intro/</id><summary type="html">&lt;p&gt;My name is Vitaly R. Samigullin. I&amp;#8217;m a software developer, a team
leader, and a person who enjoys thinking about how and why things work
the way they work. Having lived in Austria and Spain, I got some
interesting immigration experience. I&amp;#8217;ve also started a small
publishing company …&lt;/p&gt;</summary><content type="html">&lt;p&gt;My name is Vitaly R. Samigullin. I&amp;#8217;m a software developer, a team
leader, and a person who enjoys thinking about how and why things work
the way they work. Having lived in Austria and Spain, I got some
interesting immigration experience. I&amp;#8217;ve also started a small
publishing company in 2008, which is run by my family&amp;nbsp;now.&lt;/p&gt;
&lt;p&gt;Over the last few years I&amp;#8217;ve written a bunch of texts in social media
that some people confessed to find though-provoking and
interesting. As social networks tend to disurupt the openness of their
services and the internet, making user&amp;#8217;s content ephemeral and hardly
discoverable, I decided to start a personal blog. It&amp;#8217;s somewhat
2000-ish. But still a great tool to categorize and back up texts that
I consider worth&amp;nbsp;saving.&lt;/p&gt;
&lt;img alt="Vitaly" class="align-left" src="https://blog.pilosus.org/images/me.jpg" style="height: 150px;" /&gt;
&lt;p&gt;Although stand-alone blogs never get even a fraction of attention that
social networks get, a blog post is still unbeatable when it comes
&amp;#8220;slow reading&amp;#8221;. It&amp;#8217;s an essential component of learning, actually for
both readers and an author. So I except the blogging to become a tool
to learn new things through explaining them to&amp;nbsp;others.&lt;/p&gt;
&lt;p&gt;I don&amp;#8217;t really want to limit this blog to technology and software
development. I enjoy a variety of topics ranging from arts and
architecture to literature and&amp;nbsp;philosophy.&lt;/p&gt;
&lt;p&gt;I speak a couple of natural languages. With Russian as my first
language, I&amp;#8217;m also fluent in English and have B1-level German. As
every unilingual person I would consider my mother tongue a
one-size-fits-all thing. Still there are some fields (like computer
science, kayaking or playing bass guitar) that easier to think about
in language you used to learn that field. I wouldn&amp;#8217;t even try to
translate my texts to all the languages I&amp;#8217;d mentioned. Rather I will
stick to the language that fits best to each post. So expect here a
Babylon-flavoured&amp;nbsp;mélange.&lt;/p&gt;
&lt;p&gt;How often will I post new posts? Will you find the blog completely
abandoned in a couple of years? I have no idea. But I do know that
quality is over&amp;nbsp;quantity.&lt;/p&gt;
&lt;p&gt;So let&amp;#8217;s get&amp;nbsp;started!&lt;/p&gt;
</content><category term="Blog"></category><category term="intro"></category></entry></feed>