<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0" xmlns:media="http://search.yahoo.com/mrss/"><channel><title><![CDATA[Gui Commits]]></title><description><![CDATA[Software Engineering, and Personal Failures]]></description><link>https://guicommits.com/</link><image><url>https://guicommits.com/favicon.png</url><title>Gui Commits</title><link>https://guicommits.com/</link></image><generator>Ghost 4.48</generator><lastBuildDate>Fri, 03 Apr 2026 19:46:20 GMT</lastBuildDate><atom:link href="https://guicommits.com/rss/" rel="self" type="application/rss+xml"/><ttl>60</ttl><item><title><![CDATA[Python ChatGPT API and DeepSeek API: Straight‑to‑the‑Point Guide 🐍🤖]]></title><description><![CDATA[ChatGPT is the future and not knowing its capabilities will limit your ability to produce high quality code faster than your peers.
Learn how tokenization works and how to use effectively ChatGPT and DeepSeek API to save costs.]]></description><link>https://guicommits.com/python-chatgpt-api-deepseek-api-example/</link><guid isPermaLink="false">684029e8771eec65ab9b64b1</guid><category><![CDATA[🐍 Python]]></category><category><![CDATA[AI]]></category><dc:creator><![CDATA[Guilherme Latrova]]></dc:creator><pubDate>Wed, 04 Jun 2025 11:26:21 GMT</pubDate><media:content url="https://guicommits.com/content/images/2025/06/Cover.png" medium="image"/><content:encoded><![CDATA[<img src="https://guicommits.com/content/images/2025/06/Cover.png" alt="Python ChatGPT API and DeepSeek API: Straight&#x2011;to&#x2011;the&#x2011;Point Guide &#x1F40D;&#x1F916;"><p>ChatGPT is the future and <strong>not knowing</strong> its capabilities <strong>will limit your ability</strong> to produce high quality code faster than your peers.</p><p>No fluff &#x2014; just working answers and bite&#x2011;sized code samples.</p><h2 id="%F0%9F%A4%94-can-i-actually-use-chatgpt-in-python">&#x1F914; Can I actually use ChatGPT in Python?</h2><p><strong>Yes</strong>! Integrating ChatGPT with Python is simpler than cooking instant noodles &#x1F35C;. All you need is to <code>pip install openai</code>, your API key, and a few lines of Python code. Let&#x2019;s do this!</p><h2 id="%F0%9F%A4%A9-is-chatgpt-python-api-free">&#x1F929; Is ChatGPT Python API free?</h2><p><strong>No</strong>. Even if you subscribe to ChatGPT Plus/Pro, API usage is billed separately.</p><p>OpenAI&#x2019;s API runs on a <strong>pay-as-you-go model</strong>, so you can begin experimenting for just a few dollars. If you&apos;re just learning you can start with as little as $5 to get started.</p><h4 id="%F0%9F%92%B8-python-chatgpt-api-pricing">&#x1F4B8; Python ChatGPT API Pricing</h4><p>The prices vary by the model utilized and it&apos;s based per 1M tokens.</p><p>As of the time of this writing, consider these three examples using popular models <code>o1</code>, <code>4o</code> and <code>gpt-image-1</code> (to create that cool Studio Ghibli Photos):</p><!--kg-card-begin: html--><table>
  <thead>
    <tr>
      <th>Alias</th>
      <th>Model</th>
      <th>$ Input Token</th>
      <th>$ Cache Input</th>
      <th>$ Output <strong>Token</strong></th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td><code>gpt-4o</code></td>
      <td><code>gpt-4o-2024-08-06</code></td>
      <td>$2.50</td>
      <td>$1.25</td>
      <td>$10.00</td>
    </tr>
    <tr>
      <td><code>o1</code></td>
      <td><code>o1-2024-12-17</code></td>
      <td>$15.00</td>
      <td>$7.50</td>
      <td>$60.00</td>
    </tr>
    <tr>
      <td><code>gpt-image-1</code></td>
      <td><code>gpt-image-1</code></td>
      <td>$10.00</td>
      <td>$2.50</td>
      <td>$40.00</td>
    </tr>
  </tbody>
</table>
<!--kg-card-end: html--><p>Prices might have gone up (or down - who knows?), so make sure to evaluate the <a href="https://platform.openai.com/docs/pricing">Official ChatGPT Pricing Page</a>.</p><h4 id="%F0%9F%A7%A9-whats-chatgpt-input-and-output-tokens">&#x1F9E9; What&apos;s ChatGPT Input and Output tokens?</h4><p>Tokens are how ChatGPT reads and writes text. Instead of full words, it breaks everything into <strong>small chunks called tokens</strong> &#x2014; like pieces of words, punctuation, or spaces.</p><p><strong>You pay for both the tokens you send (input) and the ones you get back (output).</strong></p><!--kg-card-begin: html--><table>
  <thead>
    <tr>
      <th>Text</th>
      <th>Token Count</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>Hello</td>
      <td>1 token</td>
    </tr>
    <tr>
      <td>Hello, world!</td>
      <td>4 tokens (<code>Hello</code>, <code>,</code>, <code>world</code>, <code>!</code>)</td>
    </tr>
    <tr>
      <td>I love Python.</td>
      <td>4 tokens (<code>I</code>, <code>love</code>, <code>Python</code>, <code>.</code>)</td>
    </tr>
  </tbody>
</table>
<!--kg-card-end: html--><p>Don&apos;t be fooled. <code>1 word != 1 token</code> because 1 token is about 4 characters of English text and <strong>there are words that are counted differently due to their complexity</strong>.</p><!--kg-card-begin: html--><table>
  <thead>
    <tr>
      <th>Word Example</th>
      <th>Token Count Breakdown</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td><code>unbelievable</code></td>
      <td><code>un</code>, <code>believable</code> &#x2192; 2 tokens</td>
    </tr>
    <tr>
      <td><code>extraordinary</code></td>
      <td><code>extra</code>, <code>ordinary</code> &#x2192; 2 tokens</td>
    </tr>
    <tr>
      <td><code>internationalization</code></td>
      <td><code>international</code>, <code>ization</code> &#x2192; 2 tokens</td>
    </tr>
    <tr>
      <td><code>transportation</code></td>
      <td><code>trans</code>, <code>port</code>, <code>ation</code> &#x2192; 3 tokens</td>
    </tr>
    <tr>
      <td><code>misunderstanding</code></td>
      <td><code>mis</code>, <code>under</code>, <code>standing</code> &#x2192; 3 tokens</td>
    </tr>
    <tr>
      <td><code>counterproductive</code></td>
      <td><code>counter</code>, <code>pro</code>, <code>duct</code>, <code>ive</code> &#x2192; 4 tokens</td>
    </tr>
    <tr>
      <td><code>disproportionately</code></td>
      <td><code>dis</code>, <code>pro</code>, <code>portion</code>, <code>ately</code> &#x2192; 4 tokens</td>
    </tr>
  </tbody>
</table>
<!--kg-card-end: html--><p>And you don&apos;t even have to trust me, you can go straight to <a href="https://platform.openai.com/tokenizer">OpenAI Tokenizer</a> and check it for yourself:</p><h4 id="%F0%9F%92%B0-caching-chatgpt-tokens-to-save-money">&#x1F4B0; Caching ChatGPT Tokens to save money</h4><p>Being practical, how can I optimize my tokens so I pay less?</p><p>Well, you could shorten prompts and messages, after all we learned that fewer tokens = lower cost but that&apos;s not easy.</p><p>Instead of putting effort into picking the right tokens (or words), you can try to use caching as much as possible to your advantage.</p><p>When you use ChatGPT API <strong>you&apos;re automatically using the caching capability</strong>. The API caches the <strong>longest prefix of a prompt</strong> recently used. After the first call you get 5&#x2013;10 minutes to reuse it.</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://guicommits.com/content/images/2025/06/Prompt-Caching.png" class="kg-image" alt="Python ChatGPT API and DeepSeek API: Straight&#x2011;to&#x2011;the&#x2011;Point Guide &#x1F40D;&#x1F916;" loading="lazy" width="1200" height="628" srcset="https://guicommits.com/content/images/size/w600/2025/06/Prompt-Caching.png 600w, https://guicommits.com/content/images/size/w1000/2025/06/Prompt-Caching.png 1000w, https://guicommits.com/content/images/2025/06/Prompt-Caching.png 1200w" sizes="(min-width: 720px) 720px"><figcaption>Prompts can be cached to save you some money</figcaption></figure><!--kg-card-begin: html--><table>
  <thead>
    <tr>
      <th>Request #</th>
      <th>Prompt</th>
      <th>Token Count</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>#1</td>
      <td>You&apos;re a Python Expert: How to print hello world?</td>
      <td>11 Tokens - No cache</td>
    </tr>
    <tr>
      <td>#2</td>
      <td>You&apos;re a Python Expert: How to sum two numbers?</td>
      <td>11 Tokens, <strong>7 Tokens Cached</strong></td>
    </tr>
    <tr>
      <td>#3 (After <strong>~10 min of inactivity</strong>)</td>
      <td>You&apos;re a Python Expert: How to sum two numbers?</td>
      <td>11 Tokens - <strong>No cache</strong></td>
    </tr>
  </tbody>
</table>
<!--kg-card-end: html--><p>Hitting caches would save you 50% of the current model pricing. (e.g. $2.5 regular -&gt; $1.25 for cached).</p><p>You can check the usage when using the API so you can evaluate if the prompt worked as you expected. Let&apos;s see it later.</p><p>Don&apos;t forget <strong>you also pay for output tokens</strong> which you can&apos;t <em>really</em> control.</p><h3 id="%F0%9F%94%91-creating-a-chatgpt-api-token-if-you-dont-have-one-yet"><strong>&#x1F511; </strong>Creating a ChatGPT API Token (If you don&apos;t have one yet)</h3><p>If you already have created your account you can skip this section.</p><p>Just give a cool name to your organization (either &quot;Personal&quot; or &quot;Hobbies&quot; should be fine).</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://guicommits.com/content/images/2025/06/02-chatgpt-create-org.png" class="kg-image" alt="Python ChatGPT API and DeepSeek API: Straight&#x2011;to&#x2011;the&#x2011;Point Guide &#x1F40D;&#x1F916;" loading="lazy" width="776" height="670" srcset="https://guicommits.com/content/images/size/w600/2025/06/02-chatgpt-create-org.png 600w, https://guicommits.com/content/images/2025/06/02-chatgpt-create-org.png 776w" sizes="(min-width: 720px) 720px"><figcaption>Step 1 - Create a new org</figcaption></figure><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://guicommits.com/content/images/2025/06/03-chatgpt-create-org.png" class="kg-image" alt="Python ChatGPT API and DeepSeek API: Straight&#x2011;to&#x2011;the&#x2011;Point Guide &#x1F40D;&#x1F916;" loading="lazy" width="471" height="516"><figcaption>Step 2 - Generate your key</figcaption></figure><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://guicommits.com/content/images/2025/06/04-chatgpt-copy-token.png" class="kg-image" alt="Python ChatGPT API and DeepSeek API: Straight&#x2011;to&#x2011;the&#x2011;Point Guide &#x1F40D;&#x1F916;" loading="lazy" width="600" height="757" srcset="https://guicommits.com/content/images/2025/06/04-chatgpt-copy-token.png 600w"><figcaption>Step 3 - Get your key</figcaption></figure><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://guicommits.com/content/images/2025/06/05-chatgpt-pay.png" class="kg-image" alt="Python ChatGPT API and DeepSeek API: Straight&#x2011;to&#x2011;the&#x2011;Point Guide &#x1F40D;&#x1F916;" loading="lazy" width="533" height="569"><figcaption>Step 4 - Pay to use ChatGPT API</figcaption></figure><p>Pay for it. If you&apos;re just experimenting, I recommend paying only $5 and sticking to model <code>gpt-4.1-nano</code> which is the cheapest.</p><h3 id="how-to-create-my-chatgpt-api-token">How to create my ChatGPT API Token</h3><p>Assuming you already have an account.</p><p>We can start by visiting <a href="https://platform.openai.com/settings/organization/api-keys">OpenAI&apos;s Platform API Keys Page</a> (Not the regular ChatGPT page).</p><p>Click on <code>Create new secret key</code> and submit it:</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://guicommits.com/content/images/2025/06/06-create-api-key.png" class="kg-image" alt="Python ChatGPT API and DeepSeek API: Straight&#x2011;to&#x2011;the&#x2011;Point Guide &#x1F40D;&#x1F916;" loading="lazy" width="497" height="522"><figcaption>Creating a ChatGPT API Key</figcaption></figure><h2 id="%F0%9F%A7%91%E2%80%8D%F0%9F%92%BB-chatgpt-api-example-using-responsescreate">&#x1F9D1;&#x200D;&#x1F4BB; ChatGPT API Example using responses.create</h2><p>To get you started you must install <code>openai</code> by running: <code>pip install openai</code>. I&apos;m also installing <code>rich</code> to display the data nicely.</p><pre><code class="language-py">import typing as t
from openai import OpenAI
from rich import print

API_KEY: t.Final = &quot;sk-proj-[REDACTED]&quot;
MODEL: t.Final = &quot;gpt-4.1-nano&quot;  # Cheapest: $0.10 Input / $0.40 Output


class PythonExpert:
    def __init__(self):
        self.client = OpenAI(api_key=API_KEY)

    def ask_the_expert(self, question: str):
        response = self.client.responses.create(
            model=MODEL,
            instructions=&quot;You&apos;re a Python expert. Answer the question as best you can.&quot;,
            input=question,
        )

        return response


def main():
    expert = PythonExpert()

    question = &apos;How do I print &quot;Hello, world!&quot; in Python?&apos;
    response = expert.ask_the_expert(question)
    answer = response.output_text

    print(f&quot;[yellow]Question:[/yellow] {question}&quot;)
    print(f&quot;\tAnswer: {answer}&quot;)


if __name__ == &quot;__main__&quot;:
    main()
</code></pre><p>Note we&apos;re using <code>client.responses.create</code>. This is a recent release from 2025, and it supports background execution, web search, file search, memorize history, and interact with GUI.</p><p>You might decide to stick to the old <code>client.chat.completions.create()</code> if you want to keep the history and context locally.</p><h2 id="%F0%9F%A7%91%E2%80%8D%F0%9F%92%BB-how-to-measure-chatgpt-api-token-caching">&#x1F9D1;&#x200D;&#x1F4BB; How to measure ChatGPT API Token Caching?</h2><p>Let&apos;s say you want to measure your prompts to see how much caching you can get to optimize costs.</p><p>ChatGPT <strong>Caching only works for prompts with &gt; 1,024 tokens</strong>. For our example I&#x2019;m going to provide an extremely long <code>instruction</code> and try to be as verbose as possible:</p><pre><code class="language-py">import typing as t
from openai import OpenAI
from rich import print
from rich.console import Console
from rich.table import Table as RichTable
from textwrap import dedent


API_KEY: t.Final = &quot;sk-proj-[REDACTED]&quot;
MODEL: t.Final = &quot;gpt-4.1-nano&quot;  # &#x1F448; Cheapest: $0.10 Input / $0.40 Output


class PythonExpert:
    def __init__(self):
        self.client = OpenAI(api_key=API_KEY)

    # NOTE: &#x1F447; Since we&apos;re simulating token caching, we need to ensure it has at least 1,024 tokens.
    # Part of this initial prompt boilerplate will ALSO be cached.
    BASE_PROMPT_BULLSHIT = dedent(
        &quot;&quot;&quot;
    &#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;  PYTHON EXPERT SYSTEM REFERENCE GUIDE  &#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;

    SECTION 1  - Code Style (PEP 8)
    &#x25B8; Prefer *snake_case* for variables and functions, *PascalCase* for classes, and UPPER_SNAKE_CASE for module -level constants.
    &#x25B8; Keep lines &#x2264; 79 chars; wrap long expressions with implied line -continuations inside (), [] or {}.
    &#x25B8; Imports: standard lib &#x25B8; third -party &#x25B8; local, each block alphabetised; never use wildcard imports.
    &#x25B8; Use f -strings for interpolation; reserve `%` formatting for logging -style placeholders.

    SECTION 2  - Typing &amp; Static Analysis
    &#x25B8; Add *type hints* (PEP 484) to every public function: def square(n: int | float) -&gt; int | float: &#x2026;
    &#x25B8; Avoid `Any`; prefer Protocols and generics for flexible APIs.
    &#x25B8; Run *mypy* or *pyright* in CI; treat *warnings as errors* to prevent regressions.
    &#x25B8; Use *typing -extensions* for back -ports of upcoming features (e.g. TypeAliasType).

    SECTION 3  - Performance &amp; Profiling
    &#x25B8; Use built -ins and std -lib (sum, max, heapq) before reaching for numpy or pandas; C -optimised code often beats naive C -extensions.
    &#x25B8; Profile first!  `python  -m cProfile  -o stats.prof main.py` + *snakeviz* to visualise hotspots.
    &#x25B8; Favour list -comprehensions over explicit loops where readability permits; avoid premature micro -optimisation.
    &#x25B8; For numeric hotspots consider `numba` or Cython; for I/O hot paths, use buffering and async.

    SECTION 4  - Concurrency &amp; Parallelism
    &#x25B8; *asyncio* excels at I/O -bound workloads: await network, file, or DB calls without blocking the event -loop.
    &#x25B8; For CPU -bound tasks use `concurrent.futures.ProcessPoolExecutor` or *multiprocessing*; the GIL limits pure threads.
    &#x25B8; Shield long awaitables with `asyncio.to_thread` in 3.9+ when you need to run a sync function without freezing awaitables.
    &#x25B8; Never share mutable state across processes without proper IPC (queues, managers, shared memory).

    SECTION 5  - Packaging &amp; Distribution
    &#x25B8; Adopt *pyproject.toml*; specify build -system (`[build -system] requires = [&quot;setuptools&gt;=64&quot;, &quot;wheel&quot;]`).
    &#x25B8; `python  -m build` produces sdist + wheel; upload via *twine* to TestPyPI first.
    &#x25B8; Use *semantic -versioning*: MAJOR &#x2192; breaking, MINOR &#x2192; features, PATCH &#x2192; fixes.
    &#x25B8; Provide rich metadata (classifiers, project -urls) so pip search surfaces your project.

    SECTION 6  - Testing Philosophy
    &#x25B8; Prefer *pytest*; write small, deterministic tests&#x2014;no sleeps or network calls.
    &#x25B8; Isolate side -effects with fixtures + tmp_path; parametrize happy -path and edge cases.
    &#x25B8; Aim for behaviour over implementation: changing internals should not break tests as long as public contract holds.
    &#x25B8; Track coverage but don&apos;t chase 100 %; guard against critical regressions instead.

        SECTION 7 - Debugging &amp; Logging
    &#x25B8; Insert `breakpoint()` (Python 3.7+) to drop into pdb without imports; use `pdbpp` for nicer colours &amp; sticky mode.
    &#x25B8; Configure *logging* early: level via env var, write JSON logs in production, colourised human -friendly logs locally.
    &#x25B8; Never log secrets; scrub tokens/IP addresses with custom filters or structlog processors.
    &#x25B8; Prefer structured logging over free -text for easier log aggregation and querying.

    SECTION 8 - Security Best Practices
    &#x25B8; Load secrets from the environment or a secrets -manager&#x2014;never hard -code keys.
    &#x25B8; Pin dependencies with hashes (`pip -tools`, `poetry lock --no-update`); audit with `pip-audit` or GitHub Dependabot.
    &#x25B8; Validate user input; distrust deserialisation (yaml.load, pickle).  Use `json.loads` or pydantic models instead.
    &#x25B8; Keep Python patched (security -fix releases); run containers as non -root and drop capabilities.

    SECTION 9 - Data Classes &amp; Validation
    &#x25B8; Use `@dataclass(slots=True, frozen=True)` for lightweight value objects; benefits: immutability &amp; memory savings.
    &#x25B8; For external data, model with *pydantic* or *attrs* for runtime validation and parsing.
    &#x25B8; Document JSON schema; version breaking changes.  Provide migration scripts between schema versions.
    &#x25B8; Convert between domain models and persistence DTOs to keep layers isolated.

    SECTION 10 - Command -line Interfaces (CLI)
    &#x25B8; Prefer *typer* (built on click) for ergonomic CLIs with auto -generated help and type -hints.
    &#x25B8; Support `--version`, `--help`, exit codes (0 success, non -zero failure).  Provide rich `stderr` messages for errors.
    &#x25B8; Package entry -points under `[project.scripts]` in *pyproject.toml* so `pipx` users can install system -wide.
    &#x25B8; Test CLI commands with `pytest` + `capsys` or *click.testing*&apos;s runner.

    SECTION 11 - Configuration Management
    &#x25B8; Hierarchy: CLI args &#x25B6; env vars &#x25B6; `.env` file &#x25B6; config file &#x25B6; defaults.  Later overrides earlier.
    &#x25B8; Use *dynaconf* or `pydantic.Settings` for 12 -factor -style config loading.
    &#x25B8; Keep secrets out of git; supply sample env files for local dev.
    &#x25B8; Provide schema validation so a broken config fails fast at startup.

    SECTION 12 - Documentation &amp; Docstrings
    &#x25B8; Write *Google -style* or *NumPy -style* docstrings; include type hints, parameter descriptions, return values, raises.
    &#x25B8; Generate docs with *mkdocs -material* or *Sphinx* + *autodoc*; host on GitHub Pages.
    &#x25B8; Keep examples runnable: embed doctests or use *pytest -doctestplus*.
    &#x25B8; Treat docs as code: review PRs, run spell -checkers (codespell), and enforce link rot checks.

    &#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;
    &quot;&quot;&quot;
    )

    def ask_the_expert(self, question: str):
        response = self.client.responses.create(
            model=MODEL,
            instructions=self.BASE_PROMPT_BULLSHIT,
            input=question,
        )

        return response

# &#x1F447; Organize &amp; Output it nicely
def _create_table() -&gt; RichTable:
    table = RichTable(title=&quot;Token Usage Summary&quot;)
    table.add_column(&quot;[bold]Prompt[/bold]&quot;)
    table.add_column(&quot;[bold]Input Tokens[/bold]&quot;, justify=&quot;right&quot;)
    table.add_column(&quot;[bold]Cached Tokens[/bold]&quot;, justify=&quot;right&quot;)
    table.add_column(&quot;[bold]Output Tokens[/bold]&quot;, justify=&quot;right&quot;)
    table.add_section()

    return table


QUESTIONS = [
    &apos;How do I print &quot;Hello, world!&quot; in Python, and why is `print` a function rather than a statement?&apos;,
    # &#x1F446; Verbose
    # &#x1F447; Somewhat simple to simulate cache hitting
    &apos;How do I print &quot;Hello, world!&quot; in Python?&apos;,
]


def main():
    expert = PythonExpert()
    table = _create_table()
    console = Console()

    for question in QUESTIONS:
        response = expert.ask_the_expert(question)

        answer = response.output_text
        input_tokens = response.usage.input_tokens
        cached = response.usage.input_tokens_details.cached_tokens
        output_tokens = response.usage.output_tokens

        print(f&quot;[yellow]Question:[/yellow] {question}&quot;)
        print(f&quot;\tAnswer: {answer}&quot;)

        table.add_row(
            question,
            str(input_tokens),
            str(cached),
            str(output_tokens),
        )

    console.print(table)


if __name__ == &quot;__main__&quot;:
    main()
</code></pre><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://guicommits.com/content/images/2025/06/07-token-usage-sample.png" class="kg-image" alt="Python ChatGPT API and DeepSeek API: Straight&#x2011;to&#x2011;the&#x2011;Point Guide &#x1F40D;&#x1F916;" loading="lazy" width="1322" height="137" srcset="https://guicommits.com/content/images/size/w600/2025/06/07-token-usage-sample.png 600w, https://guicommits.com/content/images/size/w1000/2025/06/07-token-usage-sample.png 1000w, https://guicommits.com/content/images/2025/06/07-token-usage-sample.png 1322w" sizes="(min-width: 720px) 720px"><figcaption>Token Usage through API Example</figcaption></figure><p>Since we know that <code>gpt-4.1-nano</code> charges $0.10 / $0.025 / $0.40 we can calculate:</p><!--kg-card-begin: html--><table>
  <thead>
    <tr>
      <th>Prompt #</th>
      <th>Uncached Input Tokens</th>
      <th>Cached Input Tokens</th>
      <th>Output Tokens</th>
      <th>Estimated Cost</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>#1</td>
      <td>1,369</td>
      <td>0</td>
      <td>227</td>
      <td>$0.000<strong>2277</strong></td>
    </tr>
    <tr>
      <td>#2</td>
      <td>107</td>
      <td>1,262</td>
      <td>30</td>
      <td>$0.000<strong>05926</strong></td>
    </tr>
  </tbody>
</table>
<!--kg-card-end: html--><h2 id="%F0%9F%93%90-structuring-chatgpt-responses-as-json-models">&#x1F4D0; Structuring ChatGPT responses as JSON Models</h2><p>When you&apos;re creating your tool you probably want a <strong>structured response</strong> to ensure the output has a known format.</p><p>You can achieve that by using <code>Pydantic</code> and ChatGPT&apos;s <code>client.responses.parse</code>.</p><ol><li>Define your expected response model with Pydantic;</li><li>Update to use <code>client.responses.parse</code></li><li>Update <code>instructions</code> to mention what you expect to be parsed</li><li>Pass your model as the <code>text_format</code> keyword argument</li><li>Get <code>response.output_parsed</code></li></ol><pre><code class="language-py">import typing as t
from openai import OpenAI
from rich import print
from pydantic import BaseModel

API_KEY: t.Final = &quot;sk-proj-[REDACTED]&quot;
MODEL: t.Final = &quot;gpt-4.1-nano&quot;  # Cheapest: $0.10 Input / $0.40 Output


# &#x1F447; Define the expected output model
class ExpertResponse(BaseModel):
    explanation: str
    example_code: str


class PythonExpert:
    def __init__(self):
        self.client = OpenAI(api_key=API_KEY)

    def ask_the_expert(self, question: str) -&gt; ExpertResponse:
        # &#x1F447; Use `responses.parse`
        response = self.client.responses.parse(
            model=MODEL,
            # &#x1F447; Explain what you&apos;re willing to receive
            instructions=&quot;You&apos;re a Python expert. Answer the question as best you can. Give a brief explanation and provide example code if applicable.&quot;,
            input=question,
            # &#x1F447; Pass expected output model
            text_format=ExpertResponse,
        )

        # &#x1F447; Pass the parsed model
        return response.output_parsed


def main():
    expert = PythonExpert()

    question = &apos;How do I print &quot;Hello, world!&quot; in Python?&apos;
    response = expert.ask_the_expert(question)

    print(f&quot;[yellow]Question:[/yellow] {question}&quot;)
    print(f&quot;[yellow]Explanation:[/yellow]\n\t {response.explanation}&quot;)
    print(f&quot;[yellow]Example Code[/yellow]:\n\t{response.example_code}&quot;)


if __name__ == &quot;__main__&quot;:
    main()
</code></pre><p>And it works as expected:</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://guicommits.com/content/images/2025/06/08-output-format-sample-1.png" class="kg-image" alt="Python ChatGPT API and DeepSeek API: Straight&#x2011;to&#x2011;the&#x2011;Point Guide &#x1F40D;&#x1F916;" loading="lazy" width="970" height="100" srcset="https://guicommits.com/content/images/size/w600/2025/06/08-output-format-sample-1.png 600w, https://guicommits.com/content/images/2025/06/08-output-format-sample-1.png 970w" sizes="(min-width: 720px) 720px"><figcaption>JSON Output Example</figcaption></figure><p>Without it, you&#x2019;d have to include more instructions to force ChatGPT to reply in the expected format. I did it many times, it&apos;s not a pleasant experience.</p><h2 id="%F0%9F%92%B0-cheaper-api-alternative-to-openais-chatgpt">&#x1F4B0; Cheaper API alternative to OpenAI&apos;s ChatGPT</h2><p><strong>DeepSeek is significantly cheaper</strong> than ChatGPT (from my experience a bit slower though):</p><!--kg-card-begin: html--><table>
  <thead>
    <tr>
      <th>ChatGPT Model</th>
      <th>Pricing/1M Input Tokens</th>
      <th>DeepSeek Model</th>
      <th>Pricing/1M Input Tokens</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td><code>gpt-4o</code></td>
      <td>$2.50</td>
      <td><code>deepseek-chat</code></td>
      <td>$0.27</td>
    </tr>
    <tr>
      <td><code>o3</code></td>
      <td>$10.0</td>
      <td><code>deepseek-reasoner</code></td>
      <td>$0.55</td>
    </tr>
  </tbody>
</table>
<!--kg-card-end: html--><p>Still not cheap enough? Okay, you can get this even lower by using it during <strong>off-peak hours</strong> giving you up to <strong>75% OFF</strong>:</p><!--kg-card-begin: html--><table>
  <thead>
    <tr>
      <th>DeepSeek Model</th>
      <th>Cached</th>
      <th>Regular Hours Pricing/1M Tokens</th>
      <th>Discount Off-Peak Hours/1M Tokens</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td><code>deepseek-chat</code></td>
      <td>No</td>
      <td>$0.27</td>
      <td>$0.135</td>
    </tr>
    <tr>
      <td><code>deepseek-chat</code></td>
      <td>Yes</td>
      <td>$0.07</td>
      <td>$0.035</td>
    </tr>
    <tr>
      <td><code>deepseek-reasoner</code></td>
      <td>No</td>
      <td>$0.55</td>
      <td>$0.135</td>
    </tr>
    <tr>
      <td><code>deepseek-reasoner</code></td>
      <td>Yes</td>
      <td>$0.14</td>
      <td>$0.035</td>
    </tr>
  </tbody>
</table>
<!--kg-card-end: html--><h3 id="%F0%9F%90%B3-python-deepseek-api-vs-chatgpt-api">&#x1F433; Python DeepSeek API vs ChatGPT API</h3><p>Alright, let&#x2019;s use DeepSeek, but... Now I have to install yet another lib and modify my working code?</p><p>No. You don&apos;t need to install anything else but <code>openai</code> (as we already did).</p><p>You can create your DeepSeek API Key <a href="https://platform.deepseek.com/api_keys"><strong>here</strong></a>.</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://guicommits.com/content/images/2025/06/09-deepseek-create-api-key.png" class="kg-image" alt="Python ChatGPT API and DeepSeek API: Straight&#x2011;to&#x2011;the&#x2011;Point Guide &#x1F40D;&#x1F916;" loading="lazy" width="1005" height="718" srcset="https://guicommits.com/content/images/size/w600/2025/06/09-deepseek-create-api-key.png 600w, https://guicommits.com/content/images/size/w1000/2025/06/09-deepseek-create-api-key.png 1000w, https://guicommits.com/content/images/2025/06/09-deepseek-create-api-key.png 1005w" sizes="(min-width: 720px) 720px"><figcaption>Create DeepSeek API Key</figcaption></figure><p>You just can&apos;t use <code>client.responses.create</code>, you need <code>client.chat.completions.create</code>.</p><pre><code class="language-py">import typing as t
from openai import OpenAI
from rich import print

API_KEY: t.Final = &quot;sk-[REDACTED]&quot;
MODEL: t.Final = &quot;deepseek-chat&quot;


class PythonExpert:
    def __init__(self):
        self.client = OpenAI(
            api_key=API_KEY,
            # &#x1F447; The trick!
            base_url=&quot;https://api.deepseek.com/v1&quot;,
        )

    def ask_the_expert(self, question: str):
        response = self.client.chat.completions.create(
            model=MODEL,
            messages=[
                # &#x1F447; System message to set the context, analogous to `instructions`
                {
                    &quot;role&quot;: &quot;system&quot;,
                    &quot;content&quot;: &quot;You&apos;re a Python expert. Answer the question as best you can.&quot;,
                },
                # &#x1F447; Actual request
                {&quot;role&quot;: &quot;user&quot;, &quot;content&quot;: question},
            ],
        )

        return response


def main():
    expert = PythonExpert()

    question = &apos;How do I print &quot;Hello, world!&quot; in Python?&apos;
    response = expert.ask_the_expert(question)
    # &#x1F447; Not much user friendly
    answer = response.choices[0].message.content

    print(f&quot;[yellow]Question:[/yellow] {question}&quot;)
    print(f&quot;\tAnswer: {answer}&quot;)


if __name__ == &quot;__main__&quot;:
    main()
</code></pre><p>I hope you enjoyed learning these tricks. If it was useful give me a <a href="https://x.com/intent/follow?original_referer=https%3A%2F%2Fguicommits.com%2F&amp;screen_name=guilatrova">follow on X</a> to be notified when I post new Python tricks.</p><p></p><p></p><p></p>]]></content:encoded></item><item><title><![CDATA[Add docstrings to Python Enum members]]></title><description><![CDATA[Python Enum members don't "support" docstrings natively. I show how you can use __new__ to require members to take docstrings for each Enum member.]]></description><link>https://guicommits.com/add-docstrings-python-enum-members/</link><guid isPermaLink="false">6685924e771eec65ab9b6415</guid><category><![CDATA[🐍 Python]]></category><dc:creator><![CDATA[Guilherme Latrova]]></dc:creator><pubDate>Wed, 03 Jul 2024 18:39:16 GMT</pubDate><media:content url="https://guicommits.com/content/images/2024/07/Cover.png" medium="image"/><content:encoded><![CDATA[<img src="https://guicommits.com/content/images/2024/07/Cover.png" alt="Add docstrings to Python Enum members"><p>Recently I learned that Python Enum <strong>members</strong> don&apos;t &quot;support&quot; docstrings natively which is quite annoying.</p><p>I had to manage a list of feature flags and provide a good description for each of them through FastAPI:</p><pre><code class="language-py">from enum import StrEnum, auto

class FeatureFlag(StrEnum):
    &quot;&quot;&quot;Holds a list of available feature toggles&quot;&quot;&quot;
    
    ENABLE_CHAT_GPT = auto()
    &quot;&quot;&quot;Requested by a few customers to enable Chat GPT within the app&quot;&quot;&quot;
    
    DARK_MODE = auto()
    &quot;&quot;&quot;Fixes your color schema&quot;&quot;&quot;
    </code></pre><p></p><h2 id="python-enum-member-docstring-limitation">Python Enum member docstring limitation</h2><p>To my surprise when I extracted each member&apos;s <code>__doc__</code> (which should contain the docstring) I got the <code>FeatureFlag</code> class docstring instead.</p><pre><code>print(FeatureFlag.ENABLE_CHAT_GPT.__doc__) # &#x274C; output: Holds a list of available feature toggles

print(FeatureFlag.DARK_MODE.__doc__) # &#x274C; output: Holds a list of available feature toggles</code></pre><p><strong>This is definitively not what we expect. We want the enum member&apos;s docstring.</strong></p><p></p><h2 id="how-to-add-docstring-to-enum-members">How to add docstring to enum members?</h2><p>The best solution I could come up with is to override the <code>__new__</code> method and require members to take two string values:</p><pre><code class="language-py">from enum import StrEnum, auto
import typing as t


class FeatureFlag(StrEnum):
    &quot;&quot;&quot;Holds a list of available feature toggles&quot;&quot;&quot;
    
    # &#x1F447; Magic method needed to enforce members to take docstrings
    def __new__(cls, value: str, docstr: str) -&gt; t.Self:
        member = str.__new__(cls, value)

        member._value_ = value
        member.__doc__ = docstr.strip()  # &#x1FA84; Magic

        return member


    ENABLE_CHAT_GPT = (auto(), &quot;&quot;&quot;Requested by a few customers to enable Chat GPT within the app&quot;&quot;&quot;)
        
    DARK_MODE = (auto(), &quot;&quot;&quot;Fixes your color schema&quot;&quot;&quot;)
    

print(FeatureFlag.ENABLE_CHAT_GPT.__doc__) # &#x2705; output: Requested by a few customers to enable Chat GPT within the app
print(FeatureFlag.DARK_MODE.__doc__) # &#x2705; output: Fixes your color schema</code></pre><p></p><p>This might not be the cleanest, but it works and it&apos;s simple to maintain.</p><p>Now I can iterate over the enum members and take each &quot;docstring&quot;:</p><pre><code>flags = [dict(key=flag.value, description=flag.__doc__) for flag in FeatureFlags]
print(flags)

# outputs:
# [
#  {&apos;key&apos;: &apos;enable_chat_gpt&apos;, &apos;description&apos;: &apos;Requested by a few customers to enable Chat GPT within the app&apos;}, 
# {&apos;key&apos;: &apos;dark_mode&apos;, &apos;description&apos;: &apos;Fixes your color schema&apos;}
#]</code></pre>]]></content:encoded></item><item><title><![CDATA[Generic functions and generic classes in Python]]></title><description><![CDATA[Python does have generics! Learn how to use typing TypeVar and Generic to reuse code with proper typing.]]></description><link>https://guicommits.com/python-generic-type-function-class/</link><guid isPermaLink="false">65e25dba771eec65ab9b6273</guid><category><![CDATA[🐍 Python]]></category><category><![CDATA[typing]]></category><dc:creator><![CDATA[Guilherme Latrova]]></dc:creator><pubDate>Fri, 01 Mar 2024 23:20:35 GMT</pubDate><media:content url="https://guicommits.com/content/images/2024/03/cover.png" medium="image"/><content:encoded><![CDATA[<img src="https://guicommits.com/content/images/2024/03/cover.png" alt="Generic functions and generic classes in Python"><p>&quot;Generic&quot; is a term used for any typing that might change based on the context.</p><p>If we have a function that may take either strings or ints and return the sum or concatenation of both values, without generic we would have to define two distinct functions:</p><pre><code class="language-py">def sum_numbers(v1: int, v2: int) -&gt; int:
    return v1 + v2

def concat_strs(v1: str, v2: str) -&gt; str:
    return v1 + v2

numbers = sum_numbers(10, 20)
strs = concat_strs(&quot;app&quot;, &quot;le&quot;)

print(numbers)
print(strs)
</code></pre><p>Even though we got the proper typing, this isn&apos;t good. My code is mostly duplicated just for the sake of types - that&apos;s not really what we want here.</p><p>That&apos;s where generics can make our life easier. <strong>We reuse the code snippet while keeping the dynamic typing based on context.</strong></p><p>This is quite popular in <a href="https://www.typescriptlang.org/docs/handbook/2/generics.html">Typescript</a> but doesn&apos;t seem as popular in Python.</p><h1 id="%F0%9F%8F%B7%EF%B8%8F-python-generic-in-functions">&#x1F3F7;&#xFE0F; Python generic in functions</h1><p>To make this happen we need to define a <code>typing.TypeVar</code> to be used as the type of each argument and output.</p><pre><code class="language-py">import typing as t


T = t.TypeVar(&quot;T&quot;) # Defines the TypeVar

# v1, v2 and outcome should all be of type T
def sum(v1: T, v2: T) -&gt; T:
    return v1 + v2

numbers = sum(10, 20)
strs = sum(&quot;app&quot;, &quot;le&quot;)

print(numbers)
print(strs)
</code></pre><p></p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://guicommits.com/content/images/2024/03/01-generic-function-ide.gif" class="kg-image" alt="Generic functions and generic classes in Python" loading="lazy" width="632" height="405"><figcaption>Single generic function with proper typing</figcaption></figure><p>Now your IDE infers the output based on the argument type:</p><h2 id="%F0%9F%A4%8C-narrow-down-typevar-types">&#x1F90C; Narrow down TypeVar types</h2><p>This will work with any value that can be &quot;summed&quot;. This is problematic though because someone may attempt to pass an invalid type that wouldn&apos;t make sense.</p><p>Like:</p><pre><code class="language-py">will_fail = sum(Exception(&quot;what?&quot;), Exception(&quot;lol&quot;))
</code></pre><p>Even though the IDE will resolve the outcome to another <code>Exception</code>, the execution will raise an exception: <code>TypeError: unsupported operand type(s) for +: &apos;Exception&apos; and &apos;Exception&apos;</code>.</p><p>To resolve this we can limit allowed types to work properly using <code>TypeVar</code>&apos;s <code>bound</code> argument.</p><pre><code class="language-py">T = t.TypeVar(&quot;T&quot;, bound=str | int | float)
</code></pre><p>This will ensure your type checker catches if any other type that is non str, int, or float is passed as an argument.</p><h1 id="%F0%9F%8E%A9-using-generics-in-classes">&#x1F3A9; Using generics in classes</h1><p>Generic classes also exist, and they allow more complex configurations.</p><p>Imagine a data-layer class that reads data from a data source and parses to some Python model for our scenario.</p><p><code>typing.TypeVar</code> is not enough anymore, we also need <code>typing.Generic</code>.</p><p>Each data-layer class will be responsible for:</p><ul><li>Returning one entity based on id</li><li>Listing all entities</li><li>Creating an entity</li></ul><p>Note <strong>we&apos;re not implementing this functionality</strong> as our purpose is only to understand how to use generics to define complex classes.</p><pre><code class="language-py">import typing as t
from datetime import datetime
from abc import ABC, abstractmethod

T = t.TypeVar(&quot;T&quot;) # &#x1F448; We still use the TypeVar

# &#x1F447; Now we must also rely on Generic to say the class
# accepts a type
class BaseDatabase(t.Generic[T], ABC):
    @abstractmethod
    def get_by_id(self, id: int) -&gt; T:
        ...

    @abstractmethod
    def list_all(self) -&gt; list[T]:
        ...

    @abstractmethod
    def create(self, entity: T) -&gt; None:
        ...
</code></pre><p>We can start with the <code>CompanyDatabase</code> class:</p><pre><code class="language-py">class Company:  # Model sample for the company DB
    name: str
    phone: str
    address: str


class CompanyDatabase(BaseDatabase[Company]):
    def get_by_id(self, id: int) -&gt; Company:
        return super().get_by_id(id)

    def list_all(self) -&gt; list[Company]:
        return super().list_all()

    def create(self, entity: Company) -&gt; None:
        return super().create(entity)
</code></pre><p>Note the IDE is capable of defining the correct typing by itself:</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://guicommits.com/content/images/2024/03/02-ide-guesses-correcty-typing.gif" class="kg-image" alt="Generic functions and generic classes in Python" loading="lazy" width="710" height="578"><figcaption>IDE setting correct types from generic</figcaption></figure><p>and even if we don&apos;t create the methods, it also guesses everything correctly:</p><pre><code class="language-py">class EmployeeDatabase(BaseDatabase[Employee]):
    pass

employee_db = EmployeeDatabase()

found_employee = employee_db.get_by_id(1)
all_employees = employee_db.list_all()
</code></pre><p></p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://guicommits.com/content/images/2024/03/03-ide-guesses-correct-typing-undef.gif" class="kg-image" alt="Generic functions and generic classes in Python" loading="lazy" width="797" height="219"><figcaption>IDE guessing expected types from generic</figcaption></figure><h1 id="%F0%9F%91%8E-overcoming-python-generic-limitations">&#x1F44E; Overcoming Python generic limitations</h1><p>Unfortunately, Python is not as strong as TypeScript regarding typing.</p><p>Functions, differently from typing, can&apos;t use the <code>Generic</code> which means we can&apos;t:</p><pre><code class="language-py"># &#x26A0;&#xFE0F;&#x26A0;&#xFE0F;&#x26A0;&#xFE0F; Broken code for concept only:
import typing as T

T = t.TypeVar(&quot;T&quot;)

def get_something(t.Generic[T], v: str) -&gt; T:
    ...

some_str = get_something[str](&quot;str&quot;)
some_int = get_something[int](&quot;10&quot;)
</code></pre><p>This won&apos;t work.</p><p><a href="https://peps.python.org/pep-0484/#the-type-of-class-objects">PEP 0484</a> suggests we pass the actual type as an argument to allow proper inference.</p><pre><code class="language-py">import typing as t


T = t.TypeVar(&quot;T&quot;)

# &#x1F447; We must define whether we&apos;re receiving a TYPE of T
def get_something(output_type: t.Type[T], v: str) -&gt; T:
    ...

# &#x1F447; Now it works
some_str = get_something(str, &quot;str&quot;)
some_int = get_something(int, &quot;10&quot;)
</code></pre><p>The IDE recognizes it correctly:</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://guicommits.com/content/images/2024/03/04-ide-infer-pep-suggestion.gif" class="kg-image" alt="Generic functions and generic classes in Python" loading="lazy" width="608" height="127"><figcaption>Overcoming Python&apos;s limitation</figcaption></figure><p>But I still don&apos;t like it &#x1F605; is it just me or it seems a bit hacky?</p><h2 id="using-generic-classes-to-behave-as-functions">Using generic classes to behave as functions</h2><p>I don&apos;t know you, but I&apos;d like to keep the syntax we already follow using brackets, like:</p><pre><code class="language-py">x = list[str] # [str]
x = set[int] # [int]

class EmployeeDatabase(BaseDatabase[Employee]): # [Employee]
    ...

# WTH? This feels weird
x = get_something(int, 10)
</code></pre><p>I must implement something to feel more natural like we do on TypeScript.</p><p>Typescript relies consistently on <code>&lt;&gt;</code> e.g. <code>&lt;int&gt;</code> and <code>&lt;str&gt;</code>. I do believe Python should follow the same logic for <code>[]</code>.</p><p>To make this happen we must define a class and override its <code>__new__</code> magic method to behave like a function:</p><pre><code class="language-py">import typing as t

T = t.TypeVar(&quot;T&quot;)

# &#x1F447; Keep class name lowercase so it feels like a function, name it as you want your &apos;&apos;&apos;function&apos;&apos;&apos; to be named:
class get_something(t.Generic[T]):

    # &#x1F447; This is the secret
    def __new__(
        cls,
        v: str, # Add here as many args you think your function should take
    ):
        generated_instance = super().__new__(cls)
        return generated_instance.execute(v)

    # &#x1F447; Pretend this is your actual function implementation, name it anything you wish
    def execute(self, v: str) -&gt; T: # &#x1F448; Define T as the return type
        ... # Do whatever you want

        return t.cast(T, v) # &#x1F448; Ensure returned type is T

# &#x1F481;&#x200D;&#x2642;&#xFE0F;&#x1FA84;&#x1F430; It just works
some_str = get_something[str](&quot;str&quot;)
some_int = get_something[int](&quot;10&quot;)
</code></pre><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://guicommits.com/content/images/2024/03/05-ide-infer-suggestion.gif" class="kg-image" alt="Generic functions and generic classes in Python" loading="lazy" width="608" height="127"><figcaption>Overcoming Python&apos;s limitation again but pretty</figcaption></figure><p>It feels better to me.</p><p>Note this is not something &quot;new&quot; I&apos;m coming up with. Some standard Python &quot;&quot;functions&quot;&quot; (that are not functions) do the same as <code>defaultdict</code>:</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://guicommits.com/content/images/2024/03/06-defaultdict-lier.png" class="kg-image" alt="Generic functions and generic classes in Python" loading="lazy" width="454" height="85"><figcaption><code>defaultdict</code> lied all the time to you</figcaption></figure><p><a href="https://twitter.com/intent/follow?original_referer=https%3A%2F%2Fguicommits.com%2F&amp;ref_src=twsrc%5Etfw%7Ctwcamp%5Ebuttonembed%7Ctwterm%5Efollow%7Ctwgr%5Eguilatrova&amp;region=follow_link&amp;screen_name=guilatrova">Follow me</a> for more Python magic.</p>]]></content:encoded></item><item><title><![CDATA[How to run pytest in parallel on GitHub actions]]></title><description><![CDATA[Parallelizing tests can significantly improve integration test performance. By splitting tests across multiple workers, you can achieve a +50% performance improvement, reducing test time. This approach is essential for efficient PR merges and overall development workflow.]]></description><link>https://guicommits.com/parallelize-pytest-tests-github-actions/</link><guid isPermaLink="false">65c62100771eec65ab9b61e6</guid><category><![CDATA[🐍 Python]]></category><category><![CDATA[DevOps]]></category><category><![CDATA[GHA]]></category><dc:creator><![CDATA[Guilherme Latrova]]></dc:creator><pubDate>Sun, 11 Feb 2024 20:13:14 GMT</pubDate><media:content url="https://guicommits.com/content/images/2024/02/cover.png" medium="image"/><content:encoded><![CDATA[<img src="https://guicommits.com/content/images/2024/02/cover.png" alt="How to run pytest in parallel on GitHub actions"><p>In my current company, we write lots of integration tests. Integration tests are great because they encompass the whole functionality of a use case end to end <strong>which frequently includes the database</strong>.</p><p>The downside is that it tends to be slow. As for each test you need to set up the data before, execute, and then clear the database for the next tests.</p><p>As you write many tests it&apos;s expected the whole suite case will take ~5min to complete or even more. <strong>Today it takes around ~12min</strong>.</p><p>This is not good for Pull Requests as opening any PR would take 12 minutes to allow any developer to merge his work:</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://guicommits.com/content/images/2024/02/01-integration-tests.png" class="kg-image" alt="How to run pytest in parallel on GitHub actions" loading="lazy" width="843" height="334" srcset="https://guicommits.com/content/images/size/w600/2024/02/01-integration-tests.png 600w, https://guicommits.com/content/images/2024/02/01-integration-tests.png 843w" sizes="(min-width: 720px) 720px"><figcaption>Before: Integration tests take 12min to complete</figcaption></figure><p>This is too slow to merge a PR. The quickest way to improve our time would be to parallelize the integration tests.</p><p>As result <strong>we cut down the time back to ~5min</strong> which is +50% performance improvement with a small effort:</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://guicommits.com/content/images/2024/02/02-integration-tests.png" class="kg-image" alt="How to run pytest in parallel on GitHub actions" loading="lazy" width="833" height="262" srcset="https://guicommits.com/content/images/size/w600/2024/02/02-integration-tests.png 600w, https://guicommits.com/content/images/2024/02/02-integration-tests.png 833w" sizes="(min-width: 720px) 720px"><figcaption>Then: Integration tests broken into 4 workers take a total of 5min to complete</figcaption></figure><p>In this article, I&apos;m going to explain what was done and how you can reproduce it in your environment.</p><p>I know <a href="https://circleci.com/docs/parallelism-faster-jobs/">CircleCI has a feature and guide dedicated to splitting tests that is even easier to use and set up</a>, but this is not common for other CIs. For GitHub Actions we had to implement something similar ourselves.</p><h2 id="%F0%9F%90%A2-how-it-was-before">&#x1F422; How it was before</h2><p>We had two parallel jobs already:</p><ol><li>Unit testing and linting (~2min)</li><li>Integration testing (~13min)</li></ol><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://guicommits.com/content/images/2024/02/03-workflow-overview-before.png" class="kg-image" alt="How to run pytest in parallel on GitHub actions" loading="lazy" width="319" height="231"><figcaption>Before: GitHub Actions Workflow&#xA0;</figcaption></figure><p><strong>For integration testing we need to use MongoDB and Redis</strong> to test our features end to end so we get one container up for each.</p><p>We have some Machine Learning and AWS specific tests, so we decided to exclude them from our Pull Request checks and just test them locally.</p><pre><code class="language-yaml">name: Python lint and test

on:
  pull_request:

permissions:
  contents: read

jobs:
  unit_tests_lint: # &#x1F448; We&apos;re not intested as these are fast enough
    runs-on: ubuntu-latest
    steps: ...

  integration_tests: # &#x1F448;&#x1F3AF; Focus here
    runs-on: ubuntu-latest

    steps:
      - uses: actions/checkout@v3

      - uses: ./.github/workflows/setup_python # &#x1F448; Installs/Caches poetry

      - run: mv ./.github/.env .env # &#x1F448; Default vars for our tests

        # &#x1F447; Allows our docker compose to be used within GitHub container
      - uses: KengoTODA/actions-setup-docker-compose@main
        with:
          version: &quot;1.29.2&quot;

        # &#x1F447; Now we can just get the services we use up
      - run: docker-compose up -d mongo redis

        # &#x1F447; Executes our integration test suite
      - name: Integration Tests without ML
        run: poetry run pytest src/tests/integration -m &apos;not (ml or aws_deps)&apos;
</code></pre><h2 id="%F0%9F%94%80-pytest-split-tests">&#x1F500; Pytest split tests</h2><p>We want somehow to <strong>split the tests into groups</strong> so we can test them in parallel.</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://guicommits.com/content/images/2024/02/04-diag-goal.svg" class="kg-image" alt="How to run pytest in parallel on GitHub actions" loading="lazy" width="401" height="342"><figcaption>Overview: Before vs Goal</figcaption></figure><p>Before explicitly touching the GitHub workflow yaml, we need to teach pytest how to split tests.</p><p>Luckily pytest supports <a href="https://docs.pytest.org/en/7.1.x/how-to/writing_hook_functions.html">hooks</a> we can leverage to achieve our goal.</p><p>For this, we need <code><strong>pytest_collection_modifyitems</strong></code> which we create inside <code>src/tests/integration/conftest.py</code>.</p><p>We need to create the <code>conftest</code> file inside the proper directory otherwise, it might affect all tests (including unit ones which we don&apos;t want to parallelize).</p><p>Picture something as:</p><pre><code>src
&#x2514;&#x2500;&#x2500; tests
    &#x251C;&#x2500;&#x2500; unit
    &#x2502;   &#x251C;&#x2500;&#x2500; test_xxx.py
    &#x2502;   &#x2514;&#x2500;&#x2500; test_yyy.py
    &#x2502;
    &#x2514;&#x2500;&#x2500; integration
        &#x251C;&#x2500;&#x2500; conftest.py # &#x1F448;
        &#x251C;&#x2500;&#x2500; test_xxx.py
        &#x2514;&#x2500;&#x2500; test_yyy.py
</code></pre><p>Let&apos;s start with this stub, and increment slowly:</p><pre><code class="language-py">import pytest

def pytest_collection_modifyitems(
    session: pytest.Session,
    config: pytest.Config,
    items: list[pytest.Item] # &#x1F448; Contains an ordered list of tests pytest found
) -&gt; None:
    selected = [...] # Decide how to split/pick
    deselected = [...] # Decide how to deselect remaining tests

    config.hook.pytest_deselected(items=deselected) # &#x1F448; Marks as deselected
    items[:] = selected # &#x1F448; Overwrites current selection
</code></pre><p>We need something robust that achieves three minor objectives:</p><ol><li>(Local DevExp) Identify when running locally so we don&apos;t split</li><li>(Purpose) Smartly select the proper range for each worker</li><li>(Maintenance) Be easily extensible to X workers</li></ol><p>My approach here was to take environment variables as optional arguments:</p><!--kg-card-begin: html--><table>
<thead>
  <tr>
    <th>Environment variable</th>
    <th>Purpose</th>
      <th>Example value</th>
  </tr>
</thead>
<tbody>
  <tr>
    <td>GITHUB_WORKER_ID</td>
    <td>Holds the current worker id</td>
      <td>1</td>
  </tr>
  <tr>
    <td>GITHUB_TOTAL_WORKERS</td>
    <td>Counts how many workers we have in total</td>
      <td>400</td>
  </tr> 
</tbody>
</table><!--kg-card-end: html--><p>Now we can start filling in these values:</p><pre><code class="language-py">import math
import os
import pytest

def pytest_collection_modifyitems(
    session: pytest.Session,
    config: pytest.Config,
    items: list[pytest.Item]
) -&gt; None:
    # &#x1F447; Make these vars optional so locally we don&apos;t have to set anything
    current_worker = int(os.getenv(&quot;GITHUB_WORKER_ID&quot;, 0)) - 1
    total_workers = int(os.getenv(&quot;GITHUB_TOTAL_WORKERS&quot;, 0))

    # &#x1F447; If there&apos;s no workers we can affirm we won&apos;t split
    if total_workers:
        # &#x1F447; Decide how many tests per worker
        num_tests = len(items)
        matrix_size = math.ceil(num_tests / total_workers)

        # &#x1F447; Select the test range with start and end
        start = current_worker * matrix_size
        end = (current_worker + 1) * matrix_size

        # &#x1F447; Set how many tests are going to be deselected
        deselected_items = items[:start] + items[end:]
        config.hook.pytest_deselected(items=deselected_items)

        # &#x1F447; Set which tests are going to be handled
        items[:] = items[start:end]
        print(f&quot; Executing {start} - {end} tests&quot;)
</code></pre><p>Now you can run your integration tests locally and... Nothing changed which is our goal.</p><h2 id="%F0%9F%90%87-split-pytest-tests-across-github-workers">&#x1F407; Split pytest tests across GitHub workers</h2><p>Whenever we want to spin multiple workers on GitHub we use <code>matrix</code> and pass any values we want. It designs a custom worker for each matrix value.</p><p>This is commonly used to run tests in different OS or python versions. Take this example from <a href="https://github.com/guilatrova/gracy/blob/2ae3717aa4620e114e750b01eb363f49b0f0fd97/.github/workflows/ci.yml#L23-L27">Gracy</a>.</p><p>We run the same suite for Linux only (1) across 4 Python versions. So 1 * 4 = 4 parallel workers running tests.</p><p>For our case, <strong>we just want to assign worker ids</strong> (bare ints) which is fine to do as:</p><pre><code class="language-diff">  integration_tests:
  runs-on: ubuntu-latest
+ strategy:
+     matrix:
+     worker_id: [1, 2, 3, 4]

  steps:
      - uses: actions/checkout@v3

      - uses: ./.github/workflows/setup_python

      - run: mv ./.github/.env .env

      - uses: KengoTODA/actions-setup-docker-compose@main
        with:
          version: &quot;1.29.2&quot;

      - run: docker-compose up -d mongo redis

+     # &#x1F447; We need to set up the env vars from values taken from the matrix
+     - name: Set up worker env vars
+       run: |
+         echo &quot;GITHUB_WORKER_ID=${{matrix.worker_id}}&quot; &gt;&gt; $GITHUB_ENV
+         echo &quot;GITHUB_TOTAL_WORKERS=4&quot; &gt;&gt; $GITHUB_ENV

+     # &#x1F447; Rename to something clearer
-     - name: Integration Tests without ML
+     - name: Integration Tests without ML - Worker. ${{matrix.worker_id}}
        run: poetry run pytest src/tests/integration -m &apos;not (ml or aws_deps)&apos;
</code></pre><p>Note for this case I explicitly defined <code>GITHUB_TOTAL_WORKERS</code> to 4, so our snippet will count all the tests we have and split them evenly for each worker.</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://guicommits.com/content/images/2024/02/05-final-workflow.png" class="kg-image" alt="How to run pytest in parallel on GitHub actions" loading="lazy" width="397" height="426"><figcaption>GitHub Workflow split across 4 workers</figcaption></figure><p>Using this example, each dev that wants to merge a PR will have to wait for ~4m in contrast to ~13min before.</p><p>There&apos;s still space for further improvement though:</p><!--kg-card-begin: html--><table>
<thead>
  <tr>
    <th>Worker</th>
    <th>Time Spent</th>
  </tr>
</thead>
<tbody>
  <tr>
    <td>3</td>
    <td>2m 11s</td>
  </tr>
  <tr>
    <td>2</td>
    <td>2m 42s</td>
  </tr>
  <tr>
    <td>4</td>
    <td>3m 55s</td>
  </tr>
  <tr>
    <td>1</td>
    <td>4m 31s</td>
  </tr>
</tbody>
</table><!--kg-card-end: html--><p>Worker 3 finished early while Worker 1 kept running for another ~2min.</p><p>This means that if we group and better split slow tests for each worker to each worker we can probably get all done in around ~3m 30s.</p><p>This feels like an idea for another blog post though.</p><p>If you learned something new today consider giving me a <a href="https://x.com/intent/user?screen_name=guilatrova">follow on X</a>.</p><p></p>]]></content:encoded></item><item><title><![CDATA[Effective Python Async like a PRO 🐍🔀]]></title><description><![CDATA[See some common mistakes when writing Python Async and learn how to avoid them to increase your code's performance.]]></description><link>https://guicommits.com/effective-python-async-like-a-pro/</link><guid isPermaLink="false">639b1ba1d4ffab040f159efb</guid><category><![CDATA[🐍 Python]]></category><category><![CDATA[async]]></category><dc:creator><![CDATA[Guilherme Latrova]]></dc:creator><pubDate>Thu, 15 Dec 2022 14:23:47 GMT</pubDate><media:content url="https://guicommits.com/content/images/2022/12/cover.png" medium="image"/><content:encoded><![CDATA[<img src="https://guicommits.com/content/images/2022/12/cover.png" alt="Effective Python Async like a PRO &#x1F40D;&#x1F500;"><p>I noticed some people using the async syntax <strong>without knowing what they were doing</strong>.</p><p>First, they think async is parallel which is not true as <a href="https://guicommits.com/async-python-in-real-life/">I explain in another article</a>.</p><p>Then they write code that doesn&apos;t take any advantage of Python async. In other words, <strong>they write sync code with async syntax</strong>.</p><p>The goal of this post is to point out these performance issues and help you benefit the most from async code.</p><h2 id="%F0%9F%A4%94-when-to-use-python-async">&#x1F914; When to use Python Async</h2><p><strong>Async only makes sense if you&apos;re doing IO.</strong></p><p>There&apos;s ZERO benefit in using async to stuff like this that is CPU-bound:</p><pre><code class="language-py">import asyncio


async def sum_two_numbers_async(n1: int, n2: int) -&gt; int:
    return n1 + n2


async def main():
    await sum_two_numbers_async(2, 2)
    await sum_two_numbers_async(4, 4)


asyncio.run(main())
</code></pre><p>Your code might even get slower by doing that due to the Event Loop.</p><p>That&apos;s because <strong>Python async only optimizes IDLE time</strong>!</p><figure class="kg-card kg-embed-card"><blockquote class="twitter-tweet"><p lang="fr" dir="ltr">Sync vs Async vs Parallel <a href="https://t.co/hZEXfkKmU0">pic.twitter.com/hZEXfkKmU0</a></p>&#x2014; Gui Latrova (@guilatrova) <a href="https://twitter.com/guilatrova/status/1602647449205547008?ref_src=twsrc%5Etfw">December 13, 2022</a></blockquote>
<script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>
</figure><p>If these concepts are new to you, read this article first:</p><figure class="kg-card kg-bookmark-card"><a class="kg-bookmark-container" href="https://guicommits.com/async-python-in-real-life/"><div class="kg-bookmark-content"><div class="kg-bookmark-title">Async python in real life &#x1F40D;&#x1F500;</div><div class="kg-bookmark-description">Await Async Python applied with real examples. I show a slow API server and a slow database, and explain why async is not parallel but concurrent....</div><div class="kg-bookmark-metadata"><img class="kg-bookmark-icon" src="https://guicommits.com/favicon.png" alt="Effective Python Async like a PRO &#x1F40D;&#x1F500;"><span class="kg-bookmark-author">Gui Commits</span><span class="kg-bookmark-publisher">Guilherme Latrova</span></div></div><div class="kg-bookmark-thumbnail"><img src="https://guicommits.com/content/images/2022/06/Blog-Post-Python-Async.png" alt="Effective Python Async like a PRO &#x1F40D;&#x1F500;"></div></a></figure><p><strong>IO-bound operations are related to reading/writing operations.</strong></p><p>A good example would be:</p><ul><li>Requesting some data from HTTP</li><li>Reading/Writing some <code>json</code>/<code>txt</code> file</li><li>Reading data from a database</li></ul><p>&#x1F446; All these operations consist of <em>waiting for the data to be available</em>.</p><p>While the data is UNAVAILABLE the EVENT LOOP does something else.</p><p>This is <em>Concurrency</em>.</p><p><strong>NOT</strong> <em><s>Parallelism</s></em>.</p><h2 id="%F0%9F%96%BC%EF%B8%8F-python-async-await-example">&#x1F5BC;&#xFE0F; Python Async Await Example</h2><p>Let&apos;s set up a scenario to get started.</p><p>We need to build a simple Pokedex that queries for 3 pokemons simultaneously (so we benefit from async).</p><p>After querying the pokemons we&apos;re going to build an object with them, so:</p><!--kg-card-begin: markdown--><table>
<thead>
<tr>
<th><strong>Step</strong></th>
<th><strong>Operation type</strong></th>
</tr>
</thead>
<tbody>
<tr>
<td>Query <a href="https://pokeapi.co">pokeapi.co</a></td>
<td>IO-bound</td>
</tr>
<tr>
<td>Build an object holding the data</td>
<td>CPU-bound</td>
</tr>
</tbody>
</table>
<!--kg-card-end: markdown--><p>I&apos;ll be using <a href="https://docs.pydantic.dev/"><code>pydantic</code></a> for model parsing and <a href="https://www.python-httpx.org/async/"><code>httpx</code></a> for HTTP as its syntax is compatible with <a href="https://requests.readthedocs.io/en/latest/"><code>requests</code></a>.</p><h2 id="%F0%9F%90%8C-use-python-async-and-await">&#x1F40C; Use Python <code>async</code> and <code>await</code></h2><p>Let&apos;s start with the basic scenario that everybody writes and proudly says: &quot;the code is async&quot;.</p><p>Take your time to visualize:</p><ul><li>The model class</li><li>The <code>parse_pokemon</code> function (CPU-bound)</li><li>The <code>get_pokemon</code> function (IO-bound)</li><li>The <code>get_all</code> function</li></ul><pre><code class="language-py">import asyncio
from datetime import timedelta
import time
import httpx
from pydantic import BaseModel

class Pokemon(BaseModel): # &#x1F448; Defines model to parse pokemon
    name: str
    types: list[str]

def parse_pokemon(pokemon_data: dict) -&gt; Pokemon: # &#x1F448; CPU-bound operation
    print(&quot;&#x1F504; Parsing pokemon&quot;)

    poke_types = []
    for poke_type in pokemon_data[&quot;types&quot;]:
        poke_types.append(poke_type[&quot;type&quot;][&quot;name&quot;])

    return Pokemon(name=pokemon_data[&apos;name&apos;], types=poke_types)

async def get_pokemon(name: str) -&gt; dict | None: # &#x1F448; IO-bound operation
    async with httpx.AsyncClient() as client:
        print(f&quot;&#x1F50D; Querying for &apos;{name}&apos;&quot;)
        resp = await client.get(f&quot;https://pokeapi.co/api/v2/pokemon/{name}&quot;)
        print(f&quot;&#x1F64C; Got data for &apos;{name}&apos;&quot;)

        try:
            resp.raise_for_status()

        except httpx.HTTPStatusError as err:
            if err.response.status_code == 404:
                return None

            raise

        else:
            return resp.json()

async def get_all(*names: str): # &#x1F448; Async
    started_at = time.time()

    for name in names: # &#x1F448; Iterates over all names
        if data := await get_pokemon(name): # &#x1F448; Invokes async function
            pokemon = parse_pokemon(data)
            print(f&quot;&#x1F481; {pokemon.name} is of type(s) {&apos;,&apos;.join(pokemon.types)}&quot;)
        else:
            print(f&quot;&#x274C; No data found for &apos;{name}&apos;&quot;)

    finished_at = time.time()
    elapsed_time = finished_at - started_at
    print(f&quot;&#x23F2;&#xFE0F; Done in {timedelta(seconds=elapsed_time)}&quot;)


POKE_NAMES = [&quot;blaziken&quot;, &quot;pikachu&quot;, &quot;lugia&quot;, &quot;bad_name&quot;]
asyncio.run(get_all(*POKE_NAMES))
</code></pre><p>This produces the following output:</p><pre><code class="language-py">&#x1F50D; Querying for &apos;blaziken&apos;
&#x1F64C; Got data for &apos;blaziken&apos;
&#x1F504; Parsing pokemon
&#x1F481; blaziken is of type(s) fire,fighting

&#x1F50D; Querying for &apos;pikachu&apos;
&#x1F64C; Got data for &apos;pikachu&apos;
&#x1F504; Parsing pokemon
&#x1F481; pikachu is of type(s) electric

&#x1F50D; Querying for &apos;lugia&apos;
&#x1F64C; Got data for &apos;lugia&apos;
&#x1F504; Parsing pokemon
&#x1F481; lugia is of type(s) psychic,flying

&#x1F50D; Querying for &apos;bad_name&apos;
&#x1F64C; Got data for &apos;bad_name&apos;
&#x274C; No data found for &apos;bad_name&apos;

&#x23F2;&#xFE0F; Done in 0:00:02.152331
</code></pre><p>This is bad usage for this scenario.</p><p>If you analyze the output you&apos;ll understand that:</p><p><strong>We&apos;re requesting one HTTP resource at a time</strong> thus it doesn&apos;t matter if we use async or not.</p><p>Let&apos;s fix that! &#x1F9D1;&#x200D;&#x1F3ED;</p><h2 id="use-python-asynciocreatetask-and-asynciogather">Use Python <code>asyncio.create_task</code> and <code>asyncio.gather</code></h2><p>If you want 2 or more functions to run concurrently, you need <code>asyncio.create_task</code>.</p><p>Creating a task triggers the async operation, and it needs to be awaited at some point.</p><p>For example:</p><pre><code class="language-py">task = create_task(my_async_function(&apos;arg1&apos;))
result = await task
</code></pre><p>As we&apos;re creating many tasks, we need <code>asyncio.gather</code> which awaits all tasks to be done.</p><p>This is our code now (check the <code>get_all</code> function):</p><pre><code class="language-py">import asyncio
from datetime import timedelta
import time
import httpx
from pydantic import BaseModel

class Pokemon(BaseModel):
    name: str
    types: list[str]

def parse_pokemon(pokemon_data: dict) -&gt; Pokemon:
    print(&quot;&#x1F504; Parsing pokemon&quot;)

    poke_types = []
    for poke_type in pokemon_data[&quot;types&quot;]:
        poke_types.append(poke_type[&quot;type&quot;][&quot;name&quot;])

    return Pokemon(name=pokemon_data[&apos;name&apos;], types=poke_types)

async def get_pokemon(name: str) -&gt; dict | None:
    async with httpx.AsyncClient() as client:
        print(f&quot;&#x1F50D; Querying for &apos;{name}&apos;&quot;)
        resp = await client.get(f&quot;https://pokeapi.co/api/v2/pokemon/{name}&quot;)
        print(f&quot;&#x1F64C; Got data for &apos;{name}&apos;&quot;)

        try:
            resp.raise_for_status()

        except httpx.HTTPStatusError as err:
            if err.response.status_code == 404:
                return None

            raise

        else:
            return resp.json()

async def get_all(*names: str):
    started_at = time.time()

    # &#x1F447; Create tasks, so we start requesting all of them concurrently
    tasks = [asyncio.create_task(get_pokemon(name)) for name in names]

    # &#x1F447; Await ALL
    results = await asyncio.gather(*tasks)

    for result in results:
        if result:
            pokemon = parse_pokemon(result)
            print(f&quot;&#x1F481; {pokemon.name} is of type(s) {&apos;,&apos;.join(pokemon.types)}&quot;)
        else:
            print(f&quot;&#x274C; No data found for...&quot;)

    finished_at = time.time()
    elapsed_time = finished_at - started_at
    print(f&quot;&#x23F2;&#xFE0F; Done in {timedelta(seconds=elapsed_time)}&quot;)


POKE_NAMES = [&quot;blaziken&quot;, &quot;pikachu&quot;, &quot;lugia&quot;, &quot;bad_name&quot;]
asyncio.run(get_all(*POKE_NAMES))
</code></pre><p>And this is the output:</p><pre><code class="language-py">&#x1F50D; Querying for &apos;blaziken&apos;
&#x1F50D; Querying for &apos;pikachu&apos;
&#x1F50D; Querying for &apos;lugia&apos;
&#x1F50D; Querying for &apos;bad_name&apos;

&#x1F64C; Got data for &apos;lugia&apos;
&#x1F64C; Got data for &apos;blaziken&apos;
&#x1F64C; Got data for &apos;pikachu&apos;
&#x1F64C; Got data for &apos;bad_name&apos;

&#x1F504; Parsing pokemon
&#x1F481; blaziken is of type(s) fire,fighting
&#x1F504; Parsing pokemon
&#x1F481; pikachu is of type(s) electric
&#x1F504; Parsing pokemon
&#x1F481; lugia is of type(s) psychic,flying
&#x274C; No data found for...

&#x23F2;&#xFE0F; Done in 0:00:00.495780
</code></pre><p><strong>We dropped from ~2s to 500ms</strong> just by using Python async <em>correctly</em>.</p><p>Note how:</p><ul><li>We query everything right away in the order passed (e.g. <code>blaziken</code> first)</li><li>We retrieve the data in a random order as they become available (e.g. Now <code>lugia</code> comes first)</li><li>We parse the data in sequence (it&apos;s CPU-bound anyway)</li></ul><h2 id="use-python-asyncioascompleted">Use Python <code>asyncio.as_completed</code></h2><p>There will be moments when you don&apos;t have to await for every single task to be processed right away.</p><p>That&apos;s similar to our scenario, we can start parsing the data right after the first data becomes available.</p><p>We do this by using <code>asyncio.as_completed</code> which returns a generator with completed coroutines:</p><pre><code class="language-py">import asyncio
from datetime import timedelta
import time
import httpx
from pydantic import BaseModel

class Pokemon(BaseModel):
    name: str
    types: list[str]

def parse_pokemon(pokemon_data: dict) -&gt; Pokemon:
    print(f&quot;&#x1F504; Parsing pokemon &apos;{pokemon_data[&apos;name&apos;]}&apos;&quot;)

    poke_types = []
    for poke_type in pokemon_data[&quot;types&quot;]:
        poke_types.append(poke_type[&quot;type&quot;][&quot;name&quot;])

    return Pokemon(name=pokemon_data[&apos;name&apos;], types=poke_types)

async def get_pokemon(name: str) -&gt; dict | None:
    async with httpx.AsyncClient() as client:
        print(f&quot;&#x1F50D; Querying for &apos;{name}&apos;&quot;)
        resp = await client.get(f&quot;https://pokeapi.co/api/v2/pokemon/{name}&quot;)
        print(f&quot;&#x1F64C; Got data for &apos;{name}&apos;&quot;)

        try:
            resp.raise_for_status()

        except httpx.HTTPStatusError as err:
            if err.response.status_code == 404:
                return None

            raise

        else:
            return resp.json()

async def get_all(*names: str):
    started_at = time.time()

    tasks = [asyncio.create_task(get_pokemon(name)) for name in names]

    # &#x1F447; Process the tasks individually as they become available
    for coro in asyncio.as_completed(tasks):
        result = await coro # &#x1F448; You still need to await

        if result:
            pokemon = parse_pokemon(result)
            print(f&quot;&#x1F481; {pokemon.name} is of type(s) {&apos;,&apos;.join(pokemon.types)}&quot;)
        else:
            print(f&quot;&#x274C; No data found for...&quot;)

    finished_at = time.time()
    elapsed_time = finished_at - started_at
    print(f&quot;&#x23F2;&#xFE0F; Done in {timedelta(seconds=elapsed_time)}&quot;)


POKE_NAMES = [&quot;blaziken&quot;, &quot;pikachu&quot;, &quot;lugia&quot;, &quot;bad_name&quot;]
asyncio.run(get_all(*POKE_NAMES))
</code></pre><p>The benefit is not easily visible:</p><pre><code class="language-py">&#x1F50D; Querying for &apos;blaziken&apos;
&#x1F50D; Querying for &apos;pikachu&apos;
&#x1F50D; Querying for &apos;lugia&apos;
&#x1F50D; Querying for &apos;bad_name&apos;

&#x1F64C; Got data for &apos;blaziken&apos;
&#x1F504; Parsing pokemon &apos;blaziken&apos;
&#x1F481; blaziken is of type(s) fire,fighting
&#x1F64C; Got data for &apos;bad_name&apos;
&#x1F64C; Got data for &apos;lugia&apos;
&#x1F64C; Got data for &apos;pikachu&apos;
&#x274C; No data found for...
&#x1F504; Parsing pokemon &apos;lugia&apos;
&#x1F481; lugia is of type(s) psychic,flying
&#x1F504; Parsing pokemon &apos;pikachu&apos;
&#x1F481; pikachu is of type(s) electric

&#x23F2;&#xFE0F; Done in 0:00:00.316266
</code></pre><p>We still query everything at once (which is good).</p><p>Note how the order is completely mixed up though.</p><p>It means that Python processed the data as soon as it got available, giving enough time for other requests to finish later.</p><p><strong>You&apos;ll become a better developer</strong> if you understand when/why to use <code>async</code>, <code>await</code>, <code>create_task</code>, <code>gather</code>, and <code>as_completed</code>.</p><p>This is part of the book I&apos;m currently writing. If you want to stop writing &apos;OK-code&apos; that works and <strong>start writing &apos;GREAT-code&apos;</strong>, you should consider getting your copy before I finish writing it (I&apos;ll increase the price once it&apos;s released).</p><p>Get your copy here:</p><figure class="kg-card kg-bookmark-card"><a class="kg-bookmark-container" href="https://guilatrova.gumroad.com/l/python-like-a-pro"><div class="kg-bookmark-content"><div class="kg-bookmark-title">Python Like a PRO &#x1F40D;&#x1F4DA; Book</div><div class="kg-bookmark-description">&#x26A0;&#xFE0F;&#x1F4DA; This book is still under development (that&#x2019;s why it&#x2019;s so cheap right now, the price will increase once all chapters are published).You need to know what the hell you&#x2019;re doing &#x1F525;&#x1F40D;Python is one of the most flexible languages I have had contact with.Everything too flexible enhances the odds of ba&#x2026;</div><div class="kg-bookmark-metadata"><img class="kg-bookmark-icon" src="https://public-files.gumroad.com/variants/i88lhvlps18emf7eoguxxu51gdzv/4ec519eb32080d4ff1ef08cba157dc2ac7dab092fa26aeca54e8e2b8f31f9a63" alt="Effective Python Async like a PRO &#x1F40D;&#x1F500;"><span class="kg-bookmark-author">Gumroad</span></div></div><div class="kg-bookmark-thumbnail"><img src="https://public-files.gumroad.com/variants/baq69b30tip3tygvo8wg7s7hm7gd/3298c3eb001bbed90f1d616da66708480096a0a1b6e81bd4f8a2d6e9b831d301" alt="Effective Python Async like a PRO &#x1F40D;&#x1F500;"></div></a></figure><h2 id="%F0%9F%94%80-real-life-scenario-using-async-io">&#x1F500; Real-life scenario using Async IO</h2><p>I&apos;m currently working for another SF startup: <a href="https://silk.security/">Silk Security</a> and we rely a lot on third-party integrations and their APIs.</p><p>We query a lot of data, and we need to do it as fast as possible.</p><p>For example, we query <a href="https://snyk.docs.apiary.io/">Snyk&apos;s API</a> to collect code vulnerabilities.</p><p><a href="https://snyk.io/">Snyk&apos;s</a> data is composed of <strong>Organizations</strong> that contain many <strong>Projects</strong> that contain many <strong>Issues</strong>.</p><p>It means that we need to list all projects and organizations before getting any issues.</p><p>So picture it as:</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://guicommits.com/content/images/2022/12/snyk-overview.svg" class="kg-image" alt="Effective Python Async like a PRO &#x1F40D;&#x1F500;" loading="lazy" width="798" height="541"><figcaption>Snyk Flow Overview</figcaption></figure><p>Note how many queries we need to do! We do them concurrently.</p><p>We need to be careful with rate limiting issues that the API may throw. To resolve that we limit the number of queries we do in a single shot, and we start running some processing before querying for more data.</p><p>This allows us to gain time and don&apos;t hit any rate limits imposed by the API while.</p><p>See a <em><strong>redacted</strong></em> code snippet from a real project running in production:</p><pre><code class="language-py">def _iter_grouped(self, issues: list[ResultType], group_count: int):
    group_count = min([len(issues), group_count])

    return zip(*[iter(issues)] * group_count)


async def get_issue_details(self):
    ...

    # NOTE: We need to be careful here, we can&apos;t create tasks for every issue or Snyk will raise 449
    # Instead, let&apos;s do it in chunks, and let&apos;s yield as it&apos;s done, so we can spend some time processing it
    # and we can query Snyk again.
    chunk_count = 4 # &#x1F448; Limit to 4 queries at a time
    coro: Awaitable[tuple[ResultType | None]]
    for issues in self._iter_grouped(issues, chunk_count):
        tasks = [asyncio.create_task(self._get_data(project, issue)) for issue in issues]

        for coro in asyncio.as_completed(tasks):
            issue, details = await coro

            yield issue, details
</code></pre><p>We can represent it as:</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://guicommits.com/content/images/2022/12/snyk-rate-limit.svg" class="kg-image" alt="Effective Python Async like a PRO &#x1F40D;&#x1F500;" loading="lazy" width="393" height="663"><figcaption>Generators + <code>asyncio.as_completed</code> flow</figcaption></figure><p>If you learned something new today consider giving me a <a href="https://twitter.com/intent/user?screen_name=guilatrova">follow on Twitter</a>. I frequently share Python content and cool projects. DMs are open to any feedback.</p>]]></content:encoded></item><item><title><![CDATA[Pyrun: Execute Python inside your Twitter, Facebook, Linkedin]]></title><description><![CDATA[Pyrun integrates a small IDE window in your Twitter page, so you can copy and run Python code inside Twitter]]></description><link>https://guicommits.com/pyrun-run-python-from-tweets/</link><guid isPermaLink="false">6310d789d4ffab040f159e71</guid><category><![CDATA[react]]></category><category><![CDATA[extensions]]></category><category><![CDATA[🐍 Python]]></category><dc:creator><![CDATA[Guilherme Latrova]]></dc:creator><pubDate>Thu, 01 Sep 2022 22:04:10 GMT</pubDate><media:content url="https://guicommits.com/content/images/2022/09/Blog-Post-Pyrun-Project-1.png" medium="image"/><content:encoded><![CDATA[<figure class="kg-card kg-image-card"><img src="https://guicommits.com/content/images/2022/09/pyrun-logo.gif" class="kg-image" alt="Pyrun: Execute Python inside your Twitter, Facebook, Linkedin" loading="lazy" width="240" height="240"></figure><figure class="kg-card kg-image-card"><a href="https://www.producthunt.com/posts/pyrun-2"><img src="https://api.producthunt.com/widgets/embed-image/v1/featured.svg?post_id=358643&amp;theme=light" class="kg-image" alt="Pyrun: Execute Python inside your Twitter, Facebook, Linkedin" loading="lazy"></a></figure><figure class="kg-card kg-bookmark-card"><a class="kg-bookmark-container" href="https://chrome.google.com/webstore/detail/pyrun/mpkfgkeapfgoamnbdopmdlgilhjhiini?ref=producthunt"><div class="kg-bookmark-content"><div class="kg-bookmark-title">Pyrun</div><div class="kg-bookmark-description">Extracts data from tweets and runs Python code inside Twitter</div><div class="kg-bookmark-metadata"><img class="kg-bookmark-icon" src="https://ssl.gstatic.com/chrome/webstore/images/icon_144px.png" alt="Pyrun: Execute Python inside your Twitter, Facebook, Linkedin"><span class="kg-bookmark-author">Chrome Web Store</span></div></div><div class="kg-bookmark-thumbnail"><img src="https://lh3.googleusercontent.com/kFGJM2sNuSyI684_ELFGhARJvxPP29NxP3U5qLJ73yJ2urwy4ULIiEuAxa_mC24oB7KghTe1liBAP66aePLDBfPW6g=w128-h128-e365-rj-sc0x00ffffff" alt="Pyrun: Execute Python inside your Twitter, Facebook, Linkedin"></div></a></figure><img src="https://guicommits.com/content/images/2022/09/Blog-Post-Pyrun-Project-1.png" alt="Pyrun: Execute Python inside your Twitter, Facebook, Linkedin"><p><strong>Stack:</strong> Typescript, React, Browser Extension, Pyodide</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://guicommits.com/content/images/2022/09/pyrun-with-snappify.gif" class="kg-image" alt="Pyrun: Execute Python inside your Twitter, Facebook, Linkedin" loading="lazy" width="1358" height="725"><figcaption>Running Python inside Twitter with a single click</figcaption></figure><hr><p>&#x1F468;&#x1F3FB;&#x200D;&#x1F4BB; Pyrun integrates a small IDE window in your feed, so you can execute Python code with a single button!</p><h2 id="%F0%9F%A4%94-why-execute-python-inside-twitter">&#x1F914; Why execute Python inside Twitter?</h2><p>At this point probably you might know I&apos;m a huge fan of <a href="https://guicommits.com/tag/interactive/">making learning interactive and engaging</a>.</p><p>That&apos;s how my brain works. <strong>I see then I try then I understand.</strong></p><p>I believe that shallow consumption without action prevents us from learning. <strong>Reading without practice is not good enough.</strong></p><p>Here&apos;s where this extension comes in, I frequently write tweets that teach some Python:</p><figure class="kg-card kg-embed-card"><blockquote class="twitter-tweet"><p lang="en" dir="ltr">If you have a list and want to take any random value from it in Python &#x1F40D;<br><br>&#x1F449; Use choice<br><br>Easy! <a href="https://twitter.com/hashtag/pyrun?src=hash&amp;ref_src=twsrc%5Etfw">#pyrun</a> <a href="https://t.co/LvlEWD9rxe">pic.twitter.com/LvlEWD9rxe</a></p>&#x2014; Gui Latrova (@guilatrova) <a href="https://twitter.com/guilatrova/status/1551522671761825792?ref_src=twsrc%5Etfw">July 25, 2022</a></blockquote>
<script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>
</figure><h2 id="%F0%9F%91%A4-whos-this-for">&#x1F464; Who&apos;s this for?</h2><p>It&apos;s for <strong>content creators who believe talk is cheap and want to improve their engagement with their audience</strong>.</p><p>It&apos;s for the <strong>avid learners who want to execute, edit and feel the code!</strong></p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://guicommits.com/content/images/2022/09/pyrun-demo.gif" class="kg-image" alt="Pyrun: Execute Python inside your Twitter, Facebook, Linkedin" loading="lazy" width="1279" height="999"><figcaption>Pyrun Working Demo</figcaption></figure><h2 id="%E2%9C%8D%EF%B8%8F-im-a-creator-how-can-i-produce-python-tweets-that-my-audience-can-execute">&#x270D;&#xFE0F; I&apos;m a creator, how can I produce Python tweets that my audience can execute?</h2><p>&#x1F481;&#x200D;&#x2642;&#xFE0F; Easy:</p><ul><li><strong>Post an image</strong> from <a href="https://carbon.now.sh/">carbon</a>, <a href="https://snappify.io/">snappify</a>, or anything else as you normally would</li><li><strong>Put the raw code as your image&apos;s ALT</strong> keeping all white spaces, comments, etc.</li><li>When posting make sure to <strong>include the <code>#pyrun</code> tag</strong> anywhere in your tweet</li></ul><p>That&apos;s it. People who don&apos;t use the extension can still consume your content as before (static boring images), and extension users can now EXECUTE IT!</p><h1 id="%F0%9F%A7%90-how-to-run-python-inside-other-pages">&#x1F9D0; How to run Python inside other pages?</h1><p>The proposal is simple:</p><p><strong>Tweets and Posts teaching Python can be executed with a single click. No installs. No manual typing.</strong></p><p>This idea struck me randomly during a night stay in S&#xE3;o Paulo at 4 am.</p><p>What do you do when you have a stupid idea that you don&apos;t even know if it works?</p><p>You spend the next hours trying to implement it of course!</p><figure class="kg-card kg-embed-card"><blockquote class="twitter-tweet"><p lang="en" dir="ltr">This is a test for a project I&apos;m currently building at 4 am (Yes).<br><br>Please, ignore it and keep scrolling. &#x1F601;<br><br>Thank you! <a href="https://twitter.com/hashtag/pyrun?src=hash&amp;ref_src=twsrc%5Etfw">#pyrun</a> <a href="https://t.co/oHPXZLlzuB">pic.twitter.com/oHPXZLlzuB</a></p>&#x2014; Gui Latrova (@guilatrova) <a href="https://twitter.com/guilatrova/status/1547848851880493056?ref_src=twsrc%5Etfw">July 15, 2022</a></blockquote>
<script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>
</figure><h2 id="%F0%9F%91%93-reading-the-python-code">&#x1F453; Reading the Python Code</h2><p>The first challenge was: How to read the Python code?</p><p>I thought it would be easy to use OCR to read images with monospace fonts. Unfortunately, it didn&apos;t work.</p><p>The OCR tools I used had to be in Javascript since I&apos;m injecting code into the browser. Such tools aren&apos;t built to &quot;read code&quot;, but actual words and sentences.</p><p>So it&apos;s extremely tricky for these tools to recognize: <code>def func:</code> because it doesn&apos;t make much sense when read by a human.</p><p>Let&apos;s not even mention spaces, tabs, and line breaks.</p><p>The best I could do is extract code from the ALT which preserves the original content and all characters.</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://guicommits.com/content/images/2022/09/tweet-alt-sample.png" class="kg-image" alt="Pyrun: Execute Python inside your Twitter, Facebook, Linkedin" loading="lazy" width="775" height="569" srcset="https://guicommits.com/content/images/size/w600/2022/09/tweet-alt-sample.png 600w, https://guicommits.com/content/images/2022/09/tweet-alt-sample.png 775w" sizes="(min-width: 720px) 720px"><figcaption>Tweet&apos;s ALT Example</figcaption></figure><p>Next I had to:</p><ul><li>Find relevant tweets</li><li>Read image alt&apos;s</li></ul><p>to be able to capture it.</p><h2 id="%F0%9F%95%B5%EF%B8%8F-getting-relevant-tweets">&#x1F575;&#xFE0F; Getting relevant tweets</h2><p>Finding the correct tweet seemed hard at first. How can I know the content (1) is about Python, (2) has code, and that (3) the image&apos;s alt is properly set?</p><p>The best approach I could do is to enforce tweets to have some specific tag <code>#pyrun</code>. This is both <strong>a technical limitation and a feature.</strong></p><p>Now your audience <a href="https://twitter.com/hashtag/pyrun?src=hashtag_click">can filter every tweet that can be executed</a>!</p><p>I wrote an xpath query to look for:</p><pre><code class="language-js">const TARGET_TWEET_TAG = &quot;#pyrun&quot;;
const xpath = `//a[text()=&apos;${TARGET_TWEET_TAG}&apos;]`;
</code></pre><p>I also added some simple styling to make it stand out and appended an execute button right after it.</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://guicommits.com/content/images/2022/09/tweet-standout.png" class="kg-image" alt="Pyrun: Execute Python inside your Twitter, Facebook, Linkedin" loading="lazy" width="580" height="399"><figcaption>Tweet standing out with #pyrun tag</figcaption></figure><p>Then it worked and it was awesome!</p><p>&quot;I nailed it&quot; - I thought...</p><p>Until I realized that Twitter loads tweets as you scroll. So I need to keep &quot;listening&quot; for new tweets to inject my code into.</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://guicommits.com/content/images/2022/09/scroll-find-new-tweets.gif" class="kg-image" alt="Pyrun: Execute Python inside your Twitter, Facebook, Linkedin" loading="lazy" width="1279" height="999"><figcaption>Finding new tweets as you scroll</figcaption></figure><p>Once I figured it out it was easy.</p><p>You can notice that as you scroll some logs are being emitted counting how many tweets it finds.</p><p>It works the same for other tools:</p><figure class="kg-card kg-embed-card"><blockquote class="twitter-tweet"><p lang="en" dir="ltr">Pyrun running on Facebook and Linkedin example: <a href="https://t.co/MomugKMoPq">pic.twitter.com/MomugKMoPq</a></p>&#x2014; Gui Latrova (@guilatrova) <a href="https://twitter.com/guilatrova/status/1570451522277998593?ref_src=twsrc%5Etfw">September 15, 2022</a></blockquote>
<script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>
</figure><h1 id="%F0%9F%90%8D-run-pyodide-inside-a-chrome-extension">&#x1F40D; Run Pyodide inside a Chrome Extension</h1><p>This issue took me 2 months due to my lack of experience building Chrome Extensions &#x1F648; (Hey, better late than never).</p><p>I learned that I can&apos;t just follow Pyodide&apos;s tutorial to get it to work. It fails miserably. Installing the npm package didn&apos;t work either.</p><p>Then I decided to find who else did something similar before and I stumbled on <a href="https://chrome.google.com/webstore/detail/swindle/fbehneehbojmfglgfccgkaegpnnaagkk?hl=pt-PT&amp;gl=001&amp;authuser=2">Swindle</a>.</p><p>Swindle is an open-source extension that allows you to run Python (powered by Pyodide) inside the DevTools.</p><p>I noticed it has its own &quot;<a href="https://github.com/Mario2334/swindle/blob/master/ide/public/javascript/pyodide/pyodide.js">Pyodide bootstrap flow</a>&quot; and I realized I would probably have to do the same...</p><p>It was not a pleasant experience. I opened <a href="https://github.com/pyodide/pyodide/blob/main/src/js/pyodide.ts">the original Pyodide&apos;s file on Github</a> and replicated every line, recompiled, repackaged, and retested it.</p><p>It&apos;s surprising that it took me only 2 months &#x1F605;.</p><p>Then I learned that the recent Chrome manifest v3 forbids the usage of Javascript&apos;s <code>eval</code> inside extensions and guess what? I also found out that Pyodide uses Javascript&apos;s eval.</p><p>&#x1F62E;&#x200D;&#x1F4A8; That was a nightmare. It cost me so much to get it working and then this...</p><figure class="kg-card kg-embed-card"><blockquote class="twitter-tweet"><p lang="en" dir="ltr">&quot;i hate programming&quot; meme based upon <a href="https://twitter.com/Nasser_Junior?ref_src=twsrc%5Etfw">@Nasser_Junior</a> comic. <a href="https://t.co/KrpAmHL644">pic.twitter.com/KrpAmHL644</a></p>&#x2014; nixCraft (@nixcraft) <a href="https://twitter.com/nixcraft/status/1332983389943517185?ref_src=twsrc%5Etfw">November 29, 2020</a></blockquote>
<script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>
</figure><p>Looks like the manifest v3 requires you to run <code>eval</code> code inside an iframed sandbox.</p><p>That&apos;s how you can imagine it:</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://guicommits.com/content/images/2022/09/sandbox-communication-example.png" class="kg-image" alt="Pyrun: Execute Python inside your Twitter, Facebook, Linkedin" loading="lazy" width="955" height="413" srcset="https://guicommits.com/content/images/size/w600/2022/09/sandbox-communication-example.png 600w, https://guicommits.com/content/images/2022/09/sandbox-communication-example.png 955w" sizes="(min-width: 720px) 720px"><figcaption>Main window and sandbox communicating</figcaption></figure><p>Effectively, that&apos;s what you can find in your DOM if you install the extension and inspect the page:</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://guicommits.com/content/images/2022/09/sandbox-dom.png" class="kg-image" alt="Pyrun: Execute Python inside your Twitter, Facebook, Linkedin" loading="lazy" width="955" height="355" srcset="https://guicommits.com/content/images/size/w600/2022/09/sandbox-dom.png 600w, https://guicommits.com/content/images/2022/09/sandbox-dom.png 955w" sizes="(min-width: 720px) 720px"><figcaption>IDE and Sandbox in DOM</figcaption></figure><p>The <code>&lt;div id=&quot;pyrun-container&quot;&gt;</code> holds a React rendered IDE while the <code>&lt;iframe id=&quot;pyrun-sandbox&quot;&gt;</code> is an invisible element responsible only for listening to <a href="https://developer.mozilla.org/en-US/docs/Web/API/Window/postMessage">posted messages</a>, executing the code, and emitting outputs back to the main window.</p><h2 id="%F0%9F%A7%91%E2%80%8D%F0%9F%92%BB-editor-area-and-output-console">&#x1F9D1;&#x200D;&#x1F4BB; Editor area and Output console</h2><p>It was simple to set up an editor. I just had to install <a href="https://github.com/securingsincity/react-ace"><code>react-ace</code></a>.</p><p>I didn&apos;t have the same luck with the console though.</p><p>I tried many but none of them was simple enough for my needs: a styled output that I can jot many lines.</p><p>I ended up building a simple div that keeps adding lines as <code>&lt;p&gt;</code> inside. It works fine:</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://guicommits.com/content/images/2022/09/simple-output.gif" class="kg-image" alt="Pyrun: Execute Python inside your Twitter, Facebook, Linkedin" loading="lazy" width="1081" height="561"><figcaption>Output example</figcaption></figure><h2 id="%F0%9F%A7%91%E2%80%8D%F0%9F%92%BB-what-about-the-future">&#x1F9D1;&#x200D;&#x1F4BB; What about the future?</h2><p>Content creators are everywhere and the Social media tools share some &quot;common features&quot;.<br>They allow posts with tags and images and images with ALT.<br>This is enough to get this extension up and running anywhere.</p><p>Take LinkedIn as an example:</p><figure class="kg-card kg-embed-card"><blockquote class="twitter-tweet"><p lang="en" dir="ltr">What if I could do the same I did for Twitter inside Linkedin? &#x1F40D;<br><br>Both posts with images +ALT and tags &#x1F914;<br><br>&#x1F937;&#x200D;&#x2642;&#xFE0F; The hardest work is already implemented: An extension holding an IDE that runs Python<br><br>&#x1F977; I found out I can &quot;hack&quot; the HTML to allow higher ALT length. (and it works!) <a href="https://t.co/lWRZbr4Bco">pic.twitter.com/lWRZbr4Bco</a></p>&#x2014; Gui Latrova (@guilatrova) <a href="https://twitter.com/guilatrova/status/1566018184221585409?ref_src=twsrc%5Etfw">September 3, 2022</a></blockquote>
<script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>
</figure><p>I imagine supporting more Social Media tools depending on the audience feedback.</p><p>Today this extension supports just Python, but <strong>it should be quite easy to support Javascript</strong> since it requires no setup/preparation!</p><p>Make sure to <a href="https://twitter.com/intent/user?screen_name=guilatrova">follow me on Twitter</a> to know if any of these will ever happen.</p><h2 id="%F0%9F%8C%9F-tips">&#x1F31F; Tips</h2><p>I never built a chrome extension before. I had many challenges and wanted to develop one fast.</p><p><strong>I abused on many different open source projects</strong> to learn how they solve similar problems.</p><p>I&apos;m going to list some of them for reference (and also to say thanks to the maintainers!):</p><h3 id="manifest-v3-sandboxes">Manifest v3 + Sandboxes</h3><p>I learned more about web3 and sandboxes by looking at how this project worked</p><figure class="kg-card kg-bookmark-card"><a class="kg-bookmark-container" href="https://github.com/jorgenbuilder/chrome-dfinity-decoder"><div class="kg-bookmark-content"><div class="kg-bookmark-title">GitHub - jorgenbuilder/chrome-dfinity-decoder: Decode responses from the Dfinity blockchain in chrome devtools</div><div class="kg-bookmark-description">Decode responses from the Dfinity blockchain in chrome devtools - GitHub - jorgenbuilder/chrome-dfinity-decoder: Decode responses from the Dfinity blockchain in chrome devtools</div><div class="kg-bookmark-metadata"><img class="kg-bookmark-icon" src="https://github.com/fluidicon.png" alt="Pyrun: Execute Python inside your Twitter, Facebook, Linkedin"><span class="kg-bookmark-author">GitHub</span><span class="kg-bookmark-publisher">jorgenbuilder</span></div></div><div class="kg-bookmark-thumbnail"><img src="https://opengraph.githubassets.com/da35a3b88b908aa36ae7e683eb952dc94864a1ae6c91c61e2918999f6ea7317c/jorgenbuilder/chrome-dfinity-decoder" alt="Pyrun: Execute Python inside your Twitter, Facebook, Linkedin"></div></a></figure><h3 id="pyodide-related-projects">Pyodide related projects</h3><p>Even though I couldn&apos;t take any meaningful piece of code, both projects helped me understand that I had to define a custom bootstrapping for Pyodide.</p><figure class="kg-card kg-bookmark-card"><a class="kg-bookmark-container" href="https://github.com/grimmer0125/embedded-pydicom-react-viewer"><div class="kg-bookmark-content"><div class="kg-bookmark-title">GitHub - grimmer0125/embedded-pydicom-react-viewer: Medical DICOM file P10 Viewer/Chrome Extension + Python Code In Browser (-Pyodide-&gt; WebAssembly) + Pydicom parser + TypeScript React App (CRA). Use d4c-queue npm lib.</div><div class="kg-bookmark-description">Medical DICOM file P10 Viewer/Chrome Extension + Python Code In Browser (-Pyodide-&amp;gt; WebAssembly) + Pydicom parser + TypeScript React App (CRA). Use d4c-queue npm lib. - GitHub - grimmer0125/embe...</div><div class="kg-bookmark-metadata"><img class="kg-bookmark-icon" src="https://github.com/fluidicon.png" alt="Pyrun: Execute Python inside your Twitter, Facebook, Linkedin"><span class="kg-bookmark-author">GitHub</span><span class="kg-bookmark-publisher">grimmer0125</span></div></div><div class="kg-bookmark-thumbnail"><img src="https://opengraph.githubassets.com/011ffe16011a6ff88b778e5a979bf8e569a4b8118d4f2b53d7d6e700101227b1/grimmer0125/embedded-pydicom-react-viewer" alt="Pyrun: Execute Python inside your Twitter, Facebook, Linkedin"></div></a></figure><figure class="kg-card kg-bookmark-card"><a class="kg-bookmark-container" href="https://github.com/Mario2334/swindle"><div class="kg-bookmark-content"><div class="kg-bookmark-title">GitHub - Mario2334/swindle</div><div class="kg-bookmark-description">Contribute to Mario2334/swindle development by creating an account on GitHub.</div><div class="kg-bookmark-metadata"><img class="kg-bookmark-icon" src="https://github.com/fluidicon.png" alt="Pyrun: Execute Python inside your Twitter, Facebook, Linkedin"><span class="kg-bookmark-author">GitHub</span><span class="kg-bookmark-publisher">Mario2334</span></div></div><div class="kg-bookmark-thumbnail"><img src="https://opengraph.githubassets.com/579d77288d4d883b3c1c86daf3fed4fd07697d7f04ce212f4ab47e6bbe755082/Mario2334/swindle" alt="Pyrun: Execute Python inside your Twitter, Facebook, Linkedin"></div></a></figure><figure class="kg-card kg-bookmark-card"><a class="kg-bookmark-container" href="https://github.com/alexmojaki/futurecoder"><div class="kg-bookmark-content"><div class="kg-bookmark-title">GitHub - alexmojaki/futurecoder: 100% free and interactive Python course for beginners</div><div class="kg-bookmark-description">100% free and interactive Python course for beginners - GitHub - alexmojaki/futurecoder: 100% free and interactive Python course for beginners</div><div class="kg-bookmark-metadata"><img class="kg-bookmark-icon" src="https://github.com/fluidicon.png" alt="Pyrun: Execute Python inside your Twitter, Facebook, Linkedin"><span class="kg-bookmark-author">GitHub</span><span class="kg-bookmark-publisher">alexmojaki</span></div></div><div class="kg-bookmark-thumbnail"><img src="https://opengraph.githubassets.com/ef53f8d04c9d1da4c69e4b6ae8621e89a92a0f463378d065d785c123c90ee67f/alexmojaki/futurecoder" alt="Pyrun: Execute Python inside your Twitter, Facebook, Linkedin"></div></a></figure>]]></content:encoded></item><item><title><![CDATA[Python Match Case is more powerful than you think 🐍🕹️]]></title><description><![CDATA[Python 3.10 has the match case which is Structural Pattern Matching.  I'm going to show you what it can do with examples!]]></description><link>https://guicommits.com/python-match-case-examples/</link><guid isPermaLink="false">63035ca6d4ffab040f159e3e</guid><category><![CDATA[🐍 Python]]></category><category><![CDATA[interactive]]></category><dc:creator><![CDATA[Guilherme Latrova]]></dc:creator><pubDate>Mon, 22 Aug 2022 11:05:31 GMT</pubDate><media:content url="https://guicommits.com/content/images/2022/08/Blog-Post-Python-Match-Case.png" medium="image"/><content:encoded><![CDATA[<img src="https://guicommits.com/content/images/2022/08/Blog-Post-Python-Match-Case.png" alt="Python Match Case is more powerful than you think &#x1F40D;&#x1F579;&#xFE0F;"><p>Python 3.10 brought the <code>match case</code> syntax which is <em>similar</em> to the <code>switch case</code> from other languages.</p><p>It&apos;s just similar though. <strong>Python&apos;s match case is WAY MORE POWERFUL than the switch case</strong> because it&apos;s a <strong>Structural Pattern Matching</strong>.</p><p>You don&apos;t know what I mean? <strong>I&apos;m going to show you</strong> <strong>what it can do with examples</strong>!</p><p>Note that if you&apos;re reading this article in AMP mode or from mobile you won&apos;t be able to run Python code from your browser, but you can still see the code samples.</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://guicommits.com/content/images/2022/08/pythonworking.gif" class="kg-image" alt="Python Match Case is more powerful than you think &#x1F40D;&#x1F579;&#xFE0F;" loading="lazy" width="1373" height="751"><figcaption>Example of executing Python interactively</figcaption></figure><hr><h1 id="match-case-is-similar-to-a-switch-case">Match Case is similar to a Switch Case</h1><p>It&apos;s still possible to use <code>match case</code> as a common <code>switch case</code>:</p><pre><code class="language-py">from http import HTTPStatus
import random

http_status = random.choice(
    [
        HTTPStatus.OK,
        HTTPStatus.BAD_REQUEST,
        HTTPStatus.INTERNAL_SERVER_ERROR,
    ]
)

# &#x1F447; Simplest example, can be easily replaced by a dictionary
match http_status:
    case HTTPStatus.OK: # &#x1F448; &quot;case&quot; + &quot;value&quot; syntax
        print(&quot;Everything is good!&quot;)

    case HTTPStatus.BAD_REQUEST:
        print(&quot;You did something wrong!&quot;)

    case HTTPStatus.INTERNAL_SERVER_ERROR:
        print(&quot;Oops... Is the server down!?.&quot;)

    case _: # &#x1F448; Default syntax
        print(&quot;Invalid or unknown status.&quot;)
</code></pre><p>Boring, right? It can be easily replaced by a dictionary with fewer lines, see:</p><pre><code class="language-py">from http import HTTPStatus
import random

http_status = random.choice(
    [
        HTTPStatus.OK,
        HTTPStatus.BAD_REQUEST,
        HTTPStatus.INTERNAL_SERVER_ERROR,
    ]
)

dictmap = {
    HTTPStatus.OK: &quot;Everything is good!&quot;,
    HTTPStatus.BAD_REQUEST: &quot;You did something wrong!&quot;,
    HTTPStatus.INTERNAL_SERVER_ERROR: &quot;Oops... Is the server down!?.&quot;,
}

message = dictmap.get(http_status, &quot;Invalid or unknown status.&quot;)
print(message)
</code></pre><h1 id="match-case-matching-many-different-values">Match Case matching many different values</h1><p>As I mentioned initially, the match case goes beyond a regular switch case.</p><p>Let&apos;s match specific status codes with the <code>or</code> statement by using <code>|</code>:</p><pre><code class="language-py">from http import HTTPStatus
import random

http_status = random.choice(list(HTTPStatus))

match http_status:
    case 200 | 201 | 204 as status:
        # &#x1F446; Using &quot;as status&quot; extracts its value
        print(f&quot;Everything is good! {status = }&quot;) # &#x1F448; Now status can be used inside handler

    case 400 | 404 as status:
        print(f&quot;You did something wrong! {status = }&quot;)

    case 500 as status:
        print(f&quot;Oops... Is the server down!? {status = }&quot;)

    case _ as status:
        print(f&quot;No clue what to do with {status = }!&quot;)
</code></pre><p>Note we used <code>as status</code> to extract the value into a variable that can be used inside the handler.</p><h1 id="match-case-with-conditionals-guards">Match Case with conditionals (guards)</h1><p>Not exciting yet? Ok, let&apos;s improve it a little.</p><p>You can see we are missing many status codes in the previous example.</p><p>What if we want to match ranges as:</p><ul><li>&lt;200,</li><li>200-399,</li><li>400-499, and</li><li>&gt;=500?</li></ul><p>We can use <strong>guards</strong> for that:</p><pre><code class="language-py">from http import HTTPStatus
import random

http_status = random.choice(list(HTTPStatus))

match http_status:
    # &#x1F481;&#x200D;&#x2642;&#xFE0F; Note we don&apos;t match a specific value as we use &quot;_&quot; (underscore)
    # &#x1F447;&#x2705; Match any value, as long as status is between 200-399
    case _ as status if status &gt;= HTTPStatus.OK and status &lt; HTTPStatus.BAD_REQUEST:
        print(f&quot;&#x2705; Everything is good! {status = }&quot;)
        # &#x1F446;&#x1F4E4; We took &apos;status&apos; by using the &apos;as status&apos; syntax

    # &#x1F447;&#x274C; Match any value, as long as status is between 400-499
    case _ as status if status &gt;= HTTPStatus.BAD_REQUEST and status &lt; HTTPStatus.INTERNAL_SERVER_ERROR:
        print(f&quot;&#x274C; You did something wrong! {status = }&quot;)

    # &#x1F447;&#x1F4A3; Match any value, as long as status is &gt;=500
    case _ as status if status &gt;= HTTPStatus.INTERNAL_SERVER_ERROR:
        print(f&quot;&#x1F4A3; Oops... Is the server down!? {status = }.&quot;)

    # &#x1F447;&#x2753; Match any value that we didn&apos;t catch before (&lt;200)
    case _ as status:
        print(f&quot;&#x2753; No clue what to do with {status = }!&quot;)
</code></pre><p>&#x1F446; Note we didn&apos;t use any specific value inside our case statements.</p><p>We used <code>_</code> (underscore) to match all because we wanted to check ranges instead of specific values.</p><p>We call &quot;<em>guards</em>&quot; when we validate the matched pattern using an <code>if</code> as we did above.</p><h1 id="match-case-lists-value-position-and-length">Match Case lists value, position, and length</h1><p>You can match lists based on values at a specific position and even length!</p><p>See some examples below where we match:</p><ul><li>Any list with 3 items by using and extracting these items as vars</li><li>Any list with more than 3 items by using <code>*_</code></li><li>Any list starting with a specific value + possible combinations</li><li>Any list starting with a specific value</li></ul><pre><code class="language-py">baskets = [
    [&quot;apple&quot;, &quot;pear&quot;, &quot;banana&quot;], # &#x1F34E; &#x1F350; &#x1F34C;
    [&quot;chocolate&quot;, &quot;strawberry&quot;], # &#x1F36B; &#x1F353;
    [&quot;chocolate&quot;, &quot;banana&quot;], # &#x1F36B; &#x1F34C;
    [&quot;chocolate&quot;, &quot;pineapple&quot;], # &#x1F36B; &#x1F34D;
    [&quot;apple&quot;, &quot;pear&quot;, &quot;banana&quot;, &quot;chocolate&quot;], # &#x1F34E; &#x1F350; &#x1F34C; &#x1F36B;
]

def resolve_basket(basket: list):
    match basket:

        # &#x1F447; Matches any 3 items
        case [i1, i2, i3]: # &#x1F448; These are extracted as vars and used here &#x1F447;
            print(f&quot;Wow, your basket is full with: &apos;{i1}&apos;, &apos;{i2}&apos; and &apos;{i3}&apos;&quot;)

        # &#x1F447; Matches &gt;= 4 items
        case [_, _, _, *_] as basket_items:
            print(f&quot;Wow, your basket has so many items: {len(basket_items)}&quot;)

        # &#x1F447; 2 items. First should be &#x1F36B;, second should be &#x1F353; or &#x1F34C;
        case [&quot;chocolate&quot;, &quot;strawberry&quot; | &quot;banana&quot;]:
            print(&quot;This is a superb combination. &#x1F36B; + &#x1F353;|&#x1F34C;&quot;)

        # &#x1F447; 2 items. First should be &#x1F36B;, second should be &#x1F34D;
        case [&quot;chocolate&quot;, &quot;pineapple&quot;]:
            print(&quot;Eww, really? &#x1F36B; + &#x1F34D; = ?&quot;)

        # &#x1F447; Any amount of items starting with &#x1F36B;
        case [&quot;chocolate&quot;, *_]:
            print(&quot;I don&apos;t know what you plan but it looks delicious. &#x1F36B;&quot;)

        # &#x1F447; If nothing matched before
        case _:
            print(&quot;Don&apos;t be cheap, buy something else&quot;)


for basket in baskets:
    print(f&quot;&#x1F4E5; {basket}&quot;)
    resolve_basket(basket)
    print()
</code></pre><h1 id="match-case-dicts">Match Case dicts</h1><p>We can do a lot with dicts!</p><p>Let&apos;s see many examples with dicts holding <code>str</code> keys and either <code>int</code> or <code>str</code> as their values.</p><p>We can match existing keys, value types, and dict length.</p><pre><code class="language-py">mappings: list[dict[str, str | int]] = [
    {&quot;name&quot;: &quot;Gui Latrova&quot;, &quot;twitter_handle&quot;: &quot;@guilatrova&quot;},
    {&quot;name&quot;: &quot;John Doe&quot;},
    {&quot;name&quot;: &quot;JOHN DOE&quot;},
    {&quot;name&quot;: 123456},
    {&quot;full_name&quot;: &quot;Peter Parker&quot;},
    {&quot;full_name&quot;: &quot;Peter Parker&quot;, &quot;age&quot;: 16}
]

def resolve_mapping(mapping: dict[str|int]):
    match mapping:
        # &#x1F447; Matches any
        #    (1) &quot;name&quot; AND any (2) &quot;twitter_handle&quot;
        case {&quot;name&quot;: name, &quot;twitter_handle&quot;: handle}:
            print(f&quot;&#x1F609; Make sure to follow {name} at {handle} to keep learning&quot;) # &#x1F609; This is good advice

        # &#x1F447; Matches any
        #    (1) &quot;name&quot; (2) if val is str and (3) it&apos;s all UPPER CASED
        case {&quot;name&quot;: str() as name} if name == name.upper():
            print(f&quot;&#x1F625; Hey, there&apos;s no need to shout, {name}!&quot;)

        # &#x1F447; Matches any
        #    (1) &quot;name&quot; (2) if val is str. It will fall here whenever the above &#x1F446; doesn&apos;t match
        case {&quot;name&quot;: str() as name}:
            print(f&quot;&#x1F44B; Hi {name}!&quot;)

        # &#x1F447; Matches any
        #    (1) &quot;name&quot; (2) if val is int.
        case {&quot;name&quot;: int()}:
            print(&quot;&#x1F916; Are you a robot or what? How can I say your name? &quot;)

        # &#x1F447; Matches any
        #    (1) &quot;full_name&quot; (2) and NOTHING else
        case {&quot;full_name&quot;: full_name, **remainder} if not remainder:
            print(f&quot;Thanks mr/ms {full_name}!&quot;)

        # &#x1F447; Matches any
        #    (1) &quot;full_name&quot; (2) and ANYTHING else
        case {&quot;full_name&quot;: full_name, **remainder}:
            print(f&quot;Just your full name is fine! No need to share {list(remainder.keys())}&quot;)


for mapping in mappings:
    print(f&quot;&#x1F4E5; {mapping}&quot;)
    resolve_mapping(mapping)
    print()
</code></pre><h1 id="match-case-classes-instances-and-props">Match Case classes instances and props</h1><p>The first time I saw:</p><pre><code class="language-py">class Example:
    ...

var = Example()

match var:
    case Example(): # &#x1F448; This syntax is a bit weird
        ...
</code></pre><p>I thought we could be instantiating the class &#x1F605; which is wrong.</p><p>This syntax means: &quot;<em>Instance of type <code>Example</code> with any props</em>.&quot;</p><p>Above you probably saw we doing that for <code>int()</code> and <code>str()</code>. The logic is the same.</p><p>Check a few examples:</p><ul><li>Matching a class instance with the property <code>name</code> equals <code>End</code></li><li>Matching any instance based on the type</li><li>Matching instances with specific properties set to <code>0</code></li><li>Extracting class properties to be used inside the handler</li></ul><pre><code class="language-py">from dataclasses import dataclass

@dataclass
class Move:
    x: int # horizontal
    y: int # vertical

@dataclass
class Action:
    name: str

@dataclass
class UnknownStep:
    random_value = &quot;Darth Vader riding a monocycle&quot;


steps = [
    Move(1, 0),
    Move(2, 5),
    Move(0, 5),
    Action(&quot;Work&quot;),
    Move(0, 0),
    Action(&quot;Rest&quot;),
    Move(0, 0),
    UnknownStep(),
    Action(&quot;End&quot;),
]


def resolve_step(step):
    match step:
        # &#x1F447; Match any action that has name = &quot;End&quot;
        case Action(name=&quot;End&quot;): # &#x1F448; Note we&apos;re not instantiating anything
            print(&quot;&#x1F51A; Flow finished&quot;)

        # &#x1F447; Match any Action type
        case Action():
            print(&quot;&#x1F44F; Good to see you&apos;re doing something&quot;)

        # &#x1F447; Match any Move with x,y == 0,0
        case Move(0, 0):
            print(&quot;&#x1F482; You&apos;re not really moving, stop pretending&quot;)

        # &#x1F447; Match any Move with y = 0
        case Move(x, 0):
            print(f&quot;&#x27A1;&#xFE0F; You&apos;re moving horizontally to {x}&quot;)

        # &#x1F447; Match any Move with x = 0
        case Move(0, y):
            print(f&quot;&#x1F51D; You&apos;re moving vertically to {y}&quot;)

        # &#x1F447; Match any Move type
        case Move(x, y):
            print(f&quot;&#x1F5FA;&#xFE0F; You&apos;re moving to ({x}, {y})&quot;)

        # &#x1F447; When nothing matches
        case _:
            print(f&quot;&#x2753; I&apos;ve got not idea what you&apos;re doing&quot;)


for step in steps:
    print(f&quot;&#x1F4E5; {step}&quot;)
    resolve_step(step)
    print()</code></pre><h3 id="keep-learning-with-me">Keep Learning with me</h3><figure class="kg-card kg-embed-card"><blockquote class="twitter-tweet"><p lang="en" dir="ltr">&#x1F40D; Python Match Case got released a few months ago but is still not well comprehended.<br><br>Too many people think this is a regular switch case.<br><br>Guess what? It&apos;s not! &#x1F645;&#x1F3FB;<br><br>This is DAY ONE of a series of tweets on how powerful Python Match is to be executed with <a href="https://twitter.com/hashtag/pyrun?src=hash&amp;ref_src=twsrc%5Etfw">#pyrun</a> <a href="https://t.co/VJJsapkL3W">pic.twitter.com/VJJsapkL3W</a></p>&#x2014; Gui Latrova (@guilatrova) <a href="https://twitter.com/guilatrova/status/1567467796183019520?ref_src=twsrc%5Etfw">September 7, 2022</a></blockquote>
<script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>
</figure>]]></content:encoded></item><item><title><![CDATA[Organize Python code like a PRO 🐍📦]]></title><description><![CDATA[Python is very flexible and everything too flexible enhances the odds of bad decisions. I'm going to share experiences on naming and structure]]></description><link>https://guicommits.com/organize-python-code-like-a-pro/</link><guid isPermaLink="false">62c69ce7d4ffab040f159d3c</guid><category><![CDATA[🐍 Python]]></category><dc:creator><![CDATA[Guilherme Latrova]]></dc:creator><pubDate>Thu, 07 Jul 2022 09:36:38 GMT</pubDate><media:content url="https://guicommits.com/content/images/2022/07/Blog-Post-Python-Like-a-PRO-Series.png" medium="image"/><content:encoded><![CDATA[<blockquote>For every minute spent in organizing, an hour is earned.<br>by Benjamin Franklin</blockquote><img src="https://guicommits.com/content/images/2022/07/Blog-Post-Python-Like-a-PRO-Series.png" alt="Organize Python code like a PRO &#x1F40D;&#x1F4E6;"><p>Python is different from languages like C# or Java where they enforce you to have classes named after the file they live in.</p><p>So far Python is one of the most flexible languages I had contact with and everything too flexible enhances the odds of bad decisions.</p><ul><li>Do you want to keep all project classes in a single <code>main.py</code> file? Yes, it works.</li><li>Do you need to read an os environment var? Just read it right there.</li><li>Do you need to modify a function behavior? Why not a decorator!?</li></ul><p><strong>Many decisions that are easy to implement may backfire producing code that is extremely hard to maintain.</strong></p><p>This is not necessarily bad <strong>if you know what you&apos;re doing.</strong></p><p>During this chapter, I&apos;m going to present to you guidelines that worked for me over the past working in different companies and with many different people.</p><h2 id="%F0%9F%8C%B3-structure-your-python-project">&#x1F333; Structure your Python project</h2><p>Let&apos;s focus first on directory structure, file naming, and module organization.</p><p>I recommend you to keep all your module files inside a <code>src</code> dir, and all tests living side by side with it:</p><p><strong>Top-Level project</strong></p><pre><code>&lt;project&gt;
&#x251C;&#x2500;&#x2500; src
&#x2502;   &#x251C;&#x2500;&#x2500; &lt;module&gt;/*
&#x2502;   &#x2502;    &#x251C;&#x2500;&#x2500; __init__.py
&#x2502;   &#x2502;    &#x2514;&#x2500;&#x2500; many_files.py
&#x2502;   &#x2502;
&#x2502;   &#x2514;&#x2500;&#x2500; tests/*
&#x2502;        &#x2514;&#x2500;&#x2500; many_tests.py
&#x2502;
&#x251C;&#x2500;&#x2500; .gitignore
&#x251C;&#x2500;&#x2500; pyproject.toml
&#x2514;&#x2500;&#x2500; README.md
</code></pre><p>Where <code>&lt;module&gt;</code> is your main module. If in doubt, consider what people would <code>pip install</code> and how you would like to <code>import module</code>.</p><p>Frequently it has the same name as the top project. This isn&apos;t a rule though.</p><h3 id="%F0%9F%8E%AF-the-reasoning-behind-a-src-directory">&#x1F3AF; The reasoning behind a <code>src</code> directory</h3><p>I&apos;ve seen many projects doing differently.</p><p>Some variations include no <code>src</code> dir with all project modules around the tree.</p><p>This is quite annoying because of the lack of order, producing things like (example):</p><pre><code>non_recommended_project
&#x251C;&#x2500;&#x2500; &lt;module_a&gt;/*
&#x2502;     &#x251C;&#x2500;&#x2500; __init__.py
&#x2502;     &#x2514;&#x2500;&#x2500; many_files.py
&#x2502;
&#x251C;&#x2500;&#x2500; .gitignore
&#x2502;
&#x251C;&#x2500;&#x2500; tests/*
&#x2502;    &#x2514;&#x2500;&#x2500; many_tests.py
&#x2502;
&#x251C;&#x2500;&#x2500; pyproject.toml
&#x2502;
&#x251C;&#x2500;&#x2500; &lt;module_b&gt;/*
&#x2502;     &#x251C;&#x2500;&#x2500; __init__.py
&#x2502;     &#x2514;&#x2500;&#x2500; many_files.py
&#x2502;
&#x2514;&#x2500;&#x2500; README.md
</code></pre><p>It&apos;s boring to have things so apart due to the alphabetical sorting of the IDE.</p><p>The main reason behind the <code>src</code> dir is to keep active project code concentrated inside a single directory while settings, CI/CD setup, and project metadata can reside outside of it.</p><p>The only drawback of doing it is that you can&apos;t <code>import module_a</code> in your python code out of the box. We need to set up the project to be installed under this repository. We&apos;re going to discuss how to solve this soon in this chapter.</p><h3 id="%F0%9F%8F%B7%EF%B8%8F-how-to-name-files">&#x1F3F7;&#xFE0F; How to name files</h3><p><strong>Rule 1: There are no files</strong></p><p>First of all, in Python there are no such things as &quot;files&quot; and I noticed this is the main source of confusion for beginners.</p><p>If you&apos;re inside a directory that contains any <code>__init__.py</code> it&apos;s a directory composed of modules, not files.</p><p><strong>See each module as a namespace.</strong></p><p>I mean namespace because you can&apos;t say for sure whether they have many functions, classes, or just constants. It can have virtually all of them or just a bunch of some.</p><p><strong>Rule 2: Keep things together as needed</strong></p><p><strong>It&#x2019;s fine to have several classes within a single module</strong>, and you should do so. (when classes are related to the module, obviously.)</p><p><strong>Only break it down when your module gets too big, or when it handles different concerns.</strong></p><p>Often, people think it&#x2019;s a bad practice due to some experience with other languages that enforce the other way around (e.g. Java and C#).</p><p><strong>Rule 3: By default give plural names</strong></p><p>As a rule of thumb, name your modules in the <strong>plural</strong> and name them after a business context. </p><p>There&apos;re exceptions to this rule though! Modules can be named <code>core</code>, <code>main.py</code>, and similar to represent a single thing. Use your judgment, if in doubt stick to the plural rule.</p><h3 id="%F0%9F%94%8E-real-life-example-when-naming-modules">&#x1F50E; Real-life example when naming modules</h3><p>I&apos;ll share a <a href="https://github.com/guilatrova/GMaps-Crawler">Google Maps Crawler</a> project that I built as an example.</p><p>This project is responsible for crawling data from Google Maps using Selenium and outputting it (<a href="https://guicommits.com/selenium-example-with-python-gmaps/">Read more here</a> if curious).</p><p>This is the current project tree outlining exceptions to the #3 rule:</p><pre><code>gmaps_crawler
&#x251C;&#x2500;&#x2500; src
&#x2502;   &#x2514;&#x2500;&#x2500; gmaps_crawler
&#x2502;        &#x251C;&#x2500;&#x2500; __init__.py
&#x2502;        &#x251C;&#x2500;&#x2500; config.py &#x1F448; (Singular)
&#x2502;        &#x251C;&#x2500;&#x2500; drivers.py
&#x2502;        &#x251C;&#x2500;&#x2500; entities.py
&#x2502;        &#x251C;&#x2500;&#x2500; exceptions.py
&#x2502;        &#x251C;&#x2500;&#x2500; facades.py
&#x2502;        &#x251C;&#x2500;&#x2500; main.py  &#x1F448; (Singular)
&#x2502;        &#x2514;&#x2500;&#x2500; storages.py
&#x2502;
&#x251C;&#x2500;&#x2500; .gitignore
&#x251C;&#x2500;&#x2500; pyproject.toml
&#x2514;&#x2500;&#x2500; README.md
</code></pre><p>It seems very natural to import classes and functions like:</p><pre><code class="language-py">from gmaps_crawler.storages import get_storage
from gmaps_crawler.entities import Place
from gmaps_crawler.exceptions import CantEmitPlace
</code></pre><p>I can understand that I might have one or many exception classes inside <code>exceptions</code> and so on.</p><p>The beauty about having plural modules is that:</p><ul><li>They&apos;re not too small (e.g. one per class)</li><li>You can at any moment break it down into smaller modules if required</li><li>They give you a strong sense of knowing what might exist inside</li></ul><h3 id="%F0%9F%94%96-naming-classes-functions-and-variables">&#x1F516; Naming classes, functions, and variables</h3><p>Some people claim naming things is hard. It gets less hard when you define some guidelines.</p><h4 id="%F0%9F%91%8A-functions-and-methods-should-be-verbs">&#x1F44A; Functions and Methods should be verbs</h4><p>Functions and methods represent an action or actionable stuff. </p><p>Something &quot;isn&apos;t&quot;. Something is &quot;happening&quot;.</p><p>Actions are clearly stated by verbs.</p><p>A few good examples from REAL projects I worked on before:</p><pre><code class="language-py">def get_orders():
    ...

def acknowledge_event():
    ...

def get_delivery_information():
    ...

def publish():
    ...
</code></pre><p>A few bad examples:</p><pre><code class="language-py">def email_send():
    ...

def api_call():
   ...

def specific_stuff():
   ...
</code></pre><p>They&apos;re a bit unclear whether they return an object to allow me to perform the API call or if it actually sends the email for example.</p><p>I can picture a scenario like this:</p><figure class="kg-card kg-code-card"><pre><code class="language-py">email_send.title = &quot;title&quot;
email_send.dispatch()</code></pre><figcaption>Example of a misleading function name</figcaption></figure><p>Exceptions to this rule are just a few but they exist.</p><ul><li>Creating a <code>main()</code> function to be invoked in the main entry point of your application is a good reason to skip this rule.</li><li>Using <code>@property</code> to treat a class method as an attribute is also valid.</li></ul><h4 id="%F0%9F%90%B6-variables-and-constants-should-be-nouns">&#x1F436; Variables and Constants should be nouns</h4><p>Should always be nouns, never verbs (which clarifies the difference between functions).</p><p>Good examples:</p><pre><code class="language-py">plane = Plane()
customer_id = 5
KEY_COMPARISON = &quot;abc&quot;
</code></pre><p>Bad examples:</p><pre><code class="language-py">fly = Plane()
get_customer_id = 5
COMPARE_KEY = &quot;abc&quot;
</code></pre><p>If your variable/constant is a list or collection, make it plural!</p><pre><code class="language-py">planes: list[Plane] = [Plane()] # &#x1F448; Even if it contains only one item
customer_ids: set[int] = {5, 12, 22}
KEY_MAP: dict[str, str] = {&quot;123&quot;: &quot;abc&quot;} # &#x1F448; Dicts are kept singular
</code></pre><h4 id="%F0%9F%8F%9B%EF%B8%8F-classes-should-be-self-explanatory-but-suffixes-are-fine">&#x1F3DB;&#xFE0F; Classes should be self explanatory, but Suffixes are fine</h4><p>Prefer classes with self explanatory names. It&apos;s fine to have suffixes like <code>Service</code>, <code>Strategy</code>, <code>Middleware</code>, but only when extremely necessary to make its purpose clear.</p><p><strong>Always name it in singular</strong> instead of plural. Plural reminds us of collections (e.g. if I read <code>orders</code> I assume it&apos;s a list or iterable), so remind yourself that once a class is instantiated it becomes a single object.</p><p><strong>Classes representing entities</strong></p><p>Classes that represent things from the business context should be named as is (nouns!). Like <code>Order</code>, <code>Sale</code>, <code>Store</code>, <code>Restaurant</code> and so on.</p><p><strong>Example of suffixes usage</strong></p><p>Let&#x2019;s consider you want to create a class responsible for sending emails. If you name it just as &quot;<code>Email</code>&quot;, its purpose is not clear.</p><p>Someone might think it may represent an entity e.g.</p><pre><code class="language-py">email = Email() # inferred usage example
email.title = &quot;Title&quot;
email.body = create_body()
email.send_to = &quot;guilatrova.dev&quot;

send_email(email)
</code></pre><p>You should name it &quot;<code>EmailSender</code>&quot; or &quot;<code>EmailService</code>&quot;.</p><h4 id="%F0%9F%90%AA-casing-conventions">&#x1F42A; Casing conventions</h4><p>By default follow these naming conventions:</p><!--kg-card-begin: markdown--><table>
<thead>
<tr>
<th>Type</th>
<th>Public</th>
<th>Internal</th>
</tr>
</thead>
<tbody>
<tr>
<td>Packages (directories)</td>
<td><code>lower_with_under</code></td>
<td>-</td>
</tr>
<tr>
<td>Modules (files)</td>
<td><code>lower_with_under.py</code></td>
<td>-</td>
</tr>
<tr>
<td>Classes</td>
<td><code>CapWords</code></td>
<td>-</td>
</tr>
<tr>
<td>Functions and methods</td>
<td><code>lower_with_under()</code></td>
<td><code>_lower_with_under()</code></td>
</tr>
<tr>
<td>Constants</td>
<td><code>ALL_CAPS_UNDER</code></td>
<td><code>_ALL_CAPS_UNDER</code></td>
</tr>
</tbody>
</table>
<!--kg-card-end: markdown--><h4 id="%E2%9A%A0%EF%B8%8F-disclaimer-about-private-methods">&#x26A0;&#xFE0F; Disclaimer about &quot;&quot;&quot;private&quot;&quot;&quot; methods.</h4><p>Some people found out that if you have <code>__method(self)</code> (any method starting with two underscores) Python won&apos;t let outside classes/methods invoke it normally which leads them to think it&apos;s fine.</p><p>If you came from a C# environment like myself it might sound weird that you can&apos;t protect a method.</p><p>But Guido (Python&apos;s creator) has a good reason behind it:</p><blockquote>&quot;We&apos;re all consenting adults here&quot;</blockquote><p>It means that if you&apos;re aware you shouldn&apos;t be invoking a method, then you shouldn&apos;t unless you know what you&apos;re doing.</p><p>After all, if you really decided to invoke that method, you&apos;re going to do something dirty to make it happen (known as &quot;Reflection&quot; in C#).</p><p>Mark your private method/function with a single initial underscore to state it&apos;s intended for private use only and live with it.</p><h3 id="%E2%86%AA%EF%B8%8F-when-to-create-a-function-or-a-class-in-python">&#x21AA;&#xFE0F; When to create a function or a class in Python?</h3><p>This is a common question I received a few times.</p><p>If you follow the above recommendations you&apos;re going to have clear modules and clear modules are an effective way to organize functions:</p><pre><code class="language-py">from gmaps_crawler import storages

storages.get_storage()  # &#x1F448; Similar to a class, except it&apos;s not instantied and has a plural name
storages.save_to_storage()  # &#x1F448; Potential function inside module
</code></pre><p>Sometimes you can identify subsets of functions inside a module. When this happens a class makes more sense:</p><h4 id="example-on-grouping-different-subset-of-functions">Example on grouping different subset of functions</h4><p>Consider the same <code>storages</code> module with 4 functions:</p><pre><code class="language-py">def format_for_debug(some_data):
    ...

def save_debug(some_data):
    &quot;&quot;&quot;Prints in the screen&quot;&quot;&quot;
    formatted_data = format_for_debug(some_data)
    print(formatted_data)


def create_s3(bucket):
    &quot;&quot;&quot;Create s3 bucket if it doesn&apos;t exists&quot;&quot;&quot;
    ...

def save_s3(some_data):
    s3 = create_s3(&quot;bucket_name&quot;)
    ...
</code></pre><p>S3 is a cloud storage to store any sort of data provided by Amazon (AWS). It&apos;s like Google Drive for software.</p><p>We can say that:</p><ul><li>The developer can save data in DEBUG mode (that just prints on the screen) or on S3 (that stores data on the cloud).</li><li><code>save_debug</code> uses the <code>format_for_debug</code> function</li><li><code>save_s3</code> uses the <code>create_s3</code> function</li></ul><p>I can see two groups of functions and no reason to keep them in different modules as they seem small, thus I&apos;d enjoy having them defined as classes:</p><pre><code class="language-py">class DebugStorage:
    def format_for_debug(self, some_data):
        ...

    def save_debug(self, some_data):
        &quot;&quot;&quot;Prints in the screen&quot;&quot;&quot;
        formatted_data = self.format_for_debug(some_data)
        print(formatted_data)


class S3Storage:
    def create_s3(self, bucket):
        &quot;&quot;&quot;Create s3 bucket if it doesn&apos;t exists&quot;&quot;&quot;
        ...

    def save_s3(self, some_data):
        s3 = self.create_s3(&quot;bucket_name&quot;)
        ...
</code></pre><p>Here&apos;s a rule of thumb:</p><ul><li>Always start with functions</li><li>Grow to classes once you feel you can group different subsets of functions</li></ul><h2 id="%F0%9F%9A%AA-creating-modules-and-entry-points">&#x1F6AA; Creating modules and entry points</h2><p>Every application has an entry point.</p><p>It means that there&apos;s a single module (aka file) that runs your application. It can be either a single script or a big module.</p><p>Whenever you&apos;re creating an entry point, make sure to add a condition to ensure <strong>it&apos;s being executed and not imported</strong>:</p><pre><code class="language-py">def execute_main():
    ...


if __name__ == &quot;__main__&quot;:  # &#x1F448; Add this condition
    execute_main()
</code></pre><p><strong>By doing that you ensure that any imports won&apos;t trigger your code by accident.</strong> Unless it&apos;s explicitly executed.</p><h3 id="defining-main-for-modules">Defining main for modules</h3><p>You might have noticed some python packages that can be invoked by passing down <code>-m</code> like:</p><pre><code class="language-bash">python -m pytest
python -m tryceratops
python -m faust
python -m flake8
python -m black
</code></pre><p>Such packages are treated almost like regular commands since you can also run them as:</p><pre><code class="language-bash">pytest
tryceratops
faust
flake8
black
</code></pre><p>To make this happen you need to specify a single <code>__main__.py</code> file inside your main module:</p><pre><code>&lt;project&gt;
&#x251C;&#x2500;&#x2500; src
&#x2502;   &#x251C;&#x2500;&#x2500; example_module &#x1F448; Main module
&#x2502;   &#x2502;    &#x251C;&#x2500;&#x2500; __init__.py
&#x2502;   &#x2502;    &#x251C;&#x2500;&#x2500; __main__.py &#x1F448; Add it here
&#x2502;   &#x2502;    &#x2514;&#x2500;&#x2500; many_files.py
&#x2502;   &#x2502;
&#x2502;   &#x2514;&#x2500;&#x2500; tests/*
&#x2502;        &#x2514;&#x2500;&#x2500; many_tests.py
&#x2502;
&#x251C;&#x2500;&#x2500; .gitignore
&#x251C;&#x2500;&#x2500; pyproject.toml
&#x2514;&#x2500;&#x2500; README.md
</code></pre><p>Don&apos;t forget you still need to include the check <code>__name__ == &quot;__main__&quot;</code> inside your <code>__main__.py</code> file.</p><p>When you install your module, you can run your project as <code>python -m example_module</code>.</p><h2 id="%F0%9F%93%96-hey">&#x1F4D6; Hey!</h2><p>This is an initial draft from a book that I&apos;m writing!</p><p><s>If you&apos;re interested make sure to <strong><a href="https://subscribe.guilatrova.dev/">subscribe to the newsletter</a> </strong>and <strong><a href="https://twitter.com/intent/user?screen_name=guilatrova">follow me on Twitter</a></strong> to be notified when the book is out!</s></p><p><strong>The first chapter is out with a special discount!</strong></p><figure class="kg-card kg-bookmark-card"><a class="kg-bookmark-container" href="https://guilatrova.gumroad.com/l/python-like-a-pro"><div class="kg-bookmark-content"><div class="kg-bookmark-title">Python Like a PRO &#x1F40D;&#x1F4DA; Book</div><div class="kg-bookmark-description">&#x26A0;&#xFE0F;&#x1F4DA; This book is still under development (that&#x2019;s why it&#x2019;s so cheap right now, the price will increase once all chapters are published).You need to know what the hell you&#x2019;re doing &#x1F525;&#x1F40D;Python is one of the most flexible languages I have had contact with.Everything too flexible enhances the odds of ba&#x2026;</div><div class="kg-bookmark-metadata"><img class="kg-bookmark-icon" src="https://public-files.gumroad.com/variants/i88lhvlps18emf7eoguxxu51gdzv/4ec519eb32080d4ff1ef08cba157dc2ac7dab092fa26aeca54e8e2b8f31f9a63" alt="Organize Python code like a PRO &#x1F40D;&#x1F4E6;"><span class="kg-bookmark-author">Gumroad</span></div></div><div class="kg-bookmark-thumbnail"><img src="https://public-files.gumroad.com/variants/baq69b30tip3tygvo8wg7s7hm7gd/3298c3eb001bbed90f1d616da66708480096a0a1b6e81bd4f8a2d6e9b831d301" alt="Organize Python code like a PRO &#x1F40D;&#x1F4E6;"></div></a></figure><p>I&apos;m also open to feedback, get in touch either through email or Twitter DMs if you have any.</p>]]></content:encoded></item><item><title><![CDATA[Python 3.11 What's New?]]></title><description><![CDATA[Major Python 3.11 changes are shown with examples: ExceptionGroup, better errors, Exception add_note, new type hints, StrEnum, and more!]]></description><link>https://guicommits.com/python-3-11-whats-new/</link><guid isPermaLink="false">62bb0385d4ffab040f159cf7</guid><category><![CDATA[🐍 Python]]></category><dc:creator><![CDATA[Guilherme Latrova]]></dc:creator><pubDate>Tue, 28 Jun 2022 13:48:44 GMT</pubDate><media:content url="https://guicommits.com/content/images/2022/06/Blog-Post-Python-3.11.png" medium="image"/><content:encoded><![CDATA[<img src="https://guicommits.com/content/images/2022/06/Blog-Post-Python-3.11.png" alt="Python 3.11 What&apos;s New?"><p>Here is a selection of the major changes coming in Python 3.11:</p><h2 id="1%EF%B8%8F%E2%83%A3-better-error-handling">1&#xFE0F;&#x20E3; Better Error Handling</h2><p>Better error messages to easily spot issues in your code.</p><p>Consider some code trying to read an invalid key from your dict.</p><p>In this case, <code>instagram</code> is an unexistent key that <code>process_dict</code> attempts to read:</p><pre><code class="language-py">invalid_dict = dict(
    youtube=&quot;Gui Commits&quot;,
    blog=&quot;https://guicommits.com&quot;,
    twitter=&quot;guilatrova&quot;,
)

def process_dict(input_dict: dict):
    handles = [&quot;youtube&quot;, &quot;blog&quot;, &quot;twitter&quot;, &quot;instagram&quot;]
    social_links = []
    for handle in handles:
        social_links += input_dict[handle]

process_dict(invalid_dict)
</code></pre><p>If you were running it in <strong>Python 3.10</strong>, you would see this:</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://guicommits.com/content/images/2022/06/python310_better_errors.png" class="kg-image" alt="Python 3.11 What&apos;s New?" loading="lazy" width="2000" height="706" srcset="https://guicommits.com/content/images/size/w600/2022/06/python310_better_errors.png 600w, https://guicommits.com/content/images/size/w1000/2022/06/python310_better_errors.png 1000w, https://guicommits.com/content/images/size/w1600/2022/06/python310_better_errors.png 1600w, https://guicommits.com/content/images/2022/06/python310_better_errors.png 2022w" sizes="(min-width: 720px) 720px"><figcaption>Python 3.10 Displaying an error</figcaption></figure><p>Now in <strong>Python 3.11</strong> that&apos;s how it looks like:</p><p>The characters &quot;<code>~</code>&quot; and &quot;<code>^</code>&quot; point out exactly where the issue is located.</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://guicommits.com/content/images/2022/06/python311_better_errors.png" class="kg-image" alt="Python 3.11 What&apos;s New?" loading="lazy" width="2000" height="789" srcset="https://guicommits.com/content/images/size/w600/2022/06/python311_better_errors.png 600w, https://guicommits.com/content/images/size/w1000/2022/06/python311_better_errors.png 1000w, https://guicommits.com/content/images/size/w1600/2022/06/python311_better_errors.png 1600w, https://guicommits.com/content/images/2022/06/python311_better_errors.png 2022w" sizes="(min-width: 720px) 720px"><figcaption>Python 3.11 Displaying an error</figcaption></figure><h2 id="2%EF%B8%8F%E2%83%A3-exception-groups">2&#xFE0F;&#x20E3; Exception Groups</h2><p>Now we have an <code>ExceptionGroup</code> that groups many other exceptions inside.</p><p>Each exception living inside a group can be individually captured by the <code>except*</code> syntax. See:</p><pre><code class="language-py">def raise_exception_group():
    raise ExceptionGroup(
        &quot;Description from ExceptionGroup&quot;, # Group wrapper
        [
            ValueError(&quot;ValueError&quot;), # First exception in group
            TypeError(&quot;TypeError&quot;) # Second exception in group
        ]
    )


def main():
    try:
        raise_exception_group()
    except* ValueError:
        print(&quot;Value Error!&quot;)
    except* TypeError:
        print(&quot;Type Error!&quot;)


main()
</code></pre><p>Note the code above <strong>would print both <code>Value Error!</code> and <code>Type Error!</code></strong>. All <code>except</code> blocks are executed if any exception exists inside the <code>ExceptionGroup</code>.</p><p>It can get a bit crazy since you have groups inside groups!</p><pre><code class="language-py">def raise_exception_group():
    raise ExceptionGroup(
        &quot;Description from ExceptionGroup&quot;,
        [
            ValueError(&quot;ValueError&quot;),
            TypeError(&quot;TypeError&quot;),
            ExceptionGroup(&quot;Another group&quot;, # Second group inside main group
                [
                    Exception(&quot;Another error&quot;)
                ]
            )
        ]
    )
</code></pre><h2 id="3%EF%B8%8F%E2%83%A3-exception-addnote">3&#xFE0F;&#x20E3; Exception <code>add_note</code></h2><p>Now you can keep enriching your exceptions with further data to add even more context to them!</p><pre><code class="language-py">def raise_exception():
    raise Exception(&quot;How you aren&apos;t subscribed to this blog&apos;s newsletter? &#x1F92F;&#x1F631;&quot;)


def main():
    try:
        raise_exception()
    except Exception as ex:
        ex.add_note(&quot;Not following on Twitter would also be a mistake! &#x1F61C;&quot;) # &#x1F448; New method!
        raise


main()
</code></pre><p>So your traceback would display all notes added:</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://guicommits.com/content/images/2022/06/add_note-1.png" class="kg-image" alt="Python 3.11 What&apos;s New?" loading="lazy" width="2000" height="1144" srcset="https://guicommits.com/content/images/size/w600/2022/06/add_note-1.png 600w, https://guicommits.com/content/images/size/w1000/2022/06/add_note-1.png 1000w, https://guicommits.com/content/images/size/w1600/2022/06/add_note-1.png 1600w, https://guicommits.com/content/images/2022/06/add_note-1.png 2110w" sizes="(min-width: 720px) 720px"><figcaption>Python 3.11 exception&apos;s <code>add_note</code> feature giving you some good advice</figcaption></figure><h2 id="4%EF%B8%8F%E2%83%A3-new-type-hints">4&#xFE0F;&#x20E3; New Type Hints</h2><h3 id="self-type"><code>Self</code> Type</h3><p>The <code>self</code> type came to resolve a common issue where Python/IDE can&apos;t infer the <code>self</code> type.</p><p>Consider this working code from previous Python 3.10:</p><pre><code class="language-py">from __future__ import annotations

class Shape:
    def set_scale(self, scale: float) -&gt; Self:
        self.scale = scale
        return self

class Circle(Shape):
    def set_radius(self, r: float) -&gt; Self:
        self.radius = r
        return self

# &#x1F534; Invalid inferred type!
Circle().set_scale(0.5)
Circle().set_scale(0.5).set_radius(2.7)
</code></pre><p>My IDE keeps telling me that <code>Circle.set_scale</code> returns <code>Shape</code>, which is not true!</p><figure class="kg-card kg-image-card"><img src="https://guicommits.com/content/images/2022/06/without_selftype.png" class="kg-image" alt="Python 3.11 What&apos;s New?" loading="lazy" width="574" height="140"></figure><p>This gets resolved with the new <code>Self</code> type:</p><pre><code class="language-py">from typing import Self  # &#x1F448; New typing

class Shape:
    def set_scale(self, scale: float) -&gt; Self:
        self.scale = scale
        return self

class Circle(Shape):
    def set_radius(self, r: float) -&gt; Self:
        self.radius = r
        return self

# &#x1F7E2; Correct inferred type!
Circle().set_scale(0.5)
Circle().set_scale(0.5).set_radius(2.7)
</code></pre><h3 id="literalstring"><code>LiteralString</code></h3><p>Think <code>LiteralString</code> as specific strings typed and expected <strong>by the developer</strong>.</p><pre><code class="language-py">from typing import LiteralString # &#x1F448; New import

def inferred(s: str) -&gt; bool:
    if s == &quot;Did you already signed up for our newsletter?&quot;:
        print(s)  # &#x1F448; Python identifies this as Literal String
        return True
    else:
        print(s)  # &#x1F448; This is still any string
        return False


def defined(s: LiteralString) -&gt; bool:
    print(s)
    return True


defined(&quot;literal value&quot;)
</code></pre><p>It can be useful to spot and prevent <a href="https://guicommits.com/how-sql-injection-attack-works-with-examples/">SQL Injection issues</a>.</p><h3 id="notrequired-for-typeddict"><code>NotRequired</code> for <code>TypedDict</code></h3><p>This feature is already common in TypeScript.</p><p>Consider you can define one key&apos;s dictionary with either:</p><ul><li>One value as potentially null (already supported)</li><li>One <strong>key</strong> as potentially <strong>missing</strong> (new in Python 3.11)</li></ul><p>See a few examples of how it would work for a dict with three keys:</p><ol><li><code>blog</code> as required key and required value</li><li><code>twitter</code> as required key (but optional value)</li><li><code>instagram</code> as optional key (but required value)</li></ol><pre><code class="language-py">from typing import NotRequired, TypedDict

class MediaLinks(TypedDict):
    blog: str
    twitter: str | None # &#x1F448; Key is required, value can be either null or str
    instagram: NotRequired[str] # &#x1F448; Key is not required, but if set it should be str


# &#x1F7E2; Valid
links_with_instagram: MediaLinks = {&quot;blog&quot;: &quot;Gui Commits&quot;, &quot;twitter&quot;: &quot;@guilatrova&quot;, &quot;instagram&quot;: &quot;non existent&quot;}
links_without_instagram: MediaLinks = {&quot;blog&quot;: &quot;Gui Commits&quot;, &quot;twitter&quot;: None}


# &#x1F534; Invalid
links1: MediaLinks = {&quot;blog&quot;: &quot;Gui Commits&quot;, &quot;twitter&quot;: &quot;@guilatrova&quot;, &quot;instagram&quot;: None}
# 1) &apos;instagram&apos; can&apos;t be None. Mypy output:
# Incompatible types (expression has type &quot;None&quot;, TypedDict item &quot;instagram&quot; has type &quot;str&quot;)

links2: MediaLinks = {&quot;blog&quot;: &quot;Gui Commits&quot;}
# 2) &apos;twitter&apos; is expected. Mypy output:
# Missing key &quot;twitter&quot; for TypedDict &quot;MediaLinks&quot;

links3: MediaLinks = {&quot;blog&quot;: &quot;Gui Commits&quot;, &quot;twitter&quot;: &quot;@guilatrova&quot;, &quot;youtube&quot;: &quot;Someday?&quot;}
# 3) &apos;youtube&apos; isn&apos;t expected. Mypy output:
# Extra key &quot;youtube&quot; for TypedDict &quot;MediaLinks&quot;
</code></pre><p>Remember that <strong>Python validates nothing for you</strong>.</p><p>You must use Mypy to guarantee your typings are correct.</p><h3 id="%F0%9F%92%A1-how-to-use-new-types-before-python-311-is-released">&#x1F4A1; How to use new types before Python 3.11 is released?</h3><p>Did you know you can start using <code>NotRequired</code>, <code>Self</code>, and <code>LiteralString</code> types already?</p><p>You just have to install <code>typing_extensions</code>:</p><p>&#x1F449; <code>pip install typing_extensions</code></p><p>&#x1F481;&#x200D;&#x2642;&#xFE0F; Impot types from <code>typing_extensions</code> and that&apos;s it.</p><h2 id="5%EF%B8%8F%E2%83%A3-performance-improvement">5&#xFE0F;&#x20E3; Performance Improvement</h2><p>Python 3.11 is on average 25% faster than 3.10.</p><p>Due to improvements on CPython providing faster startup and faster runtime.</p><h2 id="6%EF%B8%8F%E2%83%A3-new-default-module-tomllib">6&#xFE0F;&#x20E3; New default module: <code>tomllib</code></h2><p>Now Python supports reading <code>toml</code> by default:</p><pre><code class="language-py">import tomllib

toml_str = &quot;&quot;&quot;
[build]
python-version = &quot;3.11.0&quot;
python-implementation = &quot;CPython&quot;

[tool.poetry.dev-dependencies]
tryceratops = &quot;^1.1.0&quot;
&quot;&quot;&quot;

data = tomllib.loads(toml_str)
print(data)
</code></pre><p><code>Toml</code> is very common in <code>pyproject.toml</code> files. See <a href="https://github.com/guilatrova/tryceratops/blob/main/pyproject.toml">this real file from the Tryceratops project</a>.</p><figure class="kg-card kg-embed-card"><blockquote class="twitter-tweet"><p lang="en" dir="ltr">When releasing your Python code to production&#x1F40D;<br><br>Keep your PRODUCTION and DEV dependencies split!<br><br>Use something powerful like Poetry from <a href="https://twitter.com/SDisPater?ref_src=twsrc%5Etfw">@SDisPater</a><br><br>&#x2022; poetry add [PROD_DEPENDENCY]<br>&#x2022; poetry add -D [DEV_DEPENDENCY]<br><br>(No, you can&apos;t rage pip install) <a href="https://t.co/T0V0ODjtWc">pic.twitter.com/T0V0ODjtWc</a></p>&#x2014; Gui Latrova (@guilatrova) <a href="https://twitter.com/guilatrova/status/1541013419943264256?ref_src=twsrc%5Etfw">June 26, 2022</a></blockquote>
<script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>
</figure><h2 id="7%EF%B8%8F%E2%83%A3-strenum">7&#xFE0F;&#x20E3; StrEnum</h2><p>New type <code>StrEnum</code> with <code>auto()</code> support so you don&apos;t have to type</p><pre><code class="language-py">from enum import StrEnum, auto

class Status(StrEnum):
    ON_HOLD = auto()
    IN_PROGRESS = auto()
    ON_REVIEW = auto()
    DONE = auto()


print(Status.ON_HOLD) # Output: on_hold
print(Status.ON_HOLD == &quot;on_hold&quot;) # Output: True
</code></pre><h2 id="now-that-you-know-about-python-311-changes">Now that you know about Python 3.11 changes</h2><p>If you enjoyed this article did you know you can receive such updates in a shorter/funnier way?</p><p>Check out this &#x1F447; thread and consider <a href="https://twitter.com/intent/user?screen_name=guilatrova">following @guilatrova</a> &#x1F609;</p><figure class="kg-card kg-embed-card"><blockquote class="twitter-tweet"><p lang="en" dir="ltr">Hey, do you know the major changes from &#x1F40D; Python 3.11 (Beta) so far?<br><br>Here&apos;s a funny/short thread to keep you updated!<br><br>&#x2705; Summary<br><br>1. Performance improvement<br>2. Better error messages<br>3. Exception groups<br>4. Exception add_note()<br>5. New type hints<br>6. New standard module<br><br>1/10 <a href="https://t.co/TOg3GPtunN">pic.twitter.com/TOg3GPtunN</a></p>&#x2014; Gui Latrova (@guilatrova) <a href="https://twitter.com/guilatrova/status/1536302385298931712?ref_src=twsrc%5Etfw">June 13, 2022</a></blockquote>
<script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>
</figure>]]></content:encoded></item><item><title><![CDATA[Building a Blockchain with Python 🐍⛓️]]></title><description><![CDATA[Blockchain, Mining, and Proof of work explained using Python code that you can run from your browser.]]></description><link>https://guicommits.com/building-blockchain-with-python/</link><guid isPermaLink="false">625e7a234f52c9070e65068f</guid><category><![CDATA[🐍 Python]]></category><category><![CDATA[interactive]]></category><category><![CDATA[Blockchain]]></category><dc:creator><![CDATA[Guilherme Latrova]]></dc:creator><pubDate>Fri, 20 May 2022 11:18:51 GMT</pubDate><media:content url="https://guicommits.com/content/images/2022/04/Blog-Post-Building-a-blockchain.png" medium="image"/><content:encoded><![CDATA[<img src="https://guicommits.com/content/images/2022/04/Blog-Post-Building-a-blockchain.png" alt="Building a Blockchain with Python &#x1F40D;&#x26D3;&#xFE0F;"><p>Always asked yourself how blockchain works? Well, ask no more. We&#x2019;re going to implement a simple version of a Blockchain written in Python interactively. &#x1F448; <strong>It means that you will be able to run the code straight from your browser.</strong></p><p>With just <strong>enough theory and a high focus on practice</strong>.</p><p>Note that if you&apos;re reading this article in AMP mode or from mobile you won&apos;t be able to run Python code from your browser, but you can still see the code samples and illustrations.</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://guicommits.com/content/images/2022/04/pythonworking.gif" class="kg-image" alt="Building a Blockchain with Python &#x1F40D;&#x26D3;&#xFE0F;" loading="lazy" width="1373" height="751"><figcaption>Example of executing Python interactively</figcaption></figure><p>The purpose here is to <strong>give you &#x201C;enough&#x201D; theory so it doesn&#x2019;t get boring</strong> and so we take baby steps towards learning about blockchains.</p><h2 id="%E2%9B%93%EF%B8%8F-what-is-a-blockchain">&#x26D3;&#xFE0F; What is a blockchain?</h2><p>Blockchain is the technology that powers Bitcoin, Ethereum, and Crypto. <strong>It&#x2019;s a public ledger that guarantees information is decentralized.</strong></p><p>For Bitcoin it means transactions are records and that each record is compliant and secure in the ledger.</p><p><strong>It literally manages a chain of blocks, and each block contains many transactions.</strong></p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://guicommits.com/content/images/2022/04/blockchain-overview.png" class="kg-image" alt="Building a Blockchain with Python &#x1F40D;&#x26D3;&#xFE0F;" loading="lazy" width="777" height="266" srcset="https://guicommits.com/content/images/size/w600/2022/04/blockchain-overview.png 600w, https://guicommits.com/content/images/2022/04/blockchain-overview.png 777w" sizes="(min-width: 720px) 720px"><figcaption>A chain of blocks</figcaption></figure><p>Blocks can be identified by two means: <strong>height or hash</strong>. The hash is long and complex while height is quite easy since it&apos;s just the block number in the chain.</p><p>In our example above it&apos;s fine to say that the block of height 1 is also the block of hash <code>6b86</code>.</p><p>The first block ever is called <strong>the Genesis Block &#x1F331;</strong>, it&apos;s a special block with a single transaction (50 BTC to Satoshi Nakamoto).</p><p>What makes it special is that <strong>it&apos;s not tied to any real previous block, that&apos;s the only and unique exception to the Blockchain</strong>.</p><p>Since the blockchain is public and open, you can check and <a href="https://blockexplorer.one/bitcoin/mainnet/blockId/1">see the genesis block here</a> (or any other block really).</p><p>I&apos;m going to abstract how transactions are validated, so we can focus on the blockchain itself. We&apos;re assuming that every transaction is legit and needs no more than <em>&quot;from who&quot;</em>, <em>&quot;to who&quot;</em> and <em>&quot;how much&quot;</em>.</p><p>If you&apos;re interested in knowing more, I&apos;ll be happy to make a part two, and implement transaction details, validation, and some cryptography (yet again, interactively!).</p><p>Here&apos;re the entities <code>Block</code> and <code>Transaction</code> we&apos;re creating for the scope of this article:</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://guicommits.com/content/images/2022/04/blockchain-entities.png" class="kg-image" alt="Building a Blockchain with Python &#x1F40D;&#x26D3;&#xFE0F;" loading="lazy" width="993" height="609" srcset="https://guicommits.com/content/images/size/w600/2022/04/blockchain-entities.png 600w, https://guicommits.com/content/images/2022/04/blockchain-entities.png 993w" sizes="(min-width: 720px) 720px"><figcaption>Blockchain Entities&#xA0;</figcaption></figure><!--kg-card-begin: markdown--><details>
  <summary>See as Python Code &#x1F40D;</summary>
<pre><code class="language-python">from dataclasses import dataclass


@dataclass
class Transaction:
  sender: str
  receiver: str
  amount: float


@dataclass
class Block:
  version: int
  timestamp: float
  nonce: int
  difficulty: int
  previous_hash: str
  transactions: list[Transaction]
</code></pre>
</details><!--kg-card-end: markdown--><hr><h2 id="%E2%9B%8F%EF%B8%8F-what-is-bitcoin-mining">&#x26CF;&#xFE0F; What is Bitcoin mining?</h2><p>Bitcoin mining is what keeps the network consistent and working. That&apos;s what allows people to send and receive bitcoins.</p><p>Let&apos;s analyze first how the economy works with banks.</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://guicommits.com/content/images/2022/04/central-authority.png" class="kg-image" alt="Building a Blockchain with Python &#x1F40D;&#x26D3;&#xFE0F;" loading="lazy" width="1402" height="372" srcset="https://guicommits.com/content/images/size/w600/2022/04/central-authority.png 600w, https://guicommits.com/content/images/size/w1000/2022/04/central-authority.png 1000w, https://guicommits.com/content/images/2022/04/central-authority.png 1402w" sizes="(min-width: 720px) 720px"><figcaption>A central authority is in charge of validating and moving money</figcaption></figure><p>Inevitably there&apos;s a central authority (the bank) that makes things happen:</p><ul><li>Validates you have enough balance to send money;</li><li>Moves money between accounts;</li><li>Attempt to detect frauds and block them;</li></ul><p>Since Bitcoin proposes to be completely decentralized (thus we can&apos;t have a single central authority) <strong>it needs to create a way so people collaborate and replace the need for banks.</strong></p><p>In simple words: <em>&quot;Mining&quot;</em> is the process that allows people to use bitcoin as a decentralized currency and that incentivizes people to collaborate.</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://guicommits.com/content/images/2022/04/decentralized-authority.png" class="kg-image" alt="Building a Blockchain with Python &#x1F40D;&#x26D3;&#xFE0F;" loading="lazy" width="2000" height="657" srcset="https://guicommits.com/content/images/size/w600/2022/04/decentralized-authority.png 600w, https://guicommits.com/content/images/size/w1000/2022/04/decentralized-authority.png 1000w, https://guicommits.com/content/images/size/w1600/2022/04/decentralized-authority.png 1600w, https://guicommits.com/content/images/2022/04/decentralized-authority.png 2016w" sizes="(min-width: 720px) 720px"><figcaption>Decentralized authorities communicate between themselves to validate</figcaption></figure><p>Instead of a single entity, we have many different entities that communicate between themselves, they&apos;re in charge of checking whether John has enough balance, and if he does, they&apos;re in charge of moving the money straight to Mary.</p><h3 id="%F0%9F%94%92-why-blockchain-and-bitcoin-are-secure">&#x1F512; Why Blockchain and Bitcoin are secure?</h3><p>Ok, let&apos;s be honest here. If you gave me $10,000 to deliver it to your friend</p><p><strong>would you trust me?</strong></p><p>Include in this question many different people I don&apos;t know, and an even bigger amount of money and this question gets even more obvious.</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://media.giphy.com/media/3tIUHyA2spHMRPwCnB/giphy.gif" class="kg-image" alt="Building a Blockchain with Python &#x1F40D;&#x26D3;&#xFE0F;" loading="lazy"><figcaption>Me not trusting you</figcaption></figure><p>If a malicious miner is trying to insert invalid blocks or even remove a block from the Blockchain, <strong>he&apos;s rejected by all due to the consensus of the network!</strong> Attempting to modify the Blockchain is a waste of time and effort.</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://guicommits.com/content/images/2022/04/hacker-mining.png" class="kg-image" alt="Building a Blockchain with Python &#x1F40D;&#x26D3;&#xFE0F;" loading="lazy" width="690" height="336" srcset="https://guicommits.com/content/images/size/w600/2022/04/hacker-mining.png 600w, https://guicommits.com/content/images/2022/04/hacker-mining.png 690w"><figcaption>Bad actors are inevitably isolated from the Blockchain</figcaption></figure><p>That&apos;s what makes Bitcoin and Blockchain an awesome combination. <strong>It forces miners to be honest and (implicitly) punishes those who aren&apos;t.</strong></p><h3 id="%F0%9F%92%B0-why-miners-help-the-blockchain">&#x1F4B0; Why miners help the Blockchain</h3><p>But still, how the hell can you get people to contribute? I mean... Why would you do all this work? Why people would want to help?</p><p>For a single reason:</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://media.giphy.com/media/xT0xeiQCDFiJEu2vXG/giphy.gif" class="kg-image" alt="Building a Blockchain with Python &#x1F40D;&#x26D3;&#xFE0F;" loading="lazy"><figcaption>Money</figcaption></figure><p><strong>Money!</strong></p><p>And the money comes from 2 sources:</p><ul><li>Bitcoin mining rewards</li><li>Transaction fees</li></ul><p>Every mined block automatically rewards the miner with some bitcoin. The number of Bitcoins earned decreases over time. </p><p>This process is called <em>&quot;Bitcoin Halving&quot; and </em>every ~4 years rewards are cut by half.</p><h3 id="%F0%9F%93%86-bitcoin-halving-dates">&#x1F4C6; Bitcoin Halving Dates</h3><!--kg-card-begin: markdown--><table>
<thead>
<tr>
<th>Year</th>
<th>Block Height</th>
<th>Block reward</th>
</tr>
</thead>
<tbody>
<tr>
<td>2009</td>
<td>1</td>
<td>50 BTC</td>
</tr>
<tr>
<td>2012</td>
<td>210,000</td>
<td>25 BTC</td>
</tr>
<tr>
<td>2016</td>
<td>420,000</td>
<td>12.5 BTC</td>
</tr>
<tr>
<td>&#x1F6A9; 2020 (We&apos;re here)</td>
<td>630,000</td>
<td>6.25 BTC</td>
</tr>
<tr>
<td>2024</td>
<td>840,000</td>
<td>3.125 BTC</td>
</tr>
</tbody>
</table>
<!--kg-card-end: markdown--><p>It means that (as of February 2022) <strong>miners earn 6.25 BTC for every mined block</strong> and that by 2024 they will be making only 3.125 BTC.</p><p>Note that <strong>rewards are created from nothing</strong>, analogous to some central authority printing money.</p><p>This process goes on until the rewards become 0 (around the year 2140). We say Bitcoin is a <strong>deflationary currency</strong> because it has a maximum limit of bitcoins that can be generated (a total of 21 million) enforced by its algorithm.</p><h3 id="%F0%9F%92%B1-transaction-fees">&#x1F4B1; Transaction fees </h3><p>Transaction fees are another incentive for miners to do their job. For every transaction, you include<strong> some extra amount</strong> so the miner can take it as <strong>his fee</strong>.</p><p>Take as an example John and Mary:</p><ul><li>John wants to send 0.050 BTC to Mary</li><li><strong>John sends 0.051 BTC where <u>only 0.050 BTC is addressed</u> to Mary</strong></li><li>A miner receives John&apos;s transaction and <strong><u>notices 0.001 BTC is unaddressed</u></strong></li><li>The miner takes 0.001 BTC as his fee and proceeds to transfer 0.050 BTC to Mary</li><li>If the miner mines it, he makes an additional 6.25 BTC as a reward</li></ul><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://guicommits.com/content/images/2022/04/mining-fee.png" class="kg-image" alt="Building a Blockchain with Python &#x1F40D;&#x26D3;&#xFE0F;" loading="lazy" width="913" height="292" srcset="https://guicommits.com/content/images/size/w600/2022/04/mining-fee.png 600w, https://guicommits.com/content/images/2022/04/mining-fee.png 913w" sizes="(min-width: 720px) 720px"><figcaption>John sends 0.051, the Miner can take 0.001 as fee + the block reward</figcaption></figure><h2 id="%F0%9F%90%8D%E2%9B%8F%EF%B8%8F-proof-of-work-with-python-example">&#x1F40D;&#x26CF;&#xFE0F; Proof of Work with Python Example</h2><p><strong>Enough theory already.</strong> Let&apos;s see some code.</p><p>What deserves your attention from our Entities here is Block&apos;s <strong><code>nonce</code></strong>, <strong><code>difficulty</code></strong>, and <strong><code>previous_hash</code></strong> properties since most of the <code>Transaction</code> complexity will be abstracted in this article.</p><!--kg-card-begin: markdown--><details>
  <summary>See as Python Code &#x1F40D;</summary>
<pre><code class="language-python">from dataclasses import dataclass


@dataclass
class Transaction:
  sender: str
  receiver: str
  amount: float


@dataclass
class Block:
  version: int
  timestamp: float
  nonce: int
  difficulty: int
  previous_hash: str
  transactions: list[Transaction]
</code></pre>
</details><!--kg-card-end: markdown--><p><code><strong>Nonce</strong></code> means <code><strong>N</strong></code><strong>umber only used </strong><code><strong>once</strong></code> and that&apos;s the magic number every computer is fighting to find. They pick any transactions they want from a pool and then compete to decide who finds the nonce, so they can mine the block, and receive the reward of doing so.</p><p><code><strong>Difficulty</strong></code> is the value defined by the network that makes it harder (or easier under certain circumstances) to find a valid <code>nonce</code>.</p><p><strong><code>Previous Hash</code></strong> simply points to the previous block, so you can&apos;t move blocks out of order, making it a chain of sequential blocks.</p><p><strong>Note that if ANY information inside a block changes it will produce a completely different hash.</strong></p><p>With all the block&apos;s properties (version, <em>timestamp*&#xB9;</em>, previous hash, <em>transactions*&#xB2;</em>, <em>difficulty*&#xB9;</em>, and <em>nonce*&#xB9;</em>) you can calculate the block hash.</p><p><em>*1 All these fields are required for the mining process to work.</em></p><p><em>*2 The transactions field in this article is a simplification of the </em><code><em>merkle root</em></code><em>, we can discuss it more in-depth over the next articles if you guys show interest.</em></p><p>But hey, it&apos;s starting to get boring again &#x1F62A;. </p><p>Let&apos;s try it together so we can have some fun.</p><p>Let&apos;s pretend we&apos;re willing to add a new block to the Blockchain after the block <code>68ffd1</code>, which means <strong>we need to define the hash of our new block</strong>.</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://guicommits.com/content/images/2022/04/append-block-sample.png" class="kg-image" alt="Building a Blockchain with Python &#x1F40D;&#x26D3;&#xFE0F;" loading="lazy" width="577" height="280"><figcaption>Appending block after 503</figcaption></figure><pre><code class="language-python">import json
from hashlib import sha256
from datetime import datetime
from dataclasses import asdict

HEX_BASE_TO_NUMBER = 16
DIFFICULTY = 1
PREV_BLOCK_HASH = &quot;68ffd13b24f9d73399a80aad9de06f676001fed56648314526cd23a4d18fef16&quot;
TRANSACTIONS = (
    Transaction(&quot;sender1&quot;, &quot;receiver1&quot;, 1),
    Transaction(&quot;sender2&quot;, &quot;receiver2&quot;, 0.5),
    Transaction(&quot;sender3&quot;, &quot;receiver3&quot;, 2.7),
)

def create_block(nonce: int, difficulty: int, previous_hash=PREV_BLOCK_HASH) -&gt; Block:
    cur_timestamp = datetime.now().timestamp()
    return Block(
        version=1,
        timestamp=cur_timestamp,
        nonce=nonce,
        previous_hash=previous_hash,
        difficulty=difficulty,
        transactions=TRANSACTIONS
    )

def encode_block(block: Block) -&gt; str:
    encoded_block = json.dumps(asdict(block)).encode()
    return sha256(sha256(encoded_block).digest()).hexdigest()

def calculate_hash(nonce: int) -&gt; str:
    block = create_block(nonce, DIFFICULTY)
    encoded_block_hash = encode_block(block)
    return encoded_block_hash


result = calculate_hash(nonce=1)
print(&quot;Block hash as hex:   &quot;, result)
print(&quot;Block hash as number: {:,}&quot;.format(int(result, HEX_BASE_TO_NUMBER)))
</code></pre><p>Please, run the code above and you&apos;re going to receive as output the block hash like:</p><p><code>9caed6c391d31790d8409804ae3cb41b19951b6f76fa21958b2be35dab38ed09</code></p><p>and the respective decimal number like:</p><p><code>70,869,718,014,525,929,260,730,978,</code><br><code>743,189,498,965,715,300,689,702,298,</code><br><code>738,657,158,148,123,721,788,681</code></p><p>Don&apos;t be afraid of running it many times. <strong>Every run will produce a different output, and that&apos;s because the variables (i.e. timestamp) change every second!</strong>.</p><h2 id="%F0%9F%92%AA-what-is-proof-of-work-and-blockchain-mining">&#x1F4AA; What is Proof of Work and Blockchain mining?</h2><p>The code you run above was quite fast, and that&apos;s a problem.</p><p><strong>The Bitcoin network enforces blocks to be mined every ~10 minutes</strong> otherwise we would mine too many blocks which would produce too many bitcoins as rewards.</p><p>Blockchain ensures it won&apos;t be a problem by adjusting the <code>difficulty</code> field with bits.</p><p>Having a <code>difficulty</code> set to <strong>15 bits</strong> generates a <code>difficulty target</code> of <code>3,533,694,129,556,768,659,166,</code><br><code>595,001,485,837,031,654,967,793,</code><br><code>751,237,916,243,212,402,585,239,552</code>.</p><p>We can represent this calculation as:</p><pre><code class="language-python">BYTE_IN_BITS = 256
DIFFICULTY = 15  # &#x1F448; Play with this number! Make it bigger or smaller

def calculate_difficulty_target(difficulty_bits: int) -&gt; int:
    return 2 ** (BYTE_IN_BITS - difficulty_bits)


decimal_target = calculate_difficulty_target(DIFFICULTY)

print(&quot;&#x2757; Any mined block hash needs to be lesser than: {:,}&quot;.format(decimal_target))
</code></pre><p><strong>Such values restrain miners from mining blocks too quickly. They need to keep mining and changing the </strong><code><strong>nonce</strong></code><strong> until they get a block hash that is lesser than the difficulty target</strong>. And this often takes around 10 minutes.</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://guicommits.com/content/images/2022/04/difficulty-bar.png" class="kg-image" alt="Building a Blockchain with Python &#x1F40D;&#x26D3;&#xFE0F;" loading="lazy" width="517" height="124"><figcaption>The higher the difficulty the longer it takes to mine a block&#xA0;</figcaption></figure><p>Try yourself with Python. See how long it takes for you to find a random hash with a simplified variation (no timestamp, no Merkle tree, etc) that is lesser than the difficulty target:</p><pre><code class="language-python">import time
from datetime import timedelta, datetime
from dataclasses import asdict


BYTE_IN_BITS = 256
HEX_BASE_TO_NUMBER = 16
SECONDS_TO_EXPIRE = 20
DIFFICULTY = 10  # &#x1F448; Play with this number! Make it bigger or smaller


def calculate_difficulty_target(difficulty_bits: int) -&gt; int:
    return 2 ** (BYTE_IN_BITS - difficulty_bits)


decimal_target = calculate_difficulty_target(DIFFICULTY)


def mine_proof_of_work(nonce: int) -&gt; tuple[bool, Block]:
    block = create_block(nonce, DIFFICULTY)
    encoded_block = encode_block(block)
    block_encoded_as_number = int(encoded_block, HEX_BASE_TO_NUMBER)

    if block_encoded_as_number &lt; decimal_target:
        return True, block

    return False, block


print(&quot;&#x2757; Any mined block hash needs to be lesser than: {:,}&quot;.format(decimal_target))

nonce = 0
start_time = time.time()
found = False

while not found:
    found, block = mine_proof_of_work(nonce)

    if not found:
        print(f&quot;&#x274C; Nonce {nonce} didn&apos;t meet the difficulty target...&quot;)
        nonce += 1

    end_time = time.time()
    elapsed_time = end_time - start_time

    if elapsed_time &gt; SECONDS_TO_EXPIRE:
        raise TimeoutError(
            f&quot;Couldn&apos;t find a block with difficulty {DIFFICULTY} fast enough&quot;
        )

print(f&quot;&#x2705; Nonce {nonce} meet difficulty target, you mined the block in {timedelta(seconds=elapsed_time)}!&quot;)
print()
print(&quot;Here&apos;s the winning block: &#x1F9F1;&quot;)
print(asdict(block))</code></pre><p>You may notice it takes longer as you increase the difficulty bits, that&apos;s why this process is called <strong>&quot;Proof of Work&quot;</strong>.</p><p><strong>It&apos;s easier to validate the block, but hard to find the winning nonce!</strong> If node A receives a correct block hash, <strong>we know for sure that node A did the work (thus <em>proof of work</em>!)</strong>.</p><p>Also, every node is frequently adjusting the difficulty to ensure blocks will take ~10 minutes to be mined.</p><p>If it&apos;s too slow &#x1F422;, then the network decreases &#x1F4C9; the difficulty like <a href="https://www.cnbc.com/2021/07/03/bitcoin-mining-difficulty-drops-after-hashrate-collapse-in-china-.html">when China banned Bitcoin mining</a>.</p><p>The reverse is also true.</p><p>If it&apos;s too fast &#x1F4A8;, then the network increases &#x1F4C8; the difficulty.</p><p>You can see the difficulty over time here: <a href="https://www.blockchain.com/charts/difficulty">https://www.blockchain.com/charts/difficulty</a>.</p><p>A simple version of a blockchain could be represented as:</p><pre><code class="language-python">import time
from datetime import timedelta, datetime


BYTE_IN_BITS = 256
HEX_BASE_TO_NUMBER = 16
SECONDS_TO_EXPIRE = 20


GENESIS_BLOCK = Block(
    version=1,
    timestamp=1231476865,
    difficulty=1,
    nonce=1,
    previous_hash=&quot;000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f&quot;,
    transactions=(Transaction(&quot;Satoshi Nakamoto&quot;, &quot;Satoshi Nakamoto&quot;, 50),)
)


def calculate_difficulty_target(difficulty_bits: int) -&gt; int:
    return 2 ** (BYTE_IN_BITS - difficulty_bits)


class Blockchain:
    VERSION = 1
    DIFFICULTY = 10
    MINUTES_TOLERANCE = 1

    def __init__(self):
        self.chain = [GENESIS_BLOCK]

    def get_last_block(self) -&gt; Block:
        return self.chain[-1]

    def get_difficulty(self) -&gt; int:
        return self.DIFFICULTY

    def add_block(self, block: Block) -&gt; bool:
        is_valid = self._validate_block(block)
        if is_valid:
            self.chain.append(block)

        return is_valid

    def _validate_block(self, candidate: Block) -&gt; bool:
        if candidate.version != self.VERSION:
            return False

        last_block = self.get_last_block()
        if candidate.previous_hash != encode_block(last_block):
            return False
        
        if candidate.difficulty != self.DIFFICULTY:
            return False

        min_allowed_time = (datetime.now() - timedelta(minutes=self.MINUTES_TOLERANCE)).timestamp()
        if candidate.timestamp &lt; min_allowed_time:
            return False

        candidate_hash = encode_block(candidate)
        candidate_decimal = int(candidate_hash, HEX_BASE_TO_NUMBER)

        target = calculate_difficulty_target(self.DIFFICULTY)
        is_block_valid = candidate_decimal &lt; target

        return is_block_valid


blockchain = Blockchain()


def mine_proof_of_work(nonce: int, difficulty: int, prev_hash: str) -&gt; tuple[bool, Block]:
    block = create_block(nonce, difficulty, prev_hash)
    encoded_block = encode_block(block)
    block_encoded_as_number = int(encoded_block, HEX_BASE_TO_NUMBER)
    decimal_target = calculate_difficulty_target(difficulty)

    if block_encoded_as_number &lt; decimal_target:
        return True, block

    return False, block


nonce = 0
start_time = time.time()
found = False
prev_hash = encode_block(blockchain.get_last_block())

while not found:
    found, block = mine_proof_of_work(nonce, blockchain.get_difficulty(), prev_hash)

    if found:
        blockchain.add_block(block)
    else:
        print(f&quot;&#x274C; Nonce {nonce} didn&apos;t meet the difficulty target...&quot;)
        nonce += 1

    end_time = time.time()
    elapsed_time = end_time - start_time

    if elapsed_time &gt; SECONDS_TO_EXPIRE:
        raise TimeoutError(
            f&quot;Couldn&apos;t find a block with difficulty {blockchain.get_difficulty()} fast enough&quot;
        )

print(
    f&quot;&#x2705; Nonce {nonce} meet difficulty target, you mined the block in {timedelta(seconds=elapsed_time)}!&quot;
)
print(&quot;Here&apos;s the the blockchain: &#x26D3;&#xFE0F;&quot;)
print(blockchain.chain)</code></pre><h2 id="%F0%9F%8F%86-what-is-a-blockchain-fork">&#x1F3C6; What is a blockchain fork?</h2><p>Notice that <strong>every single miner is trying to find the next block</strong> with the winning nonce, but <strong>only one can earn it</strong>, and it&apos;s whoever finds it first!</p><p>Consider 3 miners trying to find the block 504:</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://guicommits.com/content/images/2022/04/competition.png" class="kg-image" alt="Building a Blockchain with Python &#x1F40D;&#x26D3;&#xFE0F;" loading="lazy" width="680" height="218" srcset="https://guicommits.com/content/images/size/w600/2022/04/competition.png 600w, https://guicommits.com/content/images/2022/04/competition.png 680w"><figcaption>3 miners struggling to find the winning Nonce for block 504</figcaption></figure><p>That&apos;s why it&apos;s so important to have the most potent hardware, so you can find it faster!</p><p>If miner B finds it, it quickly propagates block 504 to the whole network, which is an implicit way of telling your fellow miners: <em>&quot;Sorry, you lost&quot;</em>.</p><p>All miners then quickly attempt to mine the next one (In our example: 505).</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://guicommits.com/content/images/2022/04/miningwinner.png" class="kg-image" alt="Building a Blockchain with Python &#x1F40D;&#x26D3;&#xFE0F;" loading="lazy" width="628" height="229" srcset="https://guicommits.com/content/images/size/w600/2022/04/miningwinner.png 600w, https://guicommits.com/content/images/2022/04/miningwinner.png 628w"><figcaption>Miner B found the nonce, Miners A and C keep trying the next block</figcaption></figure><p>But hey, if it&apos;s a competition, <strong>what would happen if two nodes find the same block at the same time?</strong></p><p>We call this event fork (see the picture below to understand why). Now it gets truly fun, they&apos;re competing for the reward and no other node in the network knows for sure who will win!</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://guicommits.com/content/images/2022/04/fork-conflict.png" class="kg-image" alt="Building a Blockchain with Python &#x1F40D;&#x26D3;&#xFE0F;" loading="lazy" width="643" height="298" srcset="https://guicommits.com/content/images/size/w600/2022/04/fork-conflict.png 600w, https://guicommits.com/content/images/2022/04/fork-conflict.png 643w"><figcaption>Mining fork caused by Miners A and C</figcaption></figure><p>Note that Miner B doesn&apos;t care who wins... Actually, other miners don&apos;t care either (yes, they&apos;re selfish) so whoever adds the new block to any path will persist.</p><p>This leads to another blockchain rule: <strong>The longest valid path is the source of truth!</strong></p><p>A few nodes will try to append a new Block 506 after Block 505-C.</p><p>Other nodes will try to append a new Block 506 after Block 505-A.</p><p>The winning path becomes the valid chain. The losing group discards their old chain and moves into mining the next block.</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://guicommits.com/content/images/2022/04/fork-winner.png" class="kg-image" alt="Building a Blockchain with Python &#x1F40D;&#x26D3;&#xFE0F;" loading="lazy" width="655" height="274" srcset="https://guicommits.com/content/images/size/w600/2022/04/fork-winner.png 600w, https://guicommits.com/content/images/2022/04/fork-winner.png 655w"><figcaption>Eventually, the fork is dissolved and the longest path wins</figcaption></figure><p><strong>All nodes that believed 505-A had a chance just lost their time and energy. As soon as Block 506 is added on top of 505-C it then becomes the valid chain.</strong></p><p>Yes, blockchains can be cold like that. No mercy.</p><h3 id="%E2%9C%85-blockchain-confirmations">&#x2705; Blockchain confirmations</h3><p>That&apos;s why people often wait for at least 2 confirmation blocks to validate a Bitcoin transaction (<a href="https://help.coinbase.com/en/coinbase/trading-and-funding/sending-or-receiving-cryptocurrency/why-is-my-transaction-pending">like Coinbase shows a transaction between Pending and Complete</a>).</p><p>They want to ensure that your transaction won&apos;t be reverted due to some temporary fork.</p><h2 id="%F0%9F%A5%A3-what-are-mining-pools">&#x1F963; What are Mining Pools?</h2><p>The blockchain world is cold and painful out there, miners need emotional support to face such hard moments.</p><p>Everything isn&apos;t about just competing, miners can team up as well! <strong>We call &quot;Mining Pools&quot; when a group of miners teams up to find the winning nonce and they all split the rewards between themselves!</strong></p><p>Now instead of individual miners competing against each other, we have big mining pools competing against each other.</p><p>After all, it&apos;s hard for a few miners to compete against big mining farms:</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://guicommits.com/content/images/2022/04/mining-farm.webp" class="kg-image" alt="Building a Blockchain with Python &#x1F40D;&#x26D3;&#xFE0F;" loading="lazy" width="1200" height="800" srcset="https://guicommits.com/content/images/size/w600/2022/04/mining-farm.webp 600w, https://guicommits.com/content/images/size/w1000/2022/04/mining-farm.webp 1000w, https://guicommits.com/content/images/2022/04/mining-farm.webp 1200w" sizes="(min-width: 720px) 720px"><figcaption>Mining farm</figcaption></figure><p>You can see some mining pools for Bitcoin here: <a href="https://btc.com/stats/pool">https://btc.com/stats/pool</a></p><p><strong>Joining a big mining pool means you have more power to find blocks faster, which also means you need to split your rewards with more miners.</strong></p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://guicommits.com/content/images/2022/04/mining-pool.jpeg" class="kg-image" alt="Building a Blockchain with Python &#x1F40D;&#x26D3;&#xFE0F;" loading="lazy" width="882" height="495" srcset="https://guicommits.com/content/images/size/w600/2022/04/mining-pool.jpeg 600w, https://guicommits.com/content/images/2022/04/mining-pool.jpeg 882w" sizes="(min-width: 720px) 720px"><figcaption>Extracted from https://btc.com/stats/pool</figcaption></figure><hr><p>I truly hope you enjoyed reading this far!</p><p>Most concepts and learnings were extracted from <a href="https://amzn.to/37TeTkj"><strong>the book Mastering Bitcoin</strong></a>.</p><p><strong>I&apos;m on the journey of making learning fun and interactive for everybody.</strong> </p><p>If you enjoyed reading this and if you&apos;re curious to understand how transactions can be validated in the network, <a href="https://twitter.com/messages/compose?recipient_id=992147115227975686">let me know</a>. I gauge interest to decide on what to write next.</p>]]></content:encoded></item><item><title><![CDATA[How to use yield in Python]]></title><description><![CDATA[Learn what Python yield does by running the commands right in this article! Learn what is generators and when to use them effectively.]]></description><link>https://guicommits.com/python-yield-examples/</link><guid isPermaLink="false">623652374f52c9070e6502c5</guid><category><![CDATA[🐍 Python]]></category><category><![CDATA[interactive]]></category><dc:creator><![CDATA[Guilherme Latrova]]></dc:creator><pubDate>Thu, 24 Mar 2022 10:29:56 GMT</pubDate><media:content url="https://guicommits.com/content/images/2022/03/Blog-Post-Python-Generators.png" medium="image"/><content:encoded><![CDATA[<img src="https://guicommits.com/content/images/2022/03/Blog-Post-Python-Generators.png" alt="How to use yield in Python"><p>There&#x2019;s nothing scarier than a code that you don&#x2019;t understand. A few years ago I got concerned every time that I spotted a <code>yield</code> command. It seemed to be returning something... Somehow it worked even though I never used that command before. Is it like <code>return</code>?</p><p>Well, they&#x2019;re similar, but work differently for sure! You&apos;re about to learn how <code>yield</code> works and what the hell are generators in a <u><strong>FUN WAY</strong></u>.</p><!--kg-card-begin: html--><p>You&apos;ll be able to <a href="#" onclick="toggleCodeSpace()">run the python commands</a> right away from this blog as we progress so you can not only understand but <strong>SEE how it works</strong>.</p><!--kg-card-end: html--><p>(It might be disabled if reading on AMP mode or on small screens though. If that&apos;s the case you might want to come back to this article later.)</p><p>You can open the Python environment by either hitting <code>ctrl</code> + <code>&apos;</code> &#xA0;or manually clicking on the bar at the bottom of this blog.</p><p>Executing the Python code can be achieved by either <code>ctrl</code> + <code>Enter</code> / <code>cmd</code> + <code>Enter</code>, or just click Run.</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://guicommits.com/content/images/2022/03/pythonworking.gif" class="kg-image" alt="How to use yield in Python" loading="lazy" width="1373" height="751"><figcaption>Example of executing Python interactively</figcaption></figure><hr><h2 id="what-does-yield-do-in-python">What does <code>yield</code> do in Python?</h2><p>Any <code>yield</code> command automatically makes your code return a generator object. <strong>Generators are functions that can be paused and resumed.</strong></p><p>Try this:</p><figure class="kg-card kg-code-card"><pre><code class="language-python">def mygenerator():
    n = 1
    yield n
    n += 2
    yield n

print(mygenerator())
</code></pre><figcaption>By just using yield we create generators</figcaption></figure><p>And you&#x2019;re going to see something like: <code>&lt;generator object mygenerator at 0xd91df0&gt;</code> as your output.</p><p>Not much impressive right? That&apos;s because <strong>we didn&apos;t use the generator object just yet</strong>. Let&#x2019;s see what&#x2019;s inside the generator we just returned. </p><p>Please, wrap the object with <code>next</code>:</p><pre><code class="language-python">def mygenerator():
    n = 1
    yield n
    n += 2
    yield n

print(next(mygenerator()))
</code></pre><p>Now you should see <code>1</code> &#xA0;as the result. </p><p>You&apos;re probably familiar with the <code>return</code> command and this would be the expected output if any function attempted to return twice: The first <code>return</code> interrupts the function and returns its results.</p><p>As we discussed before, <strong>this is not true for generators</strong>, further <code>yield</code> commands along the way are <strong>on hold</strong>.</p><p>Let&apos;s make it obvious by adding prints, and by forcing it to run fully with <code>list</code>:</p><pre><code class="language-python">def mygenerator():
    n = 1
    print(&quot;A short pause&quot;)
    yield n
    
    print(&quot;The pause is over!&quot;)
    n += 2
    yield n

print(list(mygenerator()))
</code></pre><p>You should see all our prints, and all yield values ( <code>[1, 3]</code>).</p><p>Cool right? </p><p>Unfortunately, it was not so obvious to see the &quot;pauses&quot; happening. Let&apos;s be a bit more verbose now by iterating over the generator, and doing calculations <strong><u>before</u> the generator finishes</strong>:</p><pre><code class="language-python">def mygenerator():
    print(&quot;Gen: Yielding 1&quot;)
    yield 1

    print(&quot;Gen: Yielding 2&quot;)
    yield 2
    
    print(&quot;Gen: Yielding 3&quot;)
    yield 3


gen = mygenerator()
for n in gen:
    print(f&quot;For: I got {n}&quot;)
    print(&quot;For: Since generator is paused, I can do some calculation&quot;)
    print(f&quot;For: {n} * 10 = {n * 10}&quot;) 
    print()
</code></pre><p><strong>Note how we can calculate (multiply by ten) even before the generator finishes processing!</strong></p><p>My proposal here is to make things stupidly obvious. So here&apos;s how it would differ from a regular function, try this and check the output:</p><pre><code class="language-python">def myregularfunc():
    ret = []
    for n in range(1, 4):
    	print(f&quot;Fun: Preparing {n}&quot;)
    	ret.append(n)

    print(&quot;Returning full list&quot;)
    return ret
    
ret = myregularfunc()
print()
for n in ret:
    print(f&quot;For: I got {n}&quot;)
    print(f&quot;For: {n} * 10 = {n * 10}&quot;) 
    print()</code></pre><p>Note how inevitably we have to wait for <code>myregularfunc</code> to end in order to do anything we need with its results.</p><h2 id="%F0%9F%8F%B7-python-type-hint-for-generators">&#x1F3F7; Python type hint for Generators</h2><p>Do you want to find out how to type hint your generator functions? Easy!</p><figure class="kg-card kg-code-card"><pre><code class="language-python">from typing import Iterator, Iterable, Generator

def mygen1() -&gt; Iterator[int]:
    &quot;&quot;&quot;Iterator is fine&quot;&quot;&quot;
    yield 1
    
def mygen2() -&gt; Iterable[int]:
    &quot;&quot;&quot;Iterable is equally fine&quot;&quot;&quot;
    yield 2

def mygenverbose() -&gt; Generator[str, None, None]:
    &quot;&quot;&quot;
    Verbose way of defining a generator, it receives 3 types:
    (1) Yield type
    (2) Send type (if set, otherwise None)
    (3) Return type (if returning at the end, otherwise None)    
    &quot;&quot;&quot;
    yield &quot;That&apos;s verbose&quot;

print(mygen1)
print(mygen2)
print(mygenverbose)</code></pre><figcaption>According to <a href="https://docs.python.org/3/library/typing.html#typing.Generator">Python docs</a></figcaption></figure><p>I&apos;m personally used to <code>Generator[type, None, None]</code> for keeping things explicit. Feel free to pick whatever you prefer.</p><h2 id="%F0%9F%94%82-generators-of-generators">&#x1F502; Generators of generators</h2><p>There&apos;re also moments where we want to generate from generators. Using just <code>yield</code> won&apos;t work, see:</p><pre><code class="language-python">from typing import Generator

def starting_five() -&gt; Generator[int, None, None]:
    &quot;&quot;&quot;Generator that returns integers from 1-5&quot;&quot;&quot;
    for n in range(1, 6):
        yield n
        
        
def ending_five() -&gt; Generator[int, None, None]:
    &quot;&quot;&quot;Generator that returns integers from 6-10&quot;&quot;&quot;
    for n in range(6, 11):
        yield n
        
        
def all_ten() -&gt; Generator[int, None, None]:
    &quot;&quot;&quot;Generator that relies on other generators&quot;&quot;&quot;
    yield starting_five() # This is broken
    yield ending_five()  # This is broken
    
    
print(list(all_ten()))</code></pre><p>By doing this you&apos;re creating a generator that returns generator objects (The type hint would be something like: <code>Generator[Generator[int, None, None], None, None]</code>) <strong>which is not what we want!</strong></p><p>To get it working you have to use <code>yield from</code>! </p><p><strong>Fix it yourself! </strong>Replace the bare <code>yield ...</code> from <code>all_ten</code> with <code>yield from ...</code></p><p>And you should have the expected result: <code>[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]</code></p><h1 id="%E2%8F%B1-python-generators-use-cases">&#x23F1; Python Generators Use Cases</h1><p>I enjoy bringing to you <strong>real examples </strong>that I actually use, and not just a bunch of syntax. So here&apos;re 3 times where I had and still have to deal with generators:</p><h2 id="1%EF%B8%8F%E2%83%A3-to-make-your-code-cleaner">1&#xFE0F;&#x20E3; &#xA0;To make your code cleaner</h2><p>Have you realized that functions that build lists to return need more lines? You need to set up the list, append, and then return it. <a href="https://amzn.to/3bEVHpG">The Effective Python book</a> recommends generators whenever you need to return a list or iterable.</p><pre><code class="language-python">from typing import List, Generator


def regularfunc() -&gt; List[int]:
    &quot;&quot;&quot;This works and it&apos;s fine.&quot;&quot;&quot;
    ret = []
    for n in range(10):
        ret.append(n * 10)
        
    return ret
    

def smallergen() -&gt; Generator[int, None, None]:
    &quot;&quot;&quot;This also works. Favor this one.&quot;&quot;&quot;
    for n in range(10):
        yield n * 10
        
        
# Intended Usage:
print(&quot;Regular func:&quot;, regularfunc())
print(&quot;Smaller gen:&quot;, list(smallergen()))</code></pre><h2 id="2%EF%B8%8F%E2%83%A3-to-read-large-files-and-save-memory">2&#xFE0F;&#x20E3; &#xA0;To read large files (and save memory)</h2><p><strong>Once again: Generators are functions that can be paused and resumed.</strong></p><p>It means that when you need to load a <strong>big file like a CSV</strong> <strong>you can read it line by line,</strong> instead of loading the whole thing and wasting all your memory.</p><p>Check:</p><pre><code class="language-python">def create_dummy_file():
    with open(&quot;loadme.csv&quot;, &quot;w&quot;) as f:
        f.write(&quot;Num,Name\n&quot;)
    
        for n in range(10):
            f.write(f&quot;{n},John Doe\n&quot;)
            
            
def read_the_whole_file():
    with open(&quot;loadme.csv&quot;, &quot;r&quot;) as f:
        return f.readlines()


def read_line_by_line():
    with open(&quot;loadme.csv&quot;, &quot;r&quot;) as f: 
        for row in f:
            yield row
            

create_dummy_file()

# Bad idea when file is big:
csvcontent = read_the_whole_file()
for row in csvcontent:
    # Even though we only use one line, it already loaded all lines
    print(&quot;Eager load:&quot;, row)

# Good idea when file is big:
csvcontent = read_line_by_line()
for row in csvcontent:
    # only loads a row to process
    print(&quot;Lazy load:&quot;, row)</code></pre><p>Even though the output is the same, whenever you&apos;re reading a file (that might be big) and requires processing, stick to generators. It may save you from some <code>MemoryError</code>s.</p><h2 id="3%EF%B8%8F%E2%83%A3-when-writing-tests-with-pytest">3&#xFE0F;&#x20E3; &#xA0;When writing tests with Pytest</h2><p><a href="https://docs.pytest.org/en/6.2.x/fixture.html">Pytest has the concept of fixtures</a> (out of the scope from this article so I&apos;ll be short), and there are commands that you want to be executed before <strong>AND after a test</strong>. See:</p><pre><code class="language-python">FILECONTENT = &quot;&quot;&quot;
import pytest

@pytest.fixture
def notifyonlybeforefixture():
    print(&apos;Before the test ends&apos;)
    
    
@pytest.fixture
def notifycompletefixture():
    print(&apos;Before the test ends&apos;)
    yield # trick!
    print(&apos;After the test ends&apos;)
    


def test_fixtures_before(notifyonlybeforefixture):
    print(&quot;During test&quot;)
    assert False
    
    
def test_fixtures_complete(notifycompletefixture):
    print(&quot;During test&quot;)
    assert False
&quot;&quot;&quot;

# Note: This is only needed so you can test from your browser.
with open(&quot;mytest.py&quot;, &quot;w&quot;) as f:
    f.write(FILECONTENT)


import pytest
pytest.main([&quot;-v&quot;, &quot;--cache-clear&quot;, &quot;mytest.py&quot;])</code></pre><p>Since you&apos;re running Python from your browser, there&apos;s no default file. We have to generate one named <code>mytest.py</code> , so we can execute <code>pytest</code> on it. I hope this quirck won&apos;t make it too hard for you to understand. </p><p>Note how just adding a <code>yield</code> inside our <code>notifycompletefixture</code> function is enough to let <code>pytest</code> know it&apos;s ok to execute the test, and once it&apos;s completed, get back and resume from where it was left.</p><p>That&apos;s valuable when you need to tear down things (maybe close a file, clear database state, or similar).</p><p>By the way, <strong>do you know what else can be paused and resumed?</strong> Python Async. If you don&apos;t know what I&apos;m talking about you should totally <a href="https://guicommits.com/async-python-in-real-life/">read this article</a>.</p><hr><p>Thank you for reading this far! I deeply appreciate your time and I hope you had fun playing with Python without having to leave the browser! </p><p>That was my first attempt to make Python learning fun and less boring. <strong>It would be an honor for me to hear your feedback: How was it?</strong></p><!--kg-card-begin: html--><a href="https://twitter.com/messages/compose?recipient_id=3805104374" class="twitter-dm-button" data-screen-name="@guilatrova">Message @guilatrova</a><!--kg-card-end: html-->]]></content:encoded></item><item><title><![CDATA[Abstract your code]]></title><description><![CDATA[Abstraction makes your code flexible and decoupled from vendors. It's quite easy to follow, yet is constantly ignored. See how to avoid this trap.]]></description><link>https://guicommits.com/abstract-your-code/</link><guid isPermaLink="false">61f1d6334f52c9070e650272</guid><category><![CDATA[🐍 Python]]></category><dc:creator><![CDATA[Guilherme Latrova]]></dc:creator><pubDate>Wed, 26 Jan 2022 23:41:37 GMT</pubDate><media:content url="https://guicommits.com/content/images/2022/01/cover.png" medium="image"/><content:encoded><![CDATA[<img src="https://guicommits.com/content/images/2022/01/cover.png" alt="Abstract your code"><p>Implementation abstraction makes your code <strong>flexible</strong> and <strong>decoupled</strong> from vendors or hard implementations, and finally, it&apos;s quite <strong>easy</strong> to follow, yet <strong>is constantly ignored.</strong></p><p>This post would fit perfectly in a series named &#x201C;<em>Coding Practices that should be obvious, but for some unknown reason aren&apos;t</em>&#x201D;.</p><p>You might think that I&apos;m about to mention interfaces, but not really. Python has no &quot;interface&quot; and you can still follow the principle to make your life (and hopefully yours colleagues too) easier.</p><p><strong>Every time that you write a piece of code that is very specific on the &#x201C;how-to&#x201D; and not the &#x201C;what-to&#x201D; you&apos;re cursed with code hard to change and evolve (sometimes called &#x201C;legacy code&#x201D;).</strong></p><p>Over time I noticed this simple concept being ignored over and over again, people leaking details when:</p><ul><li>&#x201C;querying database&#x201D; &#x274C; instead of <strong>querying for an entity</strong></li><li>&#x201C;reading stream from Kafka&#x201D; &#x274C; instead of <strong>listening to events</strong></li><li>&#x201C;popping SQS messages&#x201D; &#x274C; instead of <strong>receiving messages</strong></li><li>&#x201C;pushing a function to celery&#x201D; &#x274C; instead of <strong>scheduling a job</strong></li></ul><p>I&apos;m about to show you one obvious example of where it might happen, and how to prevent it.</p><p>Just think about a Flask API that allows users to upload purchase receipts to store them in the cloud.</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://guicommits.com/content/images/2022/01/intro.png" class="kg-image" alt="Abstract your code" loading="lazy" width="1304" height="362" srcset="https://guicommits.com/content/images/size/w600/2022/01/intro.png 600w, https://guicommits.com/content/images/size/w1000/2022/01/intro.png 1000w, https://guicommits.com/content/images/2022/01/intro.png 1304w" sizes="(min-width: 720px) 720px"><figcaption>Flask API allows uploading files to S3</figcaption></figure><p>Here&apos;s the code split into two files <code>main.py</code> and <code>s3.py</code>, let&apos;s see them:</p><figure class="kg-card kg-code-card"><pre><code class="language-py"># Located under: s3.py
class S3Storage:
    def store(self, bucket: str, key: str, file: bytes):
        # Implementation details of using botocore to upload bytes to S3
        ...

    def retrieve_obj(self, bucket: str, key: str) -&gt; S3Object:
        # Note it returns a specific &quot;S3 Object&quot; &#x1F446;
        # Implementation details of using botocore to read data from S3
        ...
</code></pre><figcaption>The initial version of s3.py</figcaption></figure><figure class="kg-card kg-code-card"><pre><code class="language-py"># Located under: main.py
from s3 import S3Storage
...

s3_storage = S3Storage()
s3_bucket = &quot;purchase-receipts&quot;
s3_key = &quot;submissions/receipt.jpeg&quot;


@app.route(&apos;/purchase-receipts&apos;, methods=[&apos;POST&apos;])
def submit_receipt(receipt_upload: FileStorage):
    s3_content = receipt_upload.read()
    s3_storage.store(s3_bucket, s3_key, s3_content)

    return &quot;OK&quot;


@app.route(&apos;/purchase-receipts&apos;)
def get_receipt():
    s3_obj = s3_storage.retrieve_obj(s3_bucket, s3_key)
    content = s3_obj.response[&apos;Body&apos;].read()

    return content
</code></pre><figcaption>The initial version of main.py</figcaption></figure><p>What annoys me most is that the implementation details are leaked all over the place to the caller. The way we defined our &quot;Storage&quot; class makes it impossible for the client to don&apos;t be overloaded with AWS terms: &quot;Bucket name&quot;, &quot;Key&quot;, <code>S3Storage</code> class, and finally the return type <code>S3Object</code> makes our code completely coupled to the implementation details!</p><p><strong>Your code should only be tied to the business purpose.</strong></p><h2 id="%F0%9F%92%A6-leaking-implementation-details-is-bad">&#x1F4A6; Leaking implementation details is bad</h2><p>To make this statement obvious let&apos;s say that you recently received a lifetime 80% discount to use GCP to store such data, you pitched it to your team, and it sounds like a decent discount for the long term.</p><p>The appropriate solution that I&apos;d expect would be to just replace the <code>S3Storage</code> implementation and don&apos;t even touch the <code>main.py</code> file that contains our API code.</p><figure class="kg-card kg-code-card"><pre><code class="language-py"># Located under: s3.py
class S3Storage:
    def store(self, bucket: str, key: str, file: bytes):
        # Implementation details of using botocore to upload bytes to S3
        ...

    def retrieve_obj(self, bucket: str, key: str) -&gt; S3Object:
        # Implementation details of using botocore to read data from S3
        ...

# you probably would need to rename the file as well to gcp.py for consistency
# &#x1F447; Would become something like this
class GCPStorage:
    def store(self, bucket: str, blob_name: str, blob: bytes):
        # Implementation details of using gcp sdk to upload bytes to GCP
        ...

    def retrieve_obj(self, bucket: str, key: str) -&gt; GCPObject:
        # Implementation details of using gcp sdk to read data from GCP
        ...
</code></pre><figcaption>The second version of s3.py (or maybe gcp.py?)</figcaption></figure><p>Unfortunately, just modifying this file won&apos;t be enough because we&apos;re leaking all these details to <code>main.py</code> which forces us to rename variables and make the change bigger and the review process longer and more error-prone.</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://guicommits.com/content/images/2022/01/locked.png" class="kg-image" alt="Abstract your code" loading="lazy" width="1362" height="654" srcset="https://guicommits.com/content/images/size/w600/2022/01/locked.png 600w, https://guicommits.com/content/images/size/w1000/2022/01/locked.png 1000w, https://guicommits.com/content/images/2022/01/locked.png 1362w" sizes="(min-width: 720px) 720px"><figcaption>Business tightly tied to the implementation details</figcaption></figure><h2 id="%F0%9F%8E%AF-focus-on-the-business-value">&#x1F3AF; Focus on the business value</h2><p>This problem gets easier when we focus on <strong>what</strong> problem we&apos;re solving regardless of <strong>how</strong>.</p><p>Let&apos;s rewrite the code, but this time, focusing on the problem.</p><figure class="kg-card kg-code-card"><pre><code class="language-py"># Now located under: storages.py
class PurchaseReceiptsStorage:  # &#x1F448; Do not leak &quot;HOW&quot; we&apos;re storing it
    def store(self, filename: str, file: bytes):  # &#x1F448; Only asks for the absolutely necessary
        # Implementation details for either S3, GCP or whatever
        ...

    def retrieve_receipt(self, filename: str) -&gt; bytes:  # &#x1F448; Only asks for the absolutely necessary
        # Return some data type that can be easily consumed by the client regardless of details &#x1F446;
        # Implementation details for either S3, GCP or whatever
        ...</code></pre><figcaption>Final conversion from s3.py to storages.py</figcaption></figure><figure class="kg-card kg-code-card"><pre><code class="language-py">from storages import PurchaseReceiptsStorage  # &#x1F448; Changes shouldn&apos;t modify this

storage = PurchaseReceiptsStorage()  # &#x1F448; Business value clear
filename = &quot;receipt.jpeg&quot;

@app.route(&apos;/purchase-receipts&apos;, methods=[&apos;POST&apos;])
def submit_receipt(receipt_upload: FileStorage):
    content = receipt_upload.read()
    storage.store(filename, content)  # &#x1F448; Things that are expected for the caller to know

    return &quot;OK&quot;


@app.route(&apos;/purchase-receipts&apos;)
def get_receipt():
    content = storage.retrieve_receipt(filename)  # &#x1F448; Returns a known common type not tied to any lib

    return content
</code></pre><figcaption>main.py file focused on the business</figcaption></figure><p>Any change or maintenance that you need to do over the new <code>PurchaseReceiptsStorage</code> is pretty much agnostic. We&apos;re just storing a file, the client shouldn&apos;t care how. <strong>It&apos;s none of his business.</strong></p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://guicommits.com/content/images/2022/01/free-1.png" class="kg-image" alt="Abstract your code" loading="lazy" width="1490" height="408" srcset="https://guicommits.com/content/images/size/w600/2022/01/free-1.png 600w, https://guicommits.com/content/images/size/w1000/2022/01/free-1.png 1000w, https://guicommits.com/content/images/2022/01/free-1.png 1490w" sizes="(min-width: 720px) 720px"><figcaption>Business value free from annoying details</figcaption></figure><ul><li>For storing we only provide a default filename, and the content itself.</li><li>For reading, we just specify the filename.</li><li>It&apos;s expected for the storage class to <strong>internally</strong> understand that &quot;<em>Since I manage the storage of receipts, I know which bucket, cloud, API, etc to use</em>&quot;.</li></ul><figure class="kg-card kg-embed-card"><blockquote class="twitter-tweet"><p lang="en" dir="ltr">Do you want to code a feature that might change?<br><br>Yes, it always changes.<br><br>Abstract your code from implementation details and you&apos;ll be good. <a href="https://t.co/gWJKAE0r3P">pic.twitter.com/gWJKAE0r3P</a></p>&#x2014; Gui Latrova (@guilatrova) <a href="https://twitter.com/guilatrova/status/1461348635661066240?ref_src=twsrc%5Etfw">November 18, 2021</a></blockquote>
<script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>
</figure>]]></content:encoded></item><item><title><![CDATA[AWS Organizations with Terraform Workspaces]]></title><description><![CDATA[How to use AWS Organizations to your advantage with meaningful examples, and how to use terraform to manage it and replicate resources across them.]]></description><link>https://guicommits.com/why-use-aws-organizations-with-terraform/</link><guid isPermaLink="false">619d0aae4f52c9070e650148</guid><category><![CDATA[DevOps]]></category><category><![CDATA[terraform]]></category><category><![CDATA[aws]]></category><dc:creator><![CDATA[Guilherme Latrova]]></dc:creator><pubDate>Thu, 25 Nov 2021 11:37:59 GMT</pubDate><media:content url="https://guicommits.com/content/images/2021/11/Cover.png" medium="image"/><content:encoded><![CDATA[<img src="https://guicommits.com/content/images/2021/11/Cover.png" alt="AWS Organizations with Terraform Workspaces"><p>There are three boring things in life that DevOps engineers need to do:</p><ol><li>Grant the correct set of permissions per dev, so they don&apos;t explore more than they should, and don&apos;t have less than they have to;</li><li>Replicate resources across environments;</li><li>Watch out for costs;</li></ol><p>And for all of those, having AWS Organizations with terraform workspaces is the way to go.</p><p>I&apos;m about to show you <strong>how to use AWS Organizations to your advantage with meaningful examples</strong>, and <strong>how to use terraform to manage it and replicate resources</strong> across them.</p><p>This post you&apos;re reading assumes you know the minimal about AWS and Terraform. I&apos;ll mention things like IAM, SQS, and show some terraform code.</p><h2 id="%F0%9F%97%84%EF%B8%8F-what-are-aws-organizations">&#x1F5C4;&#xFE0F; What are AWS Organizations?</h2><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://guicommits.com/content/images/2021/11/orgs-1-1.png" class="kg-image" alt="AWS Organizations with Terraform Workspaces" loading="lazy" width="1200" height="628" srcset="https://guicommits.com/content/images/size/w600/2021/11/orgs-1-1.png 600w, https://guicommits.com/content/images/size/w1000/2021/11/orgs-1-1.png 1000w, https://guicommits.com/content/images/2021/11/orgs-1-1.png 1200w" sizes="(min-width: 720px) 720px"><figcaption>AWS Organizations</figcaption></figure><p>It&apos;s an AWS account that is defined as an organization and that manages children AWS accounts. The parent (or root) account is then responsible for paying the bills of these accounts.</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://guicommits.com/content/images/2021/11/sample.png" class="kg-image" alt="AWS Organizations with Terraform Workspaces" loading="lazy" width="1914" height="1112" srcset="https://guicommits.com/content/images/size/w600/2021/11/sample.png 600w, https://guicommits.com/content/images/size/w1000/2021/11/sample.png 1000w, https://guicommits.com/content/images/size/w1600/2021/11/sample.png 1600w, https://guicommits.com/content/images/2021/11/sample.png 1914w" sizes="(min-width: 720px) 720px"><figcaption>AWS Organizations sample</figcaption></figure><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://guicommits.com/content/images/2021/11/orgs-projects-sample.png" class="kg-image" alt="AWS Organizations with Terraform Workspaces" loading="lazy" width="1902" height="1118" srcset="https://guicommits.com/content/images/size/w600/2021/11/orgs-projects-sample.png 600w, https://guicommits.com/content/images/size/w1000/2021/11/orgs-projects-sample.png 1000w, https://guicommits.com/content/images/size/w1600/2021/11/orgs-projects-sample.png 1600w, https://guicommits.com/content/images/2021/11/orgs-projects-sample.png 1902w" sizes="(min-width: 720px) 720px"><figcaption>AWS Organizations with expanded accounts sample</figcaption></figure><h3 id="%E2%9C%A8-benefits-of-aws-organizations">&#x2728; Benefits of AWS organizations</h3><p>A well structured setup makes a lot of sense for:</p><ul><li>Different environments (i.e. staging and production)</li><li>Specific projects that may use many resources and that require observation</li><li>Isolated customer projects</li></ul><p>By default, a setup for different projects will allow you to have:</p><p><strong>Safer configuration</strong> of resources since they can&apos;t touch each other unless you explicitly allow it to. Also, it&apos;s a good incentive for you to create more VPCs, Roles, etc, instead of reusing the same.</p><p><strong>Avoid resource naming hell</strong> by not creating, for example, 3 RDS instances named <code>main-database-dev</code>, <code>main-database-staging</code>, and <code>main-database-production</code>, and by not connecting services from different environments by mistake to them. All of them can perfectly have the exact same name <code>main-database</code> living on their isolated organization.</p><p><strong>Consolidated billing</strong> of accounts, so you can understand which customer of yours consumes more resources, how much it costs you to keep a staging environment, and so on.</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://guicommits.com/content/images/2021/11/orgs-costs.png" class="kg-image" alt="AWS Organizations with Terraform Workspaces" loading="lazy" width="2000" height="1021" srcset="https://guicommits.com/content/images/size/w600/2021/11/orgs-costs.png 600w, https://guicommits.com/content/images/size/w1000/2021/11/orgs-costs.png 1000w, https://guicommits.com/content/images/size/w1600/2021/11/orgs-costs.png 1600w, https://guicommits.com/content/images/2021/11/orgs-costs.png 2072w" sizes="(min-width: 720px) 720px"><figcaption>Billing allows you to filter by account</figcaption></figure><h2 id="%F0%9F%94%93-how-do-permissions-and-access-across-accounts-work">&#x1F513; How do permissions and access across accounts work?</h2><p>I&apos;ll use a realistic example from past places I worked.</p><p>It&apos;s common for companies to have at least three environments, and so it&apos;s good to keep them split as different organizations:</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://guicommits.com/content/images/2021/11/orgs-envs-2.png" class="kg-image" alt="AWS Organizations with Terraform Workspaces" loading="lazy" width="1200" height="628" srcset="https://guicommits.com/content/images/size/w600/2021/11/orgs-envs-2.png 600w, https://guicommits.com/content/images/size/w1000/2021/11/orgs-envs-2.png 1000w, https://guicommits.com/content/images/2021/11/orgs-envs-2.png 1200w" sizes="(min-width: 720px) 720px"><figcaption>AWS Organizations per environment</figcaption></figure><!--kg-card-begin: html--><table>
<thead>
  <tr>
    <th>Environment</th>
    <th>Motivation</th>
  </tr>
</thead>
<tbody>
  <tr>
    <td>dev</td>
    <td>Environment with constant changes and testing, very unstable.</td>
  </tr>
  <tr>
    <td>staging</td>
    <td>Slightly stabler environment, used frequently for testing and release candidates.</td>
  </tr>
  <tr>
    <td>production</td>
    <td>Stable environment, this is the one customers use and consume.</td>
  </tr>
</tbody>
</table><!--kg-card-end: html--><p><strong>Since the organizations are different accounts with their own set of IAM, RDS, VPCs, etc</strong> the only way for the root account to interact with any of the children accounts is by <strong>assuming a role</strong>.</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://guicommits.com/content/images/2021/11/orgs-role-3.png" class="kg-image" alt="AWS Organizations with Terraform Workspaces" loading="lazy" width="1200" height="628" srcset="https://guicommits.com/content/images/size/w600/2021/11/orgs-role-3.png 600w, https://guicommits.com/content/images/size/w1000/2021/11/orgs-role-3.png 1000w, https://guicommits.com/content/images/2021/11/orgs-role-3.png 1200w" sizes="(min-width: 720px) 720px"><figcaption>AWS Roles can limit access to resources and organizations</figcaption></figure><p>Note that you can, easily, create custom IAM users inside each new account as you would normally do. <strong>I don&apos;t recommend that approach because:</strong></p><ul><li>Now you have to watch out for many different user accounts (i.e. all of them are rotating their secrets? Do they have MFA enabled? etc)</li><li>Other engineers have many access key/access secrets</li></ul><p>It&apos;s way easier (and consequently safer) to manage such access through roles because then <strong>you can limit which user from the root account can assume what role from the child account</strong>.</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://guicommits.com/content/images/2021/11/orgs-access-2.png" class="kg-image" alt="AWS Organizations with Terraform Workspaces" loading="lazy" width="1200" height="628" srcset="https://guicommits.com/content/images/size/w600/2021/11/orgs-access-2.png 600w, https://guicommits.com/content/images/size/w1000/2021/11/orgs-access-2.png 1000w, https://guicommits.com/content/images/2021/11/orgs-access-2.png 1200w" sizes="(min-width: 720px) 720px"><figcaption>AWS IAM set to allow John to access staging resources as an admin&#xA0;</figcaption></figure><p>As soon as you created a managed child account, you need to deal with roles and permissions.</p><p>It might be hard to visualize all the permissions in place. Let&apos;s break it down to keep things simple:</p><p><strong>1&#xFE0F;&#x20E3; Root account: Permission to assume a role in the Staging account</strong></p><pre><code class="language-json">{
    &quot;Version&quot;: &quot;2012-10-17&quot;,
    &quot;Statement&quot;: [
        {
            &quot;Sid&quot;: &quot;&quot;,
            &quot;Effect&quot;: &quot;Allow&quot;,
            &quot;Action&quot;: &quot;sts:AssumeRole&quot;,
            // &#x1F447; 000000000000 represents the child AWS account
            &quot;Resource&quot;: &quot;arn:aws:iam::000000000000:role/adm-role&quot;
            // &#x1F446; &apos;adm-role&apos; the role that lives inside Staging account
        }
    ]
}
</code></pre><p>If you prefer <code>terraform</code>, here&apos;s the creation of an IAM group that has the permissions mentioned above:</p><pre><code class="language-tf"># 1&#xFE0F;&#x20E3; Set up some variables for organization
locals {
    # ROOT account
    group_name = &quot;staging-developers&quot;
    policy_name = &quot;staging-access&quot;
    iam_path = &quot;/&quot;

    # PROJECT account
    child_aws_account = &quot;000000000000&quot;
    role_in_child_aws_account = &quot;adm-role&quot;

}

# 2&#xFE0F;&#x20E3; Create a group that can access staging
resource &quot;aws_iam_group&quot; &quot;staging_group&quot; {
  name = local.group_name
  path = local.iam_path
}

# 3&#xFE0F;&#x20E3; Defines what can be done on what/where
data &quot;aws_iam_policy_document&quot; &quot;staging_access_spec&quot; {
  statement {
    actions = [
      &quot;sts:AssumeRole&quot;,  # &#x1F448; You can &quot;AssumeRole&quot;
    ]

    # &#x1F447; Upon this resource (i.e. inside this AWS account, this role)
    resources = [&quot;arn:aws:iam::${local.child_aws_account}:role/${local.role_in_child_aws_account}&quot;]
  }
}

# 4&#xFE0F;&#x20E3; &#x1F447; For the group we just created, attach the policy we just defined
resource &quot;aws_iam_group_policy&quot; &quot;staging_group_policy&quot; {
  name  = local.policy_name
  group = aws_iam_group.staging_group.name

  policy = data.aws_iam_policy_document.staging_access_spec.json
}

</code></pre><p><strong>2&#xFE0F;&#x20E3; Staging account: <code>adm-role</code> permissions to resources and trusted relationships</strong></p><p><em>Note: By default, AWS creates the exact definitions below when you create a child org.</em></p><p>Let&apos;s state that since it&apos;s staging, it&apos;s fine to be permissive. This role contains an admin policy granting access to everything.</p><pre><code class="language-json">{
    &quot;Version&quot;: &quot;2012-10-17&quot;,
    &quot;Statement&quot;: [
        {
            &quot;Effect&quot;: &quot;Allow&quot;,
            &quot;Action&quot;: &quot;*&quot;,
            &quot;Resource&quot;: &quot;*&quot;
        }
    ]
}
</code></pre><p>But we limit who can assume that role! The trusted entity allows just one specific AWS account to do that:</p><pre><code class="language-json">{
  &quot;Version&quot;: &quot;2012-10-17&quot;,
  &quot;Statement&quot;: [
    {
      &quot;Effect&quot;: &quot;Allow&quot;,
      &quot;Principal&quot;: {
        &quot;AWS&quot;: &quot;arn:aws:iam::000000000000:root&quot;  // &#x1F448; Where 0... is the full account id
      },
      &quot;Action&quot;: &quot;sts:AssumeRole&quot;
    }
  ]
}
</code></pre><p>We can narrow down the permissions by having many roles in a production account and allowing only a subset of users to assume specific roles.</p><h2 id="%F0%9F%9B%82-accessing-aws-child-accounts-from-root-account">&#x1F6C2; Accessing AWS child accounts from root account</h2><p>It might sound that it would be extremely boring or slow to access such accounts. Turns out that once you understand how the permissions works (as explained above), it becomes simple. See:</p><h3 id="%F0%9F%AA%96-set-up-aws-console-to-access-child-account">&#x1FA96; Set up AWS console to access child account</h3><p>To do that, you need to be logged as an IAM user (i.e. you can&apos;t do this with a root user).</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://guicommits.com/content/images/2021/11/orgs-console-1.png" class="kg-image" alt="AWS Organizations with Terraform Workspaces" loading="lazy" width="920" height="1016" srcset="https://guicommits.com/content/images/size/w600/2021/11/orgs-console-1.png 600w, https://guicommits.com/content/images/2021/11/orgs-console-1.png 920w" sizes="(min-width: 720px) 720px"><figcaption>AWS Console options to switch role</figcaption></figure><p>Then you&apos;re free to access children accounts by filling the form:</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://guicommits.com/content/images/2021/11/orgs-console-2.png" class="kg-image" alt="AWS Organizations with Terraform Workspaces" loading="lazy" width="1512" height="1006" srcset="https://guicommits.com/content/images/size/w600/2021/11/orgs-console-2.png 600w, https://guicommits.com/content/images/size/w1000/2021/11/orgs-console-2.png 1000w, https://guicommits.com/content/images/2021/11/orgs-console-2.png 1512w" sizes="(min-width: 720px) 720px"><figcaption>AWS Console form to assume a new role for another account</figcaption></figure><p>And finally, you must see the console again, but this time it&apos;s another account:</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://guicommits.com/content/images/2021/11/orgs-console-3.png" class="kg-image" alt="AWS Organizations with Terraform Workspaces" loading="lazy" width="1026" height="1186" srcset="https://guicommits.com/content/images/size/w600/2021/11/orgs-console-3.png 600w, https://guicommits.com/content/images/size/w1000/2021/11/orgs-console-3.png 1000w, https://guicommits.com/content/images/2021/11/orgs-console-3.png 1026w" sizes="(min-width: 720px) 720px"><figcaption>AWS Orgs look when you assume a new role</figcaption></figure><h3 id="%F0%9F%AA%96%F0%9F%8E%96%EF%B8%8F-pro-tip-use-an-extension-to-skip-the-form">&#x1FA96;&#x1F396;&#xFE0F; Pro Tip: Use an extension to skip the form</h3><p>Even though the above method is easy, it&apos;s quite boring.</p><p>Instead, I recommend using the extension <a href="https://github.com/tilfinltd/aws-extend-switch-roles">AWS Extend Switch Roles extension</a> (available for Chrome, Firefox, and Edge).</p><p>It allows you to switch between roles easily, so you&apos;re always one click away to impersonate any organization:</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://guicommits.com/content/images/2021/11/orgs-console-extension.png" class="kg-image" alt="AWS Organizations with Terraform Workspaces" loading="lazy" width="1518" height="612" srcset="https://guicommits.com/content/images/size/w600/2021/11/orgs-console-extension.png 600w, https://guicommits.com/content/images/size/w1000/2021/11/orgs-console-extension.png 1000w, https://guicommits.com/content/images/2021/11/orgs-console-extension.png 1518w" sizes="(min-width: 720px) 720px"><figcaption>Switch Roles extension in action</figcaption></figure><p>The example configuration would be like this:</p><pre><code class="language-ini">[Staging Account]
aws_account_id = 000000000000
role_name = admin-role
region = us-east-1

</code></pre><h3 id="%E2%9B%91%EF%B8%8F-setup-aws-cli-to-assume-the-role">&#x26D1;&#xFE0F; Setup AWS CLI to assume the role</h3><p>Accessing such accounts through the CLI is even easier, and no, you don&apos;t have to manually run <code>aws sts assume-role</code>.</p><p>Here is the setup for your <code>.aws/credentials</code>:</p><pre><code class="language-ini"># &#x1F447; Usual setup of a regular user
[root-account]
aws_access_key_id = AKIAXXXXXXXXXXXXXXXX
aws_secret_access_key = XXXXXXXXXXXXXXXX

# &#x1F447; Define aws organization
[staging-account]
role_arn = arn:aws:iam::000000000000:role/admin-role # &#x1F448; Which role to assume?
source_profile = root-account # &#x1F448; Use the above profile to assume this role
</code></pre><p>So anytime you do <code>export AWS_PROFILE=staging-account</code>, your AWS CLI will automatically assume the role for you and give access to the resources you should have. Pretty cool uh?</p><p>Check it by yourself:</p><pre><code class="language-json">&#x276F; export AWS_PROFILE=staging-account
&#x276F; aws sts get-caller-identity

// Output
{
    &quot;UserId&quot;: &quot;AROATLMRPSWKBES5PAFXV:botocore-session-1637767588&quot;,
    &quot;Account&quot;: &quot;000000000000&quot;,
    &quot;Arn&quot;: &quot;arn:aws:sts::000000000000:assumed-role/admin-role/botocore-session-1637767588&quot;
}
</code></pre><h2 id="%F0%9F%AA%90-replicating-resources-with-terraform-workspaces">&#x1FA90; Replicating resources with Terraform workspaces</h2><p>Since now you have different AWS accounts, you might wonder how it would work with Terraform. I have seen too many repetitions both inside AWS (resource naming hell) and within terraform (WET code). Let&apos;s start solving the resource replication first, and then we move to set up the AWS multi-org.</p><p>As an example, let&apos;s consider we want to have different SQS queues per environment, and for simplicity let&apos;s just stick to staging and production accounts.</p><p>So, the first thing that I see people doing is:</p><pre><code>.
src
&#x2514;&#x2500;&#x2500; queues
    &#x251C;&#x2500;&#x2500; main.tf  # &#x1F448; Resources defined here
    &#x251C;&#x2500;&#x2500; vars.tf
    &#x2514;&#x2500;&#x2500; state.tf # &#x1F448; Terraform backend state config
</code></pre><pre><code class="language-tf"># main.tf
# &#x1F447; Defines we&apos;re using AWS cloud provider
provider &quot;aws&quot; {
  region = &quot;us-east-1&quot;
}

# &#x1F447; Defines SQS for staging
module &quot;staging_sqs&quot; {
  source  = &quot;terraform-aws-modules/sqs/aws&quot;
  version = &quot;~&gt; 2.0&quot;

  # &#x1F447; Naming hell, we add a prefix to specify the var
  name = var.staging_sqs_name
  message_retention_seconds = 86400  # &#x1F448; Staging messages set to 1day

  # &#x1F447; We tag the resource with the env
  tags = {
    env = &quot;staging&quot;
  }
}

# &#x1F447; Defines SQS for prod
module &quot;prod_sqs&quot; {
  source  = &quot;terraform-aws-modules/sqs/aws&quot;
  version = &quot;~&gt; 2.0&quot;

  # &#x1F447; Naming hell, we add a prefix to specify the var
  name = var.prod_sqs_name
  message_retention_seconds = 259200  # &#x1F448; Prod messages set to 3days

  # &#x1F447; We tag the resource with the env
  tags = {
    env = &quot;prod&quot;
  }
}
</code></pre><p>Three things to keep in mind:</p><ul><li><strong>We don&apos;t want to copy/paste code to represent the same resource per environment</strong>, that sucks</li><li><strong>Different environments have different resource configurations</strong> (e.g. Staging databases can be way smaller than production ones), and for our example, the SQS queues have different tags and message retention time</li><li><strong>We need to create them in different AWS accounts</strong> but associated with the root account</li></ul><p>That&apos;s where <strong>terraform workspaces shine!</strong> You can have the same definition of <strong>resources with different states</strong>.</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://guicommits.com/content/images/2021/11/tf-replicated.png" class="kg-image" alt="AWS Organizations with Terraform Workspaces" loading="lazy" width="1200" height="628" srcset="https://guicommits.com/content/images/size/w600/2021/11/tf-replicated.png 600w, https://guicommits.com/content/images/size/w1000/2021/11/tf-replicated.png 1000w, https://guicommits.com/content/images/2021/11/tf-replicated.png 1200w" sizes="(min-width: 720px) 720px"><figcaption>Terraform replicating resources with workspaces</figcaption></figure><p>If you never tried I recommend you to play with it by running:</p><pre><code class="language-bash">&#x276F; terraform workspace list
* default

&#x276F; terraform workspace new staging
Created and switched to workspace &quot;staging&quot;!

You&apos;re now on a new, empty workspace. Workspaces isolate their state,
so if you run &quot;terraform plan&quot; Terraform will not see any existing state
for this configuration.

&#x276F; terraform workspace list
  default
* staging
</code></pre><p>Consider that every workspace has <strong>an independent state</strong>, and you can switch between workspaces by <code>terraform workspace select &lt;workspace-name&gt;</code>.</p><p>Considering you created two workspaces named <code>staging</code> and <code>prod</code>, we need to modify our directory structure a bit to keep different configs and update our <code>main.tf</code> file:</p><pre><code>.
src
&#x2514;&#x2500;&#x2500; queues
    &#x251C;&#x2500;&#x2500; workspaces # &#x1F448; New directory
    &#x2502;   &#x251C;&#x2500;&#x2500; prod.tfvars # &#x1F448; I recommend naming it after the workspace to keep it obvious
    &#x2502;   &#x2514;&#x2500;&#x2500; staging.tfvars
    &#x251C;&#x2500;&#x2500; main.tf
    &#x251C;&#x2500;&#x2500; vars.tf
    &#x2514;&#x2500;&#x2500; state.tf
</code></pre><p>And now our <code>main.tf</code> can be updated to be like:</p><pre><code class="language-tf">provider &quot;aws&quot; {
  region = &quot;us-east-1&quot;
}

module &quot;sqs&quot; {  # &#x1F448; Cleaner name
  source  = &quot;terraform-aws-modules/sqs/aws&quot;
  version = &quot;~&gt; 2.0&quot;

  name = &quot;${terraform.workspace}-sqs&quot; # &#x1F448; Keeping the same structure for example
  message_retention_seconds = var.message_retention_seconds

  # &#x1F447; We tag the resource with the env
  tags = {
    env = terraform.workspace
  }
}
</code></pre><p>Way better right? The biggest difference is the directory structure, where we included two new files <code>prod.tfvars</code> and <code>staging.tfvars</code>.</p><p>These files are quite simple though, see <code>staging.tfvars</code>:</p><pre><code class="language-ini"># staging.tfvars
message_retention_seconds = 86400
</code></pre><p>and <code>prod.tfvars</code>:</p><pre><code class="language-ini"># prod.tfvars
message_retention_seconds = 259200
</code></pre><p>And finally, the expected usage would be, for example:</p><pre><code class="language-bash">&#x276F; terraform workspace select staging
Switched to workspace &quot;staging&quot;.

# &#x1F447; Now you must use `-var-file`
terraform plan -var-file ./workspaces/staging.tfvars
terraform apply -var-file ./workspaces/staging.tfvars
</code></pre><p>Here&apos;s how the state is stored inside an S3 bucket:</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://guicommits.com/content/images/2021/11/tf-state-envs.gif" class="kg-image" alt="AWS Organizations with Terraform Workspaces" loading="lazy" width="1071" height="366"><figcaption>Terraform workspaces state stored in an S3 bucket</figcaption></figure><h3 id="%F0%9F%8F%97%F0%9F%AA%90-terraform-with-aws-organizations-multi-accounts">&#x1F3D7;&#x1FA90; Terraform with AWS Organizations (multi-accounts)</h3><p>That&apos;s way better already, but we still have resources located in the same account, let&apos;s fix that by telling Terraform to use different accounts for managing the resources.</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://guicommits.com/content/images/2021/11/tf-aws-multiorg.png" class="kg-image" alt="AWS Organizations with Terraform Workspaces" loading="lazy" width="1200" height="628" srcset="https://guicommits.com/content/images/size/w600/2021/11/tf-aws-multiorg.png 600w, https://guicommits.com/content/images/size/w1000/2021/11/tf-aws-multiorg.png 1000w, https://guicommits.com/content/images/2021/11/tf-aws-multiorg.png 1200w" sizes="(min-width: 720px) 720px"><figcaption>Terraform assumes different roles to create resources</figcaption></figure><p>Given that you have a nice and reusable directory structure. We just need to modify three places:</p><ul><li><code>main.tf</code> with the <code>assume_role</code> option</li><li><code>prod.tfvars</code>, and <code>staging.tfvars</code> with respective AWS config vars</li></ul><pre><code class="language-tf"># main.tf
provider &quot;aws&quot; {
  region = &quot;us-east-1&quot;

  # &#x1F447; Identifies which role terraform should assume when planning and applying resources
  assume_role {
    role_arn = var.aws_role
    # &#x1F446; We can keep different vars per environment!
  }
}

...
</code></pre><p>Keep in mind that doing it means terraform still stores the state in a bucket located in the root account, but any interaction with the cloud resources will assume a new role before.</p><p>Of course, you can set the <code>aws_role</code> var per environment:</p><pre><code class="language-ini"># staging.tfvars
aws_role = &quot;arn:aws:iam::00000000000:role/admin-role&quot;

# prod.tfvars
aws_role = &quot;arn:aws:iam::00000000000:role/admin-role&quot;
</code></pre><p>&#x2728; And that&apos;s it! Now you have reusable terraform code spread across environments, and resources well named. Cheers! &#x1F37B;</p><h2 id="%F0%9F%91%80-want-to-see-a-real-project">&#x1F440; Want to see a real project?</h2><p>I&apos;m building a microservice architecture in public in a new series called <a href="https://guicommits.com/series/antifragile-dev/">the AntifragileDev</a> while keeping the whole code open source and sharing my journey and learnings as I go.</p><p>You probably saw some references from my new project in the AWS org examples above, right? &#x1F601; I&apos;ll share everything (including costs) of keeping a microservice architecture up and running.</p><p>If that&apos;s something you&apos;re interested in, you should <a href="https://twitter.com/intent/user?screen_name=guilatrova">follow me on Twitter</a>.</p><figure class="kg-card kg-embed-card"><blockquote class="twitter-tweet"><p lang="en" dir="ltr">Did you know you can create multiple AWS accounts for better management?<br><br>This feature is called &quot;AWS organizations&quot;.<br><br>The root account will be in charge of paying for the usage of children accounts.<br><br>&#x1F447;&#x1F9F5; Thread <a href="https://t.co/AjXPdsGlgG">pic.twitter.com/AjXPdsGlgG</a></p>&#x2014; Gui Latrova (@guilatrova) <a href="https://twitter.com/guilatrova/status/1463100148947382274?ref_src=twsrc%5Etfw">November 23, 2021</a></blockquote>
<script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>
</figure><p><strong>This post is part of this project, where I bring real needs, build all microservices in public, and keep them open source.</strong></p><p>&#x1F447; And by the way, this is the infrastructure project I meant, feel free to explore!</p><figure class="kg-card kg-bookmark-card"><a class="kg-bookmark-container" href="https://github.com/guilatrova/restaurant-directory-listing-infra"><div class="kg-bookmark-content"><div class="kg-bookmark-title">GitHub - guilatrova/restaurant-directory-listing-infra: Terraform infrastructure code that generates the infra for a Restaurant Listing Directory using AWS Organizations</div><div class="kg-bookmark-description">Terraform infrastructure code that generates the infra for a Restaurant Listing Directory using AWS Organizations - GitHub - guilatrova/restaurant-directory-listing-infra: Terraform infrastructure ...</div><div class="kg-bookmark-metadata"><img class="kg-bookmark-icon" src="https://github.com/fluidicon.png" alt="AWS Organizations with Terraform Workspaces"><span class="kg-bookmark-author">GitHub</span><span class="kg-bookmark-publisher">guilatrova</span></div></div><div class="kg-bookmark-thumbnail"><img src="https://opengraph.githubassets.com/5aa8a812a96a147a8e5a24fa938526df4e08c7fe35e23225fd8698da2049ac15/guilatrova/restaurant-directory-listing-infra" alt="AWS Organizations with Terraform Workspaces"></div></a></figure><p></p>]]></content:encoded></item><item><title><![CDATA[Project: Google Maps Crawler 🗺🪲]]></title><description><![CDATA[Use Selenium in Python to extract data from places in Google Maps with examples and source code]]></description><link>https://guicommits.com/selenium-example-with-python-gmaps/</link><guid isPermaLink="false">619584314f52c9070e64ff9f</guid><category><![CDATA[🐍 Python]]></category><category><![CDATA[🤖 Automation]]></category><category><![CDATA[🪲 Web Scraping]]></category><dc:creator><![CDATA[Guilherme Latrova]]></dc:creator><pubDate>Fri, 19 Nov 2021 14:49:02 GMT</pubDate><media:content url="https://images.unsplash.com/photo-1524661135-423995f22d0b?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=MnwxMTc3M3wwfDF8c2VhcmNofDEwfHxtYXBzfGVufDB8fHx8MTYzNzUyNDExOA&amp;ixlib=rb-1.2.1&amp;q=80&amp;w=2000" medium="image"/><content:encoded><![CDATA[<figure class="kg-card kg-image-card"><img src="https://guicommits.com/content/images/2021/11/image-1.png" class="kg-image" alt="Project: Google Maps Crawler &#x1F5FA;&#x1FAB2;" loading="lazy" width="250" height="250"></figure><!--kg-card-begin: html--><a href="https://guicommits.com/antifragile-dev-1-restaurant-directory-listing-proposal/"><img alt="Project: Google Maps Crawler &#x1F5FA;&#x1FAB2;" src="https://img.shields.io/badge/%F0%9F%A7%91%E2%80%8D%F0%9F%92%BB-antifragile--dev-green"></a>
<img alt="Project: Google Maps Crawler &#x1F5FA;&#x1FAB2;" src="https://img.shields.io/badge/python-3.9%20%7C%203.10-blue">
<a href="https://github.com/guilatrova/gmaps-crawler/blob/main/LICENSE"><img alt="Project: Google Maps Crawler &#x1F5FA;&#x1FAB2;" src="https://img.shields.io/github/license/guilatrova/gmaps-crawler"></a>
<iframe src="https://ghbtns.com/github-btn.html?user=guilatrova&amp;repo=GMaps-Crawler&amp;type=star&amp;count=true" frameborder="0" scrolling="0" width="150" height="20" title="GitHub"></iframe><!--kg-card-end: html--><img src="https://images.unsplash.com/photo-1524661135-423995f22d0b?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=MnwxMTc3M3wwfDF8c2VhcmNofDEwfHxtYXBzfGVufDB8fHx8MTYzNzUyNDExOA&amp;ixlib=rb-1.2.1&amp;q=80&amp;w=2000" alt="Project: Google Maps Crawler &#x1F5FA;&#x1FAB2;"><p><strong>Stack:</strong> Python, Selenium</p><figure class="kg-card kg-bookmark-card"><a class="kg-bookmark-container" href="https://github.com/guilatrova/gmaps-crawler"><div class="kg-bookmark-content"><div class="kg-bookmark-title">GitHub - guilatrova/GMaps-Crawler: Google Maps crawler using Selenium</div><div class="kg-bookmark-description">Google Maps crawler using Selenium. Contribute to guilatrova/GMaps-Crawler development by creating an account on GitHub.</div><div class="kg-bookmark-metadata"><img class="kg-bookmark-icon" src="https://github.com/fluidicon.png" alt="Project: Google Maps Crawler &#x1F5FA;&#x1FAB2;"><span class="kg-bookmark-author">GitHub</span><span class="kg-bookmark-publisher">guilatrova</span></div></div><div class="kg-bookmark-thumbnail"><img src="https://opengraph.githubassets.com/cf8c47e498bd72e46d2ac4becc5d1cb00715a4558502bffa430f94dc99c5a40c/guilatrova/GMaps-Crawler" alt="Project: Google Maps Crawler &#x1F5FA;&#x1FAB2;"></div></a></figure><p>This is the first project created for the <strong><a href="https://guicommits.com/series/antifragile-dev/">Antifragile Dev</a></strong> series, and its purpose is to <strong>collect data from Google Maps</strong> and do pretty much whatever we want with it.</p><hr><h2 id="%F0%9F%A4%96-what-is-selenium">&#x1F916; What is Selenium</h2><p>Let&apos;s keep it simple: <strong>Selenium is a tool that manipulates and interacts with the browser as a regular user would</strong>.</p><p>It can be used to automate tests by simulating user behavior e.g. like typing, clicking, scrolling, interacting with contents, and checking outputs are being correctly displayed.</p><p>For the scope of this project, I didn&apos;t test anything, instead, I used it to capture data that would be very boring to do manually - we call it &quot;<strong><em>web scrapping</em></strong>&quot;.</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://guicommits.com/content/images/2021/11/gmaps-crawler-sample.gif" class="kg-image" alt="Project: Google Maps Crawler &#x1F5FA;&#x1FAB2;" loading="lazy" width="1425" height="810"><figcaption>Google Maps Crawler running - Example</figcaption></figure><h3 id="create-a-selenium-webdriver">Create a Selenium Webdriver</h3><p>This is how it looks like to create a &quot;Selenium web driver&quot; that will interact with Google Chrome:</p><figure class="kg-card kg-code-card"><pre><code class="language-py">from selenium import webdriver
from selenium.webdriver.chrome.options import Options
from webdriver_manager.chrome import ChromeDriverManager

IMPLICT_WAIT = 5


def create_driver(headless=False):
    chrome_options = Options()
    if headless:  # &#x1F448; Optional condition to &quot;hide&quot; the browser window
        chrome_options.headless = True

    driver = webdriver.Chrome(ChromeDriverManager().install(), chrome_options=chrome_options) 
    # &#x1F446;  Creation of the &quot;driver&quot; that we&apos;re using to interact with the browser
    
    driver.implicitly_wait(IMPLICT_WAIT) 
    # &#x1F446; How much time should Selenium wait until an element is able to interact

    return driver
</code></pre><figcaption>Creating a Chrome driver with Selenium&#xA0;</figcaption></figure><p>&#x1F446; Note we&apos;re using <code><a href="https://pypi.org/project/webdriver-manager/">ChromeDriverManager</a></code> to install the required dependencies for Selenium to manipulate the Chrome browser. That makes the setup a lot easier!</p><p>The minimum knowledge you need to get started now:</p><ul><li>Visit a page</li><li>Find an element on the page you want to interact</li><li>Wait for something to happen</li><li>Interact with the element</li></ul><p>To be able to do any of those is important that you understand a thing or two about HTML, check some basic commands:</p><figure class="kg-card kg-code-card"><pre><code class="language-py">driver = create_driver()  # Method defined in previous examples

driver.get(url)  # &#x1F448; Visits a page

# &#x1F447; Finding elements

driver.find_elements(By.XPATH, &quot;*&quot;)          # &#x1F448; Get all direct elements
driver.find_element(By.CSS_SELECTOR, &quot;#btn&quot;) # &#x1F448; Get one element with id &quot;btn&quot;
driver.find_elements(By.TAG_NAME, &quot;h1&quot;)      # &#x1F448; Get all &apos;h1&apos; elements
driver.find_elements(By.CLASS_NAME, &quot;cls&quot;)   # &#x1F448; Get all elements with classname &quot;cls&quot;</code></pre><figcaption>Examples on visiting a page and finding elements with Selenium</figcaption></figure><p>Defining &quot;the best&quot; way to find an element is harder though...</p><h2 id="%F0%9F%90%9B-how-to-debug-selenium-with-vscode">&#x1F41B; How to debug Selenium with VSCode</h2><p>My debug process for such applications is always the same. These sites don&apos;t want to help you scrap their content, so they make it really hard with random ids and class names. </p><p>Consider you want to get the business hours from a restaurant, it&apos;s not as straightforward as it looks like, because nothing makes much sense:</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://guicommits.com/content/images/2021/11/gmaps-crawler-unclear-sample2.gif" class="kg-image" alt="Project: Google Maps Crawler &#x1F5FA;&#x1FAB2;" loading="lazy" width="622" height="810"><figcaption>Random ids hard to understand</figcaption></figure><p>To scrape data from such sites it&apos;s quite painful, and <strong><u>consider they might change it anytime and of course they won&apos;t notify you</u>. </strong></p><p>This is hard to do at a first shot, so I&apos;m sharing some tricks I do to make my life less painful. You can set breakpoints in VSCode at specific moments, and then manipulate the driver right from the debug window.</p><p><strong>It&apos;s great to minimize guesswork.</strong></p><figure class="kg-card kg-image-card"><img src="https://guicommits.com/content/images/2021/11/gmaps-crawler-debug-process.gif" class="kg-image" alt="Project: Google Maps Crawler &#x1F5FA;&#x1FAB2;" loading="lazy" width="1423" height="810"></figure><figure class="kg-card kg-embed-card kg-card-hascaption"><blockquote class="twitter-tweet"><p lang="en" dir="ltr">&#x1F4A1; Tips and challenges on scraping data with Python and Selenium!<br><br>1&#xFE0F;&#x20E3; Data inconsistency<br><br>As I was trying to scrape business hours from restaurants on Google Maps, I found some random text in the middle! [...]<br><br>&#x1F447;&#x1F9F5; Solution and more challenges in thread <a href="https://t.co/gAEPbP7hOM">pic.twitter.com/gAEPbP7hOM</a></p>&#x2014; Gui Latrova (@guilatrova) <a href="https://twitter.com/guilatrova/status/1461031417324863490?ref_src=twsrc%5Etfw">November 17, 2021</a></blockquote>
<script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>
<figcaption>-</figcaption></figure><p>Finally, ensure to make your code readable, Selenium scripts get messy very quickly, so you always want <strong>meaningful methods and functions</strong>.</p><p>Check a small piece of this project code: </p><pre><code class="language-py">    def get_place_details(self):
        self.wait_restaurant_title_show()

        # DATA
        restaurant_name = self.get_restaurant_name()
        address = self.get_address()
        place = Place(restaurant_name, address)

        if self.expand_hours():
            place.business_hours = self.get_business_hours()

        # TRAITS
        place.extra_attrs = self.get_place_extra_attrs()
        traits_handler = self.get_region(PlaceDetailRegion.TRAITS)
        traits_handler.click()
        place.traits = self.get_traits()

        # REVIEWS
        place.rate, place.reviews = self.get_review()

        # PHOTOS
        place.photo_link = self.get_image_link()

        self.storage.save(place)
        self.hit_back()</code></pre><p>The goal is for the code to be self-explanatory and simple to read.</p><h2 id="%F0%9F%A4%94-why-you-didnt-use-the-google-maps-api">&#x1F914; Why you didn&apos;t use the Google Maps API?</h2><p>Mostly due to some feature limitations and rate-limiting.</p><p>Also, I&apos;m still hacking this project and I don&apos;t even know whether it will work, so I felt like just trying to get something simple real quick to move on.</p><h2 id="%F0%9F%9F%A2-whats-next">&#x1F7E2; What&apos;s next?</h2><p>Since we&apos;re willing to build a microservice architecture, we took our initial step:</p><ul><li>To get some source of data to display</li></ul><p>Now we must publish it to SQS as an event. Unfortunately, we don&apos;t have any infra yet... Well, I guess it&apos;s time for terraform and CDK. If you don&apos;t know those yet, it <strong>will be your chance to learn something fun and implemented in a real project.</strong></p><p><strong>Watch out for the next blog posts!</strong></p><figure class="kg-card kg-image-card"><img src="https://guicommits.com/content/images/2021/11/image-3.png" class="kg-image" alt="Project: Google Maps Crawler &#x1F5FA;&#x1FAB2;" loading="lazy" width="832" height="505" srcset="https://guicommits.com/content/images/size/w600/2021/11/image-3.png 600w, https://guicommits.com/content/images/2021/11/image-3.png 832w" sizes="(min-width: 720px) 720px"></figure><h2 id="%F0%9F%9F%A1-whats-pending">&#x1F7E1; What&apos;s pending?</h2><p>I made a few decisions that are worth sharing:</p><p><strong>The application is not scaling yet</strong></p><p><strong>The application doesn&apos;t crawl until the last page</strong></p><p><strong>I&apos;m running it from my own computer</strong></p><p>&#x1F446; I don&apos;t want to bother about collecting more cities, running into other schedules, etc. I&apos;ll get back to it later. We must progress and deliver something simpler but working, and having ~10 restaurants is enough for now.</p><hr><p><a href="https://twitter.com/intent/user?screen_name=guilatrova">Follow me on Twitter</a> to keep watching as the project evolves!</p><figure class="kg-card kg-embed-card"><blockquote class="twitter-tweet"><p lang="en" dir="ltr">How to use Python &#x1F40D;  iterators IRL applied to a real project!<br><br>Let&apos;s go step-by-step on how to transform &#x1F4A9;  code in art:<br><br>&#x1F447;&#x1F9F5; Open Source Repository at the end <a href="https://t.co/DQf4wK34nZ">pic.twitter.com/DQf4wK34nZ</a></p>&#x2014; Gui Latrova (@guilatrova) <a href="https://twitter.com/guilatrova/status/1461469387408371721?ref_src=twsrc%5Etfw">November 18, 2021</a></blockquote>
<script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>
</figure>]]></content:encoded></item><item><title><![CDATA[Restaurant Directory Listing - Call for Proposal]]></title><description><![CDATA[Frustrated by regular schooling, and motivated to build things on my own I'm willing to build a microservice architecture in public using Python, React, terraform, and AWS.]]></description><link>https://guicommits.com/antifragile-dev-1-restaurant-directory-listing-proposal/</link><guid isPermaLink="false">618a5cea2b6f3c110bc72eb0</guid><dc:creator><![CDATA[Guilherme Latrova]]></dc:creator><pubDate>Thu, 11 Nov 2021 21:55:00 GMT</pubDate><media:content url="https://guicommits.com/content/images/2021/11/SocialPreview-2.png" medium="image"/><content:encoded><![CDATA[<img src="https://guicommits.com/content/images/2021/11/SocialPreview-2.png" alt="Restaurant Directory Listing - Call for Proposal"><p><a href="https://guicommits.com/formal-education-is-dead/">Frustrated by regular schooling</a>, and motivated to build things on my own I&apos;m willing to build a microservice architecture in public with friends as promised:</p><figure class="kg-card kg-embed-card"><blockquote class="twitter-tweet"><p lang="en" dir="ltr">I&apos;m planning to build a whole microservice architecture in public, sharing all decisions and keeping the whole code open source for anyone to give feedback and learn from.<br><br>Follow me if you&apos;re interested in that kind of stuff!</p>&#x2014; Gui Latrova (@guilatrova) <a href="https://twitter.com/guilatrova/status/1442941179859914754?ref_src=twsrc%5Etfw">September 28, 2021</a></blockquote>
<script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>
</figure><p>And by my friends I mean you. You&apos;re welcome to join, do things on your own terms, and share your progress.</p><p><strong>I&apos;ll open space here in my blog to people who wrap up different solutions.</strong></p><p>If you&apos;re a watcher, you&apos;re welcome too! We&apos;re building everything in public, so you can observe and criticize.</p><h2 id="%F0%9F%8F%97-the-project">&#x1F3D7; The Project</h2><p>We&apos;re building a directory listing for restaurants inspired by <a href="nomadlist.com">nomadlist.com</a>.</p><figure class="kg-card kg-bookmark-card"><a class="kg-bookmark-container" href="https://nomadlist.com/"><div class="kg-bookmark-content"><div class="kg-bookmark-title">Nomad List</div><div class="kg-bookmark-description">&#x1F30D; Go nomad: Join a global community of remote workers living around the world</div><div class="kg-bookmark-metadata"><img class="kg-bookmark-icon" src="https://nomadlist.com/assets/apple-touch-icon-152x152.png" alt="Restaurant Directory Listing - Call for Proposal"><span class="kg-bookmark-author">Nomad List</span></div></div><div class="kg-bookmark-thumbnail"><img src="https://nomadlist.com/assets/media-2020-2.png?5" alt="Restaurant Directory Listing - Call for Proposal"></div></a></figure><p>So instead of looking for restaurants as someone would on Google, we&apos;re creating an app to list them focusing on the pictures and other attributes the restaurant may have.</p><p><strong>The goal is not to be original, but to build in public, and share learnings.</strong></p><h2 id="%F0%9F%8C%9F-challenges-to-keep-in-mind">&#x1F31F; Challenges to keep in mind</h2><h3 id="backend">Backend</h3><ul><li>We want to have some initial data to display, so find a way to extract data from Google Maps or some other source you trust</li><li>Ensure that updates on your source can be reflected on your app <strong><u>if/when you wish</u></strong> to</li></ul><h3 id="frontend">Frontend</h3><ul><li>We want to allow visitors to rank places, favorite them, and filter stuff like &quot;coffee shop&quot;, or maybe &quot;nice place for kids&quot;</li><li>By default, we show the restaurants in the same city the visitor is</li></ul><p>And that&apos;s it. <strong>It&apos;s intentionally open-ended to push you to bootstrap your own solutions and put into practice your problem-solving skills.</strong></p><p>I&apos;m going to share my approach to the issue later on this blog, and progress on <a href="https://twitter.com/intent/user?screen_name=guilatrova">Twitter</a>. Subscribe to the <a href="https://subscribe.guilatrova.dev/">newsletter</a> so you won&apos;t miss it!</p><h2 id="%F0%9F%97%BA%EF%B8%8F-how-is-it-going-to-work">&#x1F5FA;&#xFE0F; How is it going to work?</h2><p>It&apos;s simple. No need for fancy project launches, it&apos;s just us: friends hacking and learning together.</p><p>You&apos;re going to read the proposed challenge above, understand the features, define which technologies you want to use, design your architecture, and start building it.</p><p>You got the control! Wanna try that amazing framework? Maybe play with Deno? Why not Flutter? Well, you decide, that&apos;s your call!</p><p>There&apos;s no need to do it in a &quot;microservice fashion way&quot; either. Feel free to wrap up your monolith, that&apos;s ok.</p><p>Yet, <strong>if you&apos;re a beginner and there&apos;s something you have no clue on how to do</strong>, you can watch first and then replicate it on your own terms.</p><p>Once you have it defined, do as follows:</p><ol><li>Fork this repo: <a href="https://github.com/guilatrova/antifragile-dev-1">https://github.com/guilatrova/antifragile-dev-1</a></li><li>Follow the template</li><li>Push your changes to your own repo, and</li><li>Get in touch to let me know you&apos;re participating, so I can share your work publicly:</li></ol><p>Reach out either through Twitter or my personal email:</p><!--kg-card-begin: html--><a href="https://twitter.com/intent/tweet?screen_name=guilatrova&amp;ref_src=twsrc%5Etfw" class="twitter-mention-button" data-size="large" data-text="Hey! I&apos;d like to let you know I&apos;m going to participate in the #antifragiledev project! &#x1F9D1;&#x200D;&#x1F4BB;&#x1F680;" data-related data-lang="en" data-show-count="false">Let the world know you&apos;re participating</a><script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script><!--kg-card-end: html--><p><strong>Don&apos;t forget to share it with your friends, they might have an opinion on how to do it themselves, or they might enjoy watching you do it.</strong></p><p>And the most important: <strong>as you evolve, <u><a href="https://guicommits.com/book-show-your-work/">share your work</a></u>!</strong></p><p>Excited to see your evolution! If you want to see mine as well, consider <a href="https://twitter.com/intent/user?screen_name=guilatrova">following me on Twitter</a> and <a href="https://subscribe.guilatrova.dev/">subscribing to the newsletter</a> for updates on all the participants.</p>]]></content:encoded></item></channel></rss>