<?xml version="1.0" encoding="UTF-8"?>
<feed xmlns="http://www.w3.org/2005/Atom" xml:lang="en">
    <title>thearcherst&#x27;s materials</title>
    <subtitle>MyMaterials</subtitle>
    <link rel="self" type="application/atom+xml" href="https://mymaterials.ru/atom.xml"/>
    <link rel="alternate" type="text/html" href="https://mymaterials.ru"/>
    <generator uri="https://www.getzola.org/">Zola</generator>
    <updated>2026-06-01T00:00:00+00:00</updated>
    <id>https://mymaterials.ru/atom.xml</id>
    <entry xml:lang="en">
        <title>Python, FastAPI, 6.</title>
        <published>2026-06-01T00:00:00+00:00</published>
        <updated>2026-06-01T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Unknown
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://mymaterials.ru/lessons/python-fastapi-6/"/>
        <id>https://mymaterials.ru/lessons/python-fastapi-6/</id>
        
        <content type="html" xml:base="https://mymaterials.ru/lessons/python-fastapi-6/">&lt;h1 id=&quot;kod-s-zaniatiia&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#kod-s-zaniatiia&quot; aria-label=&quot;Anchor link for: kod-s-zaniatiia&quot;&gt;Код с занятия&lt;&#x2F;a&gt;&lt;&#x2F;h1&gt;
&lt;h2 id=&quot;fastapi-postgresql-sozdanie-i-poluchenie-pol-zovatelei&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#fastapi-postgresql-sozdanie-i-poluchenie-pol-zovatelei&quot; aria-label=&quot;Anchor link for: fastapi-postgresql-sozdanie-i-poluchenie-pol-zovatelei&quot;&gt;FastAPI + PostgreSQL: создание и получение пользователей&lt;&#x2F;a&gt;&lt;&#x2F;h2&gt;
&lt;pre class=&quot;giallo z-code&quot;&gt;&lt;code data-lang=&quot;python&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-keyword&quot;&gt;import&lt;&#x2F;span&gt;&lt;span&gt; asyncpg&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-keyword&quot;&gt;from&lt;&#x2F;span&gt;&lt;span&gt; uvicorn&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; import&lt;&#x2F;span&gt;&lt;span&gt; run&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-keyword&quot;&gt;from&lt;&#x2F;span&gt;&lt;span&gt; fastapi&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; import&lt;&#x2F;span&gt;&lt;span&gt; FastAPI&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-keyword&quot;&gt;from&lt;&#x2F;span&gt;&lt;span&gt; pydantic&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; import&lt;&#x2F;span&gt;&lt;span&gt; BaseModel&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;app&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; =&lt;&#x2F;span&gt;&lt;span&gt; FastAPI&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-storage z-type&quot;&gt;async&lt;&#x2F;span&gt;&lt;span class=&quot;z-storage z-type&quot;&gt; def&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt; create_connection&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    conn&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; =&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; await&lt;&#x2F;span&gt;&lt;span&gt; asyncpg&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span&gt;connect&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;        &amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;postgresql:&#x2F;&#x2F;postgres:42536@127.0.0.1&#x2F;postgres&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    )&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-keyword&quot;&gt;    return&lt;&#x2F;span&gt;&lt;span&gt; conn&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-storage z-type&quot;&gt;async&lt;&#x2F;span&gt;&lt;span class=&quot;z-storage z-type&quot;&gt; def&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt; create_users_table&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-parameter z-function&quot;&gt;conn&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-keyword&quot;&gt;    await&lt;&#x2F;span&gt;&lt;span&gt; conn&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span&gt;execute&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;&amp;quot;&amp;quot;&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-string&quot;&gt;    create table if not exists users (&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-string&quot;&gt;        id int generated always as identity primary key,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-string&quot;&gt;        name varchar not null&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-string&quot;&gt;    )&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;    &amp;quot;&amp;quot;&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-storage z-type&quot;&gt;class&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt; CreateUser&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity&quot;&gt;BaseModel&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    name&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;span class=&quot;z-support&quot;&gt; str&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;@&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;app&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;.&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;post&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;&#x2F;users&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-storage z-type&quot;&gt;async&lt;&#x2F;span&gt;&lt;span class=&quot;z-storage z-type&quot;&gt; def&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt; create_user&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-parameter z-function&quot;&gt;payload&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;span&gt; CreateUser&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    conn&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; =&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; await&lt;&#x2F;span&gt;&lt;span&gt; create_connection&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-keyword&quot;&gt;    await&lt;&#x2F;span&gt;&lt;span&gt; create_users_table&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span&gt;conn&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-keyword&quot;&gt;    await&lt;&#x2F;span&gt;&lt;span&gt; conn&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span&gt;execute&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;&amp;quot;&amp;quot;&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-string&quot;&gt;    insert into users (name) values ($1)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;    &amp;quot;&amp;quot;&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span&gt; payload&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span&gt;name&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-comment&quot;&gt;    #&lt;&#x2F;span&gt;&lt;span class=&quot;z-comment&quot;&gt; лучше так не делать, поскольку между вставкой и созданием может быть создан ещё один пользователь, и у нас айди будет больше чем надо.  если что потом поясню почему так&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    data&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; =&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; await&lt;&#x2F;span&gt;&lt;span&gt; conn&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span&gt;fetch&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;&amp;quot;&amp;quot;&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-string&quot;&gt;    select max(id) as id from users&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;    &amp;quot;&amp;quot;&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    user_id&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; =&lt;&#x2F;span&gt;&lt;span&gt; data&lt;&#x2F;span&gt;&lt;span&gt;[&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt;0&lt;&#x2F;span&gt;&lt;span&gt;]&lt;&#x2F;span&gt;&lt;span&gt;[&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;id&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;]&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-keyword&quot;&gt;    await&lt;&#x2F;span&gt;&lt;span&gt; conn&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span&gt;close&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-keyword&quot;&gt;    return&lt;&#x2F;span&gt;&lt;span&gt; {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;        &amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;id&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;span&gt; user_id&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;        &amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;name&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;span&gt; payload&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span&gt;name&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    }&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;@&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;app&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;.&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;get&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;&#x2F;users&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-storage z-type&quot;&gt;async&lt;&#x2F;span&gt;&lt;span class=&quot;z-storage z-type&quot;&gt; def&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt; get_users&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    conn&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; =&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; await&lt;&#x2F;span&gt;&lt;span&gt; create_connection&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-keyword&quot;&gt;    await&lt;&#x2F;span&gt;&lt;span&gt; create_users_table&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span&gt;conn&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-comment&quot;&gt;    #&lt;&#x2F;span&gt;&lt;span class=&quot;z-comment&quot;&gt; тут надо написать чтение пользователей из БД и отправление результата&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-keyword&quot;&gt;if&lt;&#x2F;span&gt;&lt;span class=&quot;z-support z-variable&quot;&gt; __name__&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; ==&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt; &amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;__main__&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    run&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span&gt;app&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;&lt;h2 id=&quot;primer-na-kotorom-razbirali-asinkhronnost&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#primer-na-kotorom-razbirali-asinkhronnost&quot; aria-label=&quot;Anchor link for: primer-na-kotorom-razbirali-asinkhronnost&quot;&gt;Пример на котором разбирали асинхронность&lt;&#x2F;a&gt;&lt;&#x2F;h2&gt;
&lt;pre class=&quot;giallo z-code&quot;&gt;&lt;code data-lang=&quot;python&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-keyword&quot;&gt;import&lt;&#x2F;span&gt;&lt;span&gt; asyncio&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-keyword&quot;&gt;import&lt;&#x2F;span&gt;&lt;span&gt; time&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-storage z-type&quot;&gt;async&lt;&#x2F;span&gt;&lt;span class=&quot;z-storage z-type&quot;&gt; def&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt; handle_request&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-parameter z-function&quot;&gt;request&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-keyword&quot;&gt;    pass&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-storage z-type&quot;&gt;async&lt;&#x2F;span&gt;&lt;span class=&quot;z-storage z-type&quot;&gt; def&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt; first_task&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-support&quot;&gt;    print&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;Hello from first!&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-keyword&quot;&gt;    await&lt;&#x2F;span&gt;&lt;span&gt; asyncio&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span&gt;sleep&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt;1&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-support&quot;&gt;    print&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;Bye from first!&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-storage z-type&quot;&gt;async&lt;&#x2F;span&gt;&lt;span class=&quot;z-storage z-type&quot;&gt; def&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt; second_task&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-support&quot;&gt;    print&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;Hello from second!&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-keyword&quot;&gt;    await&lt;&#x2F;span&gt;&lt;span&gt; asyncio&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span&gt;sleep&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt;1&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-support&quot;&gt;    print&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;Bye from second!&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-storage z-type&quot;&gt;async&lt;&#x2F;span&gt;&lt;span class=&quot;z-storage z-type&quot;&gt; def&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt; main&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    asyncio&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span&gt;create_task&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span&gt;first_task&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    asyncio&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span&gt;create_task&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span&gt;second_task&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-keyword&quot;&gt;    await&lt;&#x2F;span&gt;&lt;span&gt; asyncio&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span&gt;sleep&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt;3&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;asyncio&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span&gt;run&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span&gt;main&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;&lt;h2 id=&quot;psevdokod-primerno-pokazyvaiushchii-logiku-po-ktoroi-rabotaiut-uvicorn-fastapi&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#psevdokod-primerno-pokazyvaiushchii-logiku-po-ktoroi-rabotaiut-uvicorn-fastapi&quot; aria-label=&quot;Anchor link for: psevdokod-primerno-pokazyvaiushchii-logiku-po-ktoroi-rabotaiut-uvicorn-fastapi&quot;&gt;Псевдокод, примерно показывающий логику по кторой работают uvicorn+fastapi&lt;&#x2F;a&gt;&lt;&#x2F;h2&gt;
&lt;pre class=&quot;giallo z-code&quot;&gt;&lt;code data-lang=&quot;python&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-keyword&quot;&gt;while&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt; True&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    request&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; =&lt;&#x2F;span&gt;&lt;span&gt; get_next_request&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    handler&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; =&lt;&#x2F;span&gt;&lt;span&gt; route&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span&gt;request&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-comment&quot;&gt;  #&lt;&#x2F;span&gt;&lt;span class=&quot;z-comment&quot;&gt; маршрутизируем полученный запрос&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    arguments&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; =&lt;&#x2F;span&gt;&lt;span&gt; prepare_arguments&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span&gt;handler&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span&gt; request&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-comment&quot;&gt;  #&lt;&#x2F;span&gt;&lt;span class=&quot;z-comment&quot;&gt; fastapi получает из запроса именно те данные что мы запросили в аргументах нашей функции&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    response&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; =&lt;&#x2F;span&gt;&lt;span&gt; handler&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span&gt;arguments&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-comment&quot;&gt;  #&lt;&#x2F;span&gt;&lt;span class=&quot;z-comment&quot;&gt; вызов нашей функции&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    send_response&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span&gt;response&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-comment&quot;&gt;  #&lt;&#x2F;span&gt;&lt;span class=&quot;z-comment&quot;&gt; отправляем ответ по сети&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-comment&quot;&gt;    #&lt;&#x2F;span&gt;&lt;span class=&quot;z-comment&quot;&gt; и потом переходим к обработке следующего запроса&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Но в действительности всё работает асинхронно: FastAPI асинхронно вызывает нашу функцию, и когда наша функция отдаёт управление, FastAPI может начать обрабатывать следующий запрос.  Можно представить это так&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo z-code&quot;&gt;&lt;code data-lang=&quot;python&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-keyword&quot;&gt;import&lt;&#x2F;span&gt;&lt;span&gt; asyncio&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-storage z-type&quot;&gt;async&lt;&#x2F;span&gt;&lt;span class=&quot;z-storage z-type&quot;&gt; def&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt; process_request&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-parameter z-function&quot;&gt;request&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    handler&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; =&lt;&#x2F;span&gt;&lt;span&gt; route&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span&gt;request&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-comment&quot;&gt;  #&lt;&#x2F;span&gt;&lt;span class=&quot;z-comment&quot;&gt; маршрутизируем полученный запрос&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    arguments&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; =&lt;&#x2F;span&gt;&lt;span&gt; prepare_arguments&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span&gt;handler&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span&gt; request&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-comment&quot;&gt;  #&lt;&#x2F;span&gt;&lt;span class=&quot;z-comment&quot;&gt; fastapi получает из запроса именно те данные что мы запросили в аргументах нашей функции&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    response&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; =&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; await&lt;&#x2F;span&gt;&lt;span&gt; handler&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span&gt;arguments&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-comment&quot;&gt;  #&lt;&#x2F;span&gt;&lt;span class=&quot;z-comment&quot;&gt; асинхронный вызов нашей функции&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-comment&quot;&gt;    #&lt;&#x2F;span&gt;&lt;span class=&quot;z-comment&quot;&gt; если обработчик внутри, например, сделает асихнронный вызов к БД, управление может быть передано в цикл событий.  Если обработчик не делает ничего что отдаёт управление в event loop, код фактически останется синхронным: задачи просто будут поочерёдно выполняться.&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    send_response&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span&gt;response&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-comment&quot;&gt;  #&lt;&#x2F;span&gt;&lt;span class=&quot;z-comment&quot;&gt; отправляем ответ по сети&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-storage z-type&quot;&gt;async&lt;&#x2F;span&gt;&lt;span class=&quot;z-storage z-type&quot;&gt; def&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt; main&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-keyword&quot;&gt;    while&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt; True&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;        request&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; =&lt;&#x2F;span&gt;&lt;span&gt; get_next_request&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;        asyncio&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span&gt;create_task&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span&gt;process_request&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span&gt;request&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;asyncio&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span&gt;run&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span&gt;main&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;&lt;h1 id=&quot;domashnee-zadanie&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#domashnee-zadanie&quot; aria-label=&quot;Anchor link for: domashnee-zadanie&quot;&gt;Домашнее задание&lt;&#x2F;a&gt;&lt;&#x2F;h1&gt;
&lt;p&gt;Добавьте в код, который мы писали в классе, возможность получать список всех пользователей из базы данных.&lt;&#x2F;p&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>Переменные и объекты в Python</title>
        <published>2026-05-28T00:00:00+00:00</published>
        <updated>2026-05-28T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Unknown
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://mymaterials.ru/articles/variables-and-objects-in-python/"/>
        <id>https://mymaterials.ru/articles/variables-and-objects-in-python/</id>
        
        <content type="html" xml:base="https://mymaterials.ru/articles/variables-and-objects-in-python/">&lt;p&gt;Переменной в Python называют имя, связанное со ссылкой на объект.&lt;&#x2F;p&gt;
&lt;p&gt;Визуально переменную можно представлять как имя и стрелочку. Если стрелочка изменяется, следует говорить, что «имя теперь ссылается на другой объект» или «мы изменили ссылку переменной». Говорить просто «переменная изменилась» допустимо, но, чтобы не возникало путаницы, лучше явно говорить про ссылку&#x2F;стрелочку. Так становится явным то, что изменение не затронуло сам объект.&lt;&#x2F;p&gt;
&lt;p&gt;Объект — это данные, лежащие в оперативной памяти. У объекта есть тип и значение. Например, выражение &lt;code&gt;1&lt;&#x2F;code&gt; даёт нам объект типа &lt;code&gt;int&lt;&#x2F;code&gt; со значением &lt;code&gt;один&lt;&#x2F;code&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;Визуально любой объект можно представлять как область в оперативной памяти, в которой хранится тип объекта и значение объекта.&lt;&#x2F;p&gt;
&lt;p&gt;Тип уже созданного объекта никак нельзя изменить. Значение некоторых объектов тоже никак нельзя изменить, и нужно запомнить, какие объекты неизменяемы. Начнём с того, что нельзя изменить объекты вот с этими типами: &lt;code&gt;int&lt;&#x2F;code&gt;, &lt;code&gt;float&lt;&#x2F;code&gt;, &lt;code&gt;str&lt;&#x2F;code&gt;, &lt;code&gt;bool&lt;&#x2F;code&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;Так, если вы пишете такой код:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo z-code&quot;&gt;&lt;code data-lang=&quot;python&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;a&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; =&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt; 1&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;a&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; =&lt;&#x2F;span&gt;&lt;span&gt; a&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; +&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt; 1&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Смысл его такой:&lt;&#x2F;p&gt;
&lt;p&gt;На первой строке мы получили объект типа &lt;code&gt;int&lt;&#x2F;code&gt; со значением &lt;code&gt;один&lt;&#x2F;code&gt; и создали переменную &lt;code&gt;a&lt;&#x2F;code&gt;, которая на него ссылается.&lt;&#x2F;p&gt;




&lt;style&gt;
.python-state-shortcode {
    position: relative;
    border: 1px solid white;
    border-radius: 2px;
    overflow: hidden;
    padding: 1em;
    margin: 2em 0;
}

.python-state-shortcode__header {
    position: absolute;
    top: 0.75rem;
    right: 0.75rem;
    z-index: 4;
    pointer-events: none;
}

.python-state-shortcode__fullscreen-button {
    border: 1px solid white;
    border-radius: 2px;
    background: transparent;
    color: inherit;
    padding: 0.3em 0.55em;
    font: inherit;
    font-size: 0.78rem;
    line-height: 1;
    cursor: pointer;
    pointer-events: auto;
}

.python-state-shortcode__fullscreen-button:hover,
.python-state-shortcode__fullscreen-button:focus-visible {
    background: rgba(255, 255, 255, 0.12);
}

.python-state-shortcode__viewport {
    overflow-x: auto;
}

.python-state-shortcode__stage {
    position: relative;
    min-width: 560px;
    min-height: 240px;
    overflow: hidden;
}

.python-state-shortcode__arrows {
    position: absolute;
    inset: 0;
    width: 100%;
    height: 100%;
    pointer-events: auto;
    z-index: 1;
}

.python-state-shortcode__nodes {
    position: absolute;
    inset: 0;
    z-index: 2;
}

.python-state-shortcode__box {
    position: absolute;
    box-sizing: border-box;
    width: max-content;
    min-width: 4.5rem;
    max-width: 18rem;
    border: 1px solid white;
    border-radius: 2px;
    background: var(--bg, transparent);
    padding: 0.55em 0.7em;
    cursor: grab;
    user-select: none;
    touch-action: none;
}

.python-state-shortcode__box:active {
    cursor: grabbing;
}

.python-state-shortcode__box--dragging {
    z-index: 3;
}

.python-state-shortcode__frame-title {
    font-weight: 600;
    line-height: 1.2;
    margin-bottom: 0.45em;
    white-space: nowrap;
}

.python-state-shortcode__object-type {
    line-height: 1.2;
    margin-bottom: 0.45em;
    white-space: nowrap;
}

.python-state-shortcode__bindings,
.python-state-shortcode__object-content {
    display: grid;
    gap: 0.3em;
    margin: 0;
    padding: 0;
    list-style: none;
}

.python-state-shortcode__collection {
    display: flex;
    flex-flow: row nowrap;
    gap: 0;
    margin: 0;
    padding: 0.15em 0 0.25em 0;
    list-style: none;
}

.python-state-shortcode__reference {
    position: relative;
    display: block;
    box-sizing: border-box;
    width: 100%;
    padding-right: 1.05em;
    min-height: 1.35em;
    line-height: 1.25;
}

.python-state-shortcode__reference-label,
.python-state-shortcode__literal,
.python-state-shortcode__object-line {
    overflow-wrap: anywhere;
    font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, &quot;Liberation Mono&quot;, monospace;
    font-size: 0.9rem;
}

.python-state-shortcode__reference-label {
    display: block;
    white-space: nowrap;
}

.python-state-shortcode__reference-port {
    position: absolute;
    top: 50%;
    right: 0.2em;
    width: 0.48em;
    height: 0.48em;
    transform: translateY(-50%);
    opacity: 0;
    pointer-events: none;
}

.python-state-shortcode__reference[data-python-state-reference-side=&quot;left&quot;] {
    padding-right: 0;
    padding-left: 1.05em;
}

.python-state-shortcode__reference[data-python-state-reference-side=&quot;left&quot;] .python-state-shortcode__reference-port {
    right: auto;
    left: 0.2em;
}

.python-state-shortcode__reference--collection-item {
    display: flex;
    align-items: center;
    justify-content: center;
    width: 2.15em;
    height: 2.15em;
    min-height: 0;
    padding: 0;
    border: 1px solid white;
    box-sizing: border-box;
}

.python-state-shortcode__reference--collection-item + .python-state-shortcode__reference--collection-item {
    border-left: 0;
}

.python-state-shortcode__reference--collection-item .python-state-shortcode__reference-label {
    display: none;
}

.python-state-shortcode__reference--collection-item .python-state-shortcode__reference-port,
.python-state-shortcode__reference--collection-item[data-python-state-reference-side=&quot;left&quot;] .python-state-shortcode__reference-port {
    top: 50%;
    right: auto;
    bottom: auto;
    left: 50%;
    transform: translate(-50%, -50%);
}

.python-state-shortcode__literal {
    line-height: 1.35;
}

.python-state-shortcode__object--missing {
    border-style: dashed;
}

.python-state-shortcode__edge {
    pointer-events: visibleStroke;
    color: #60a5fa;
}

.python-state-shortcode__edge-hit {
    fill: none;
    stroke: transparent;
    stroke-width: 14;
    stroke-linecap: round;
    stroke-linejoin: round;
    pointer-events: stroke;
}

.python-state-shortcode__edge-halo {
    fill: none;
    stroke: rgba(255, 255, 255, 0);
    stroke-width: 4;
    stroke-linecap: round;
    stroke-linejoin: round;
    transition: stroke 140ms ease;
}

.python-state-shortcode__edge-line {
    fill: none;
    stroke: currentColor;
    stroke-width: 1.5;
    stroke-linecap: round;
    stroke-linejoin: round;
    transition: stroke 140ms ease;
}

.python-state-shortcode__edge-port-halo {
    fill: none;
    stroke: rgba(255, 255, 255, 0);
    stroke-width: 5;
    transition: stroke 140ms ease;
}

.python-state-shortcode__edge-arrowhead-halo {
    fill: none;
    stroke: rgba(255, 255, 255, 0);
    stroke-width: 3;
    stroke-linejoin: round;
    transition: stroke 140ms ease;
}

.python-state-shortcode__edge-arrowhead {
    fill: currentColor;
    transition: fill 140ms ease;
}

.python-state-shortcode__edge-port {
    fill: currentColor;
    stroke: currentColor;
    stroke-width: 1;
    transition: fill 140ms ease, stroke 140ms ease, r 140ms ease;
}

.python-state-shortcode__edge:hover,
.python-state-shortcode__edge:focus-visible,
.python-state-shortcode__edge--active {
    color: #93c5fd;
    outline: none;
}

.python-state-shortcode__edge:hover .python-state-shortcode__edge-halo,
.python-state-shortcode__edge:focus-visible .python-state-shortcode__edge-halo,
.python-state-shortcode__edge--active .python-state-shortcode__edge-halo,
.python-state-shortcode__edge:hover .python-state-shortcode__edge-port-halo,
.python-state-shortcode__edge:focus-visible .python-state-shortcode__edge-port-halo,
.python-state-shortcode__edge--active .python-state-shortcode__edge-port-halo {
    stroke: rgba(255, 255, 255, 0.92);
}

.python-state-shortcode__edge:hover .python-state-shortcode__edge-arrowhead-halo,
.python-state-shortcode__edge:focus-visible .python-state-shortcode__edge-arrowhead-halo,
.python-state-shortcode__edge--active .python-state-shortcode__edge-arrowhead-halo {
    stroke: rgba(255, 255, 255, 0.92);
}

.python-state-shortcode__error {
    border: 1px solid white;
    border-radius: 2px;
    padding: 0.75em 0.9em;
    max-width: 42rem;
    line-height: 1.35;
}

.python-state-shortcode__error-title {
    margin-bottom: 0.45em;
    font-weight: 600;
}

.python-state-shortcode__error-detail {
    margin: 0;
    white-space: pre-wrap;
    overflow-wrap: anywhere;
    font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, &quot;Liberation Mono&quot;, monospace;
    font-size: 0.9rem;
}

.python-state-shortcode--fullscreen {
    position: fixed;
    top: var(--python-state-fullpage-top, 0);
    right: 0;
    bottom: 0;
    left: 0;
    z-index: 1000;
    display: flex;
    flex-direction: column;
    box-sizing: border-box;
    width: 100vw;
    height: calc(100vh - var(--python-state-fullpage-top, 0px));
    height: calc(100dvh - var(--python-state-fullpage-top, 0px));
    margin: 0;
    border: 0;
    border-radius: 0;
    padding: 0;
    background: var(--python-state-fullpage-bg, transparent);
    overflow: hidden;
}

.python-state-shortcode--fullscreen .python-state-shortcode__header {
    margin: 0;
}

.python-state-shortcode--fullscreen .python-state-shortcode__viewport {
    flex: 1 1 auto;
    min-height: 0;
    overflow: auto;
}

.python-state-shortcode--fullscreen .python-state-shortcode__stage {
    min-height: 100%;
}
&lt;&#x2F;style&gt;

&lt;div class=&quot;python-state-shortcode&quot; data-python-state-shortcode&gt;
    &lt;div class=&quot;python-state-shortcode__header&quot;&gt;
        &lt;button class=&quot;python-state-shortcode__fullscreen-button&quot; type=&quot;button&quot; data-python-state-fullscreen-button aria-pressed=&quot;false&quot;&gt;Full page&lt;&#x2F;button&gt;
    &lt;&#x2F;div&gt;

    &lt;div class=&quot;python-state-shortcode__viewport&quot;&gt;
        &lt;div class=&quot;python-state-shortcode__stage&quot; data-python-state-stage aria-label=&quot;Python state&quot;&gt;
            &lt;svg class=&quot;python-state-shortcode__arrows&quot; data-python-state-arrows aria-hidden=&quot;true&quot;&gt;&lt;&#x2F;svg&gt;
            &lt;div class=&quot;python-state-shortcode__nodes&quot; data-python-state-nodes&gt;&lt;&#x2F;div&gt;
        &lt;&#x2F;div&gt;
    &lt;&#x2F;div&gt;

    &lt;script type=&quot;text&#x2F;plain&quot; class=&quot;python-state-shortcode__data&quot; data-python-state-data&gt;
frames:
  global:
    name: Global frame
    bindings:
      - name: a
        object: a
objects:
  a:
    type: int
    value: 1
    &lt;&#x2F;script&gt;

    &lt;script&gt;
    (function () {
        const currentScript = document.currentScript;
        const root = currentScript.closest(&quot;[data-python-state-shortcode]&quot;);
        const viewport = root.querySelector(&quot;.python-state-shortcode__viewport&quot;);
        const stage = root.querySelector(&quot;[data-python-state-stage]&quot;);
        const nodes = root.querySelector(&quot;[data-python-state-nodes]&quot;);
        const arrowsSvg = root.querySelector(&quot;[data-python-state-arrows]&quot;);
        const dataNode = root.querySelector(&quot;[data-python-state-data]&quot;);
        const fullscreenButton = root.querySelector(&quot;[data-python-state-fullscreen-button]&quot;);
        let savedDocumentOverflow = &quot;&quot;;
        let savedBodyOverflow = &quot;&quot;;

        const LAYOUT = {
            minWidth: 560,
            minHeight: 240,
            outerGap: 18,
            boxGap: 14,
            arrowGap: 7,
            cornerGap: 10,
            alternativePathClearance: 18
        };

        function stableId(value, fallback) {
            const raw = String(value || fallback || &quot;item&quot;).trim().toLowerCase();
            const slug = raw.replace(&#x2F;[^a-z0-9_-]+&#x2F;g, &quot;-&quot;).replace(&#x2F;^-+|-+$&#x2F;g, &quot;&quot;);
            return slug || String(fallback || &quot;item&quot;);
        }

        function clamp(value, min, max) {
            return Math.max(min, Math.min(max, value));
        }

        function parseState() {
            const raw = dataNode.textContent.trim();

            if (!raw) {
                return {
                    error: &quot;python_state shortcode expects a JSON or YAML body.&quot;
                };
            }

            try {
                const parsed = raw[0] === &quot;{&quot; || raw[0] === &quot;[&quot;
                    ? JSON.parse(raw)
                    : parseYamlSubset(raw);

                return {
                    state: validateState(parsed)
                };
            } catch (error) {
                return {
                    error: &quot;Invalid python_state shortcode data:\n&quot; + error.message
                };
            }
        }

        function dataError(path, message) {
            throw new Error(path + &quot;: &quot; + message);
        }

        function isPlainObject(value) {
            return Boolean(value) &amp;&amp; typeof value === &quot;object&quot; &amp;&amp; !Array.isArray(value);
        }

        function assertPlainObject(value, path) {
            if (!isPlainObject(value)) {
                dataError(path, &quot;expected an object&quot;);
            }
        }

        function assertArray(value, path) {
            if (!Array.isArray(value)) {
                dataError(path, &quot;expected an array&quot;);
            }
        }

        function assertAllowedKeys(value, path, allowedKeys) {
            const allowed = new Set(allowedKeys);

            Object.keys(value).forEach(function (key) {
                if (!allowed.has(key)) {
                    dataError(path + &quot;.&quot; + key, &quot;unknown field&quot;);
                }
            });
        }

        function assertId(value, path) {
            if (typeof value !== &quot;string&quot; || !&#x2F;^[A-Za-z_][A-Za-z0-9_-]*$&#x2F;.test(value)) {
                dataError(path, &quot;expected an object id like items or value_1&quot;);
            }
        }

        function assertString(value, path) {
            if (typeof value !== &quot;string&quot; || !value.trim()) {
                dataError(path, &quot;expected a non-empty string&quot;);
            }
        }

        function parseYamlSubset(raw) {
            const lines = raw.replace(&#x2F;\r\n?&#x2F;g, &quot;\n&quot;).split(&quot;\n&quot;);
            const tokens = [];

            lines.forEach(function (line, index) {
                if (!line.trim() || &#x2F;^\s*#&#x2F;.test(line)) {
                    return;
                }

                if (&#x2F;^\s*\t&#x2F;.test(line)) {
                    dataError(&quot;line &quot; + (index + 1), &quot;tabs are not allowed for indentation&quot;);
                }

                const indent = line.match(&#x2F;^ *&#x2F;)[0].length;

                if (indent % 2 !== 0) {
                    dataError(&quot;line &quot; + (index + 1), &quot;indentation must use multiples of two spaces&quot;);
                }

                tokens.push({
                    indent: indent,
                    text: line.slice(indent),
                    line: index + 1
                });
            });

            if (!tokens.length) {
                dataError(&quot;YAML&quot;, &quot;expected data&quot;);
            }

            const parsed = parseYamlBlock(tokens, 0, tokens[0].indent);

            if (parsed.index !== tokens.length) {
                dataError(&quot;line &quot; + tokens[parsed.index].line, &quot;unexpected content&quot;);
            }

            return parsed.value;
        }

        function parseYamlBlock(tokens, index, indent) {
            if (index &gt;= tokens.length) {
                dataError(&quot;YAML&quot;, &quot;expected block&quot;);
            }

            if (tokens[index].indent !== indent) {
                dataError(&quot;line &quot; + tokens[index].line, &quot;unexpected indentation&quot;);
            }

            if (tokens[index].text.startsWith(&quot;-&quot;)) {
                return parseYamlSequence(tokens, index, indent);
            }

            return parseYamlMap(tokens, index, indent);
        }

        function parseYamlMap(tokens, index, indent) {
            const value = {};

            while (index &lt; tokens.length &amp;&amp; tokens[index].indent === indent &amp;&amp; !tokens[index].text.startsWith(&quot;-&quot;)) {
                const token = tokens[index];
                const match = token.text.match(&#x2F;^([A-Za-z_][A-Za-z0-9_-]*):(.*)$&#x2F;);

                if (!match) {
                    dataError(&quot;line &quot; + token.line, &quot;expected key: value&quot;);
                }

                const key = match[1];
                const scalar = match[2].trim();

                if (Object.prototype.hasOwnProperty.call(value, key)) {
                    dataError(&quot;line &quot; + token.line, &quot;duplicate key &quot; + key);
                }

                index += 1;

                if (scalar) {
                    value[key] = parseYamlScalar(scalar, token.line);
                } else {
                    if (index &gt;= tokens.length || tokens[index].indent &lt;= indent) {
                        dataError(&quot;line &quot; + token.line, &quot;expected nested value for &quot; + key);
                    }

                    const child = parseYamlBlock(tokens, index, tokens[index].indent);
                    value[key] = child.value;
                    index = child.index;
                }
            }

            return { value: value, index: index };
        }

        function parseYamlSequence(tokens, index, indent) {
            const value = [];

            while (index &lt; tokens.length &amp;&amp; tokens[index].indent === indent &amp;&amp; tokens[index].text.startsWith(&quot;-&quot;)) {
                const token = tokens[index];
                const rest = token.text.slice(1).trim();

                index += 1;

                if (!rest) {
                    if (index &gt;= tokens.length || tokens[index].indent &lt;= indent) {
                        dataError(&quot;line &quot; + token.line, &quot;expected nested sequence item&quot;);
                    }

                    const child = parseYamlBlock(tokens, index, tokens[index].indent);
                    value.push(child.value);
                    index = child.index;
                    continue;
                }

                const pair = rest.match(&#x2F;^([A-Za-z_][A-Za-z0-9_-]*):(.*)$&#x2F;);

                if (pair) {
                    const item = {};
                    item[pair[1]] = parseYamlScalar(pair[2].trim(), token.line);

                    while (index &lt; tokens.length &amp;&amp; tokens[index].indent === indent + 2 &amp;&amp; !tokens[index].text.startsWith(&quot;-&quot;)) {
                        const propertyToken = tokens[index];
                        const property = propertyToken.text.match(&#x2F;^([A-Za-z_][A-Za-z0-9_-]*):(.*)$&#x2F;);

                        if (!property) {
                            dataError(&quot;line &quot; + propertyToken.line, &quot;expected key: value&quot;);
                        }

                        if (Object.prototype.hasOwnProperty.call(item, property[1])) {
                            dataError(&quot;line &quot; + propertyToken.line, &quot;duplicate key &quot; + property[1]);
                        }

                        const propertyValue = property[2].trim();
                        index += 1;

                        if (propertyValue) {
                            item[property[1]] = parseYamlScalar(propertyValue, propertyToken.line);
                        } else {
                            if (index &gt;= tokens.length || tokens[index].indent &lt;= propertyToken.indent) {
                                dataError(&quot;line &quot; + propertyToken.line, &quot;expected nested value for &quot; + property[1]);
                            }

                            const child = parseYamlBlock(tokens, index, tokens[index].indent);
                            item[property[1]] = child.value;
                            index = child.index;
                        }
                    }

                    value.push(item);
                    continue;
                }

                value.push(parseYamlScalar(rest, token.line));
            }

            return { value: value, index: index };
        }

        function parseYamlScalar(value, line) {
            if (!value) {
                dataError(&quot;line &quot; + line, &quot;expected scalar value&quot;);
            }

            if (value[0] === &quot;\&quot;&quot; || value[0] === &quot;&#x27;&quot;) {
                return parseQuotedYamlScalar(value, line);
            }

            if (value === &quot;true&quot;) {
                return true;
            }

            if (value === &quot;false&quot;) {
                return false;
            }

            if (value === &quot;null&quot; || value === &quot;None&quot;) {
                return null;
            }

            if (value === &quot;[]&quot;) {
                return [];
            }

            if (&#x2F;^-?(0|[1-9][0-9]*)(\.[0-9]+)?$&#x2F;.test(value)) {
                return Number(value);
            }

            if (&#x2F;[\[\]{}]&#x2F;.test(value)) {
                dataError(&quot;line &quot; + line, &quot;inline arrays and objects are not supported; use indented blocks&quot;);
            }

            return value;
        }

        function parseQuotedYamlScalar(value, line) {
            const quote = value[0];

            if (value[value.length - 1] !== quote) {
                dataError(&quot;line &quot; + line, &quot;unterminated quoted string&quot;);
            }

            if (quote === &quot;\&quot;&quot;) {
                try {
                    return JSON.parse(value);
                } catch (error) {
                    dataError(&quot;line &quot; + line, &quot;invalid quoted string&quot;);
                }
            }

            return value.slice(1, -1).replace(&#x2F;&#x27;&#x27;&#x2F;g, &quot;&#x27;&quot;);
        }

        function formatPythonLiteral(value, type) {
            if (type === &quot;str&quot;) {
                return JSON.stringify(value === undefined ? &quot;&quot; : String(value));
            }

            if (type === &quot;bool&quot;) {
                return value ? &quot;True&quot; : &quot;False&quot;;
            }

            if (type === &quot;NoneType&quot; || value === null) {
                return &quot;None&quot;;
            }

            if (value !== undefined) {
                return String(value);
            }

            return &quot;&quot;;
        }

        function normalizeDetails(value, path) {
            if (Array.isArray(value)) {
                return value.map(function (line, index) {
                    if (typeof line !== &quot;string&quot;) {
                        dataError(path + &quot;[&quot; + index + &quot;]&quot;, &quot;expected a string&quot;);
                    }

                    return line;
                });
            }

            if (value &amp;&amp; typeof value === &quot;object&quot;) {
                return Object.keys(value).map(function (key) {
                    return key + &quot;: &quot; + value[key];
                });
            }

            if (value !== undefined &amp;&amp; value !== null &amp;&amp; value !== &quot;&quot;) {
                return [String(value)];
            }

            return [];
        }

        function validateEntries(value, path) {
            if (Array.isArray(value)) {
                return value.map(function (item, index) {
                    const itemPath = path + &quot;[&quot; + index + &quot;]&quot;;

                    assertPlainObject(item, itemPath);
                    assertString(item.id, itemPath + &quot;.id&quot;);
                    assertId(item.id, itemPath + &quot;.id&quot;);

                    return {
                        id: item.id,
                        value: item,
                        path: itemPath
                    };
                });
            }

            assertPlainObject(value, path);

            return Object.keys(value).map(function (id) {
                assertId(id, path + &quot;.&quot; + id);
                assertPlainObject(value[id], path + &quot;.&quot; + id);

                return {
                    id: id,
                    value: value[id],
                    path: path + &quot;.&quot; + id
                };
            });
        }

        function validateReference(reference, path, label) {
            assertPlainObject(reference, path);
            assertAllowedKeys(reference, path, [&quot;object&quot;]);
            assertString(reference.object, path + &quot;.object&quot;);
            assertId(reference.object, path + &quot;.object&quot;);

            return {
                id: stableId(label, &quot;reference&quot;),
                label: String(label),
                target: reference.object,
                note: &quot;&quot;
            };
        }

        function validateBinding(binding, path, index) {
            assertPlainObject(binding, path);
            assertAllowedKeys(binding, path, [&quot;name&quot;, &quot;object&quot;]);
            assertString(binding.name, path + &quot;.name&quot;);

            return validateReference({ object: binding.object }, path, binding.name || index);
        }

        function validateFrame(entry) {
            const frame = entry.value;

            assertAllowedKeys(frame, entry.path, [&quot;id&quot;, &quot;name&quot;, &quot;bindings&quot;]);

            if (frame.id !== undefined &amp;&amp; frame.id !== entry.id) {
                dataError(entry.path + &quot;.id&quot;, &quot;must match key &quot; + entry.id);
            }

            if (frame.name !== undefined) {
                assertString(frame.name, entry.path + &quot;.name&quot;);
            }

            assertArray(frame.bindings, entry.path + &quot;.bindings&quot;);

            const bindingNames = new Set();

            return {
                id: entry.id,
                name: frame.name || entry.id,
                bindings: frame.bindings.map(function (binding, index) {
                    if (isPlainObject(binding) &amp;&amp; bindingNames.has(binding.name)) {
                        dataError(entry.path + &quot;.bindings[&quot; + index + &quot;].name&quot;, &quot;duplicate binding name &quot; + binding.name);
                    }

                    if (isPlainObject(binding)) {
                        bindingNames.add(binding.name);
                    }

                    return validateBinding(binding, entry.path + &quot;.bindings[&quot; + index + &quot;]&quot;, index);
                })
            };
        }

        function validateObjectValue(type, value, path) {
            if (type === &quot;int&quot;) {
                if (!Number.isInteger(value)) {
                    dataError(path, &quot;expected an integer for type int&quot;);
                }

                return;
            }

            if (type === &quot;float&quot;) {
                if (typeof value !== &quot;number&quot;) {
                    dataError(path, &quot;expected a number for type float&quot;);
                }

                return;
            }

            if (type === &quot;bool&quot;) {
                if (typeof value !== &quot;boolean&quot;) {
                    dataError(path, &quot;expected true or false for type bool&quot;);
                }

                return;
            }

            if (type === &quot;str&quot;) {
                if (typeof value !== &quot;string&quot;) {
                    dataError(path, &quot;expected a string for type str&quot;);
                }

                return;
            }

            if (type === &quot;NoneType&quot;) {
                if (value !== null) {
                    dataError(path, &quot;expected null for type NoneType&quot;);
                }
            }
        }

        function validateObject(entry) {
            const object = entry.value;
            const type = object.type || object.kind;
            let collectionItems = [];
            const supportedTypes = new Set([&quot;int&quot;, &quot;float&quot;, &quot;bool&quot;, &quot;NoneType&quot;, &quot;str&quot;, &quot;list&quot;, &quot;object&quot;]);

            assertAllowedKeys(object, entry.path, [&quot;id&quot;, &quot;type&quot;, &quot;kind&quot;, &quot;value&quot;, &quot;details&quot;]);

            if (object.id !== undefined &amp;&amp; object.id !== entry.id) {
                dataError(entry.path + &quot;.id&quot;, &quot;must match key &quot; + entry.id);
            }

            assertString(type, entry.path + &quot;.type&quot;);

            if (!supportedTypes.has(type)) {
                dataError(entry.path + &quot;.type&quot;, &quot;unsupported type &quot; + type);
            }

            if (object.type !== undefined &amp;&amp; object.kind !== undefined) {
                dataError(entry.path + &quot;.kind&quot;, &quot;use either type or kind, not both&quot;);
            }

            if (type === &quot;list&quot;) {
                assertArray(object.value, entry.path + &quot;.value&quot;);
                collectionItems = object.value.map(function (reference, index) {
                    return validateReference(reference, entry.path + &quot;.value[&quot; + index + &quot;]&quot;, index);
                });
            } else {
                if (object.value === undefined) {
                    dataError(entry.path + &quot;.value&quot;, &quot;expected a value&quot;);
                }

                validateObjectValue(type, object.value, entry.path + &quot;.value&quot;);
            }

            return {
                id: entry.id,
                type: type,
                value: object.value,
                details: normalizeDetails(object.details, entry.path + &quot;.details&quot;),
                collectionItems: collectionItems,
                missing: false
            };
        }

        function validateState(rawState) {
            assertPlainObject(rawState, &quot;root&quot;);
            assertAllowedKeys(rawState, &quot;root&quot;, [&quot;frames&quot;, &quot;objects&quot;]);

            if (rawState.frames === undefined) {
                dataError(&quot;root.frames&quot;, &quot;expected frames&quot;);
            }

            if (rawState.objects === undefined) {
                dataError(&quot;root.objects&quot;, &quot;expected objects&quot;);
            }

            const frameEntries = validateEntries(rawState.frames, &quot;frames&quot;);
            const objectEntries = validateEntries(rawState.objects, &quot;objects&quot;);
            const seenFrameIds = new Set();
            const seenIds = new Set();
            const frames = frameEntries.map(function (entry) {
                if (seenFrameIds.has(entry.id)) {
                    dataError(entry.path, &quot;duplicate frame id &quot; + entry.id);
                }

                seenFrameIds.add(entry.id);
                return validateFrame(entry);
            });
            const objects = objectEntries.map(function (entry) {
                if (seenIds.has(entry.id)) {
                    dataError(entry.path, &quot;duplicate object id &quot; + entry.id);
                }

                seenIds.add(entry.id);
                return validateObject(entry);
            });

            frames.forEach(function (frame) {
                frame.bindings.forEach(function (binding) {
                    if (!seenIds.has(binding.target)) {
                        dataError(&quot;frames.&quot; + frame.id + &quot;.bindings.&quot; + binding.label, &quot;unknown object &quot; + binding.target);
                    }
                });
            });

            objects.forEach(function (object) {
                object.collectionItems.forEach(function (item) {
                    if (!seenIds.has(item.target)) {
                        dataError(&quot;objects.&quot; + object.id + &quot;.value[&quot; + item.label + &quot;]&quot;, &quot;unknown object &quot; + item.target);
                    }
                });
            });

            return { frames: frames, objects: objects };
        }

        function textElement(tagName, className, text) {
            const element = document.createElement(tagName);
            element.className = className;
            element.textContent = text;
            return element;
        }

        function renderError(message) {
            const errorBox = document.createElement(&quot;div&quot;);
            errorBox.className = &quot;python-state-shortcode__error&quot;;
            errorBox.appendChild(textElement(&quot;div&quot;, &quot;python-state-shortcode__error-title&quot;, &quot;Python state data error&quot;));
            errorBox.appendChild(textElement(&quot;pre&quot;, &quot;python-state-shortcode__error-detail&quot;, message));
            nodes.appendChild(errorBox);

            fullscreenButton.disabled = true;
            stage.style.height = LAYOUT.minHeight + &quot;px&quot;;
        }

        function renderReference(reference, options) {
            const row = document.createElement(&quot;li&quot;);
            const isCollectionItem = options &amp;&amp; options.collectionItem;

            row.className = &quot;python-state-shortcode__reference&quot;
                + (isCollectionItem ? &quot; python-state-shortcode__reference--collection-item&quot; : &quot;&quot;);
            row.dataset.pythonStateTarget = reference.target;

            const label = textElement(&quot;span&quot;, &quot;python-state-shortcode__reference-label&quot;, reference.label);
            row.appendChild(label);

            const port = document.createElement(&quot;span&quot;);
            port.className = &quot;python-state-shortcode__reference-port&quot;;
            row.appendChild(port);

            return row;
        }

        function renderFrame(frame) {
            const box = document.createElement(&quot;section&quot;);
            box.className = &quot;python-state-shortcode__box python-state-shortcode__frame&quot;;
            box.dataset.pythonStateNodeId = frame.id;

            box.appendChild(textElement(&quot;div&quot;, &quot;python-state-shortcode__frame-title&quot;, frame.name));

            const list = document.createElement(&quot;ul&quot;);
            list.className = &quot;python-state-shortcode__bindings&quot;;

            frame.bindings.forEach(function (binding) {
                list.appendChild(renderReference(binding));
            });

            box.appendChild(list);
            return box;
        }

        function renderObjectContent(object, box) {
            const typeLine = textElement(&quot;div&quot;, &quot;python-state-shortcode__object-type&quot;, &quot;type: &quot; + object.type);
            box.appendChild(typeLine);

            const content = document.createElement(&quot;div&quot;);
            content.className = &quot;python-state-shortcode__object-content&quot;;

            if (object.collectionItems.length) {
                const collection = document.createElement(&quot;ul&quot;);
                collection.className = &quot;python-state-shortcode__collection&quot;;

                object.collectionItems.forEach(function (item) {
                    collection.appendChild(renderReference(item, { collectionItem: true }));
                });

                content.appendChild(collection);
            } else if (object.type === &quot;int&quot; || object.type === &quot;float&quot; || object.type === &quot;bool&quot; || object.type === &quot;NoneType&quot; || object.type === &quot;str&quot;) {
                content.appendChild(textElement(&quot;div&quot;, &quot;python-state-shortcode__literal&quot;, formatPythonLiteral(object.value, object.type)));
            } else if (object.details.length) {
                object.details.forEach(function (line) {
                    content.appendChild(textElement(&quot;div&quot;, &quot;python-state-shortcode__object-line&quot;, line));
                });
            } else if (object.value !== undefined) {
                content.appendChild(textElement(&quot;div&quot;, &quot;python-state-shortcode__literal&quot;, formatPythonLiteral(object.value, object.type)));
            }

            if (content.childNodes.length) {
                box.appendChild(content);
            }
        }

        function renderObject(object) {
            const box = document.createElement(&quot;section&quot;);
            const missingClass = object.missing ? &quot; python-state-shortcode__object--missing&quot; : &quot;&quot;;
            box.className = &quot;python-state-shortcode__box python-state-shortcode__object&quot; + missingClass;
            box.dataset.pythonStateNodeId = object.id;
            renderObjectContent(object, box);
            return box;
        }

        function rectInStage(element) {
            const stageRect = stage.getBoundingClientRect();
            const rect = element.getBoundingClientRect();

            return {
                left: rect.left - stageRect.left,
                top: rect.top - stageRect.top,
                right: rect.right - stageRect.left,
                bottom: rect.bottom - stageRect.top,
                width: rect.width,
                height: rect.height,
                centerX: rect.left - stageRect.left + rect.width &#x2F; 2,
                centerY: rect.top - stageRect.top + rect.height &#x2F; 2
            };
        }

        function getStageWidth() {
            const viewportWidth = viewport.clientWidth;
            return Math.max(viewportWidth, LAYOUT.minWidth);
        }

        function updateStageWidth(width) {
            stage.style.width = Math.max(getStageWidth(), width || 0) + &quot;px&quot;;
        }

        function resetStageWidthToViewport() {
            stage.style.width = getStageWidth() + &quot;px&quot;;
        }

        function setNodePosition(element, left, top) {
            element.style.left = left + &quot;px&quot;;
            element.style.top = top + &quot;px&quot;;
        }

        function captureNodeViewportPositions(nodeElements) {
            const positions = new Map();

            nodeElements.forEach(function (element) {
                const rect = element.getBoundingClientRect();
                positions.set(element, {
                    left: rect.left,
                    top: rect.top
                });
            });

            return positions;
        }

        function applyNodeViewportPositions(positions) {
            const stageRect = stage.getBoundingClientRect();

            positions.forEach(function (position, element) {
                setNodePosition(
                    element,
                    position.left - stageRect.left,
                    position.top - stageRect.top
                );
            });
        }

        function clampNodePosition(element) {
            const left = parseFloat(element.style.left) || 0;
            const top = parseFloat(element.style.top) || 0;
            const maxX = Math.max(0, stage.clientWidth - element.offsetWidth);
            const maxY = Math.max(0, stage.clientHeight - element.offsetHeight);

            setNodePosition(
                element,
                clamp(left, 0, maxX),
                clamp(top, 0, maxY)
            );
        }

        function fitNodeIntoCompactStage(element) {
            const left = parseFloat(element.style.left) || 0;
            const top = parseFloat(element.style.top) || 0;
            const maxX = Math.max(0, stage.clientWidth - element.offsetWidth);

            setNodePosition(
                element,
                clamp(left, 0, maxX),
                Math.max(0, top)
            );
        }

        function computeObjectLevels(state) {
            const levels = new Map();
            const objectsById = new Map();
            const referencesByObject = new Map();

            state.objects.forEach(function (object) {
                objectsById.set(object.id, object);
                referencesByObject.set(object.id, object.collectionItems.map(function (reference) {
                    return reference.target;
                }));
            });

            function reaches(startId, targetId, visited) {
                if (startId === targetId) {
                    return true;
                }

                if (visited.has(startId)) {
                    return false;
                }

                visited.add(startId);

                return (referencesByObject.get(startId) || []).some(function (nextId) {
                    return reaches(nextId, targetId, visited);
                });
            }

            state.frames.forEach(function (frame) {
                frame.bindings.forEach(function (binding) {
                    if (objectsById.has(binding.target) &amp;&amp; !levels.has(binding.target)) {
                        levels.set(binding.target, 1);
                    }
                });
            });

            state.objects.forEach(function (object) {
                if (!levels.has(object.id)) {
                    levels.set(object.id, 1);
                }
            });

            for (let pass = 0; pass &lt; state.objects.length; pass += 1) {
                let changed = false;

                state.objects.forEach(function (object) {
                    const sourceLevel = levels.get(object.id) || 1;

                    object.collectionItems.forEach(function (reference) {
                        if (reference.target === object.id || reaches(reference.target, object.id, new Set())) {
                            return;
                        }

                        const targetLevel = levels.get(reference.target) || 1;
                        const requiredLevel = sourceLevel + 1;

                        if (targetLevel &lt; requiredLevel) {
                            levels.set(reference.target, requiredLevel);
                            changed = true;
                        }
                    });
                });

                if (!changed) {
                    break;
                }
            }

            return levels;
        }

        function getObjectOriginalOrder(state) {
            const order = new Map();

            state.objects.forEach(function (object, index) {
                order.set(object.id, index);
            });

            return order;
        }

        function addReferenceScore(stats, target, candidateIds, score) {
            if (!candidateIds.has(target)) {
                return;
            }

            if (!stats.has(target)) {
                stats.set(target, {
                    count: 0,
                    sum: 0
                });
            }

            const targetStats = stats.get(target);
            targetStats.count += 1;
            targetStats.sum += score;
        }

        function getIncomingReferenceStats(state, objectLevels, objectColumnOrder, level, candidates) {
            const candidateIds = new Set(candidates.map(function (object) {
                return object.id;
            }));
            const stats = new Map();

            if (level === 1) {
                state.frames.forEach(function (frame, frameIndex) {
                    const referenceCount = Math.max(1, frame.bindings.length + 1);

                    frame.bindings.forEach(function (binding, bindingIndex) {
                        addReferenceScore(
                            stats,
                            binding.target,
                            candidateIds,
                            frameIndex + (bindingIndex + 1) &#x2F; referenceCount
                        );
                    });
                });
            }

            state.objects.forEach(function (object) {
                const sourceLevel = objectLevels.get(object.id) || 1;
                const sourceOrder = objectColumnOrder.get(object.id);

                if (sourceLevel &gt;= level || sourceOrder === undefined) {
                    return;
                }

                const referenceCount = Math.max(1, object.collectionItems.length + 1);

                object.collectionItems.forEach(function (reference, referenceIndex) {
                    addReferenceScore(
                        stats,
                        reference.target,
                        candidateIds,
                        sourceOrder + (referenceIndex + 1) &#x2F; referenceCount
                    );
                });
            });

            return stats;
        }

        function buildOrderedObjectColumns(state, objectElements, objectLevels) {
            const originalOrder = getObjectOriginalOrder(state);
            const objectColumnOrder = new Map();
            const objectsByLevel = new Map();
            const columns = [];
            let maxLevel = 0;

            state.objects.forEach(function (object) {
                if (!objectElements.has(object.id)) {
                    return;
                }

                const level = objectLevels.get(object.id) || 1;
                maxLevel = Math.max(maxLevel, level);

                if (!objectsByLevel.has(level)) {
                    objectsByLevel.set(level, []);
                }

                objectsByLevel.get(level).push(object);
            });

            for (let level = 1; level &lt;= maxLevel; level += 1) {
                const objects = objectsByLevel.get(level) || [];
                const incomingStats = getIncomingReferenceStats(
                    state,
                    objectLevels,
                    objectColumnOrder,
                    level,
                    objects
                );

                objects.sort(function (left, right) {
                    const leftStats = incomingStats.get(left.id);
                    const rightStats = incomingStats.get(right.id);
                    const leftScore = leftStats ? leftStats.sum &#x2F; leftStats.count : Number.POSITIVE_INFINITY;
                    const rightScore = rightStats ? rightStats.sum &#x2F; rightStats.count : Number.POSITIVE_INFINITY;

                    return leftScore - rightScore
                        || (originalOrder.get(left.id) || 0) - (originalOrder.get(right.id) || 0);
                });

                objects.forEach(function (object, index) {
                    objectColumnOrder.set(object.id, index);
                });

                if (objects.length) {
                    columns.push(objects.map(function (object) {
                        return objectElements.get(object.id);
                    }));
                }
            }

            return columns;
        }

        function buildObjectReferenceMap(state) {
            const referencesByObject = new Map();

            state.objects.forEach(function (object) {
                referencesByObject.set(object.id, object.collectionItems.map(function (reference) {
                    return reference.target;
                }));
            });

            return referencesByObject;
        }

        function objectReachesTarget(referencesByObject, startId, targetId, visited) {
            if (startId === targetId) {
                return true;
            }

            if (visited.has(startId)) {
                return false;
            }

            visited.add(startId);

            return (referencesByObject.get(startId) || []).some(function (nextId) {
                return objectReachesTarget(referencesByObject, nextId, targetId, visited);
            });
        }

        function collectReachableObjects(startIds, referencesByObject) {
            const reachable = new Set();
            const queue = startIds.slice();

            while (queue.length) {
                const id = queue.shift();

                if (reachable.has(id)) {
                    continue;
                }

                reachable.add(id);

                (referencesByObject.get(id) || []).forEach(function (targetId) {
                    queue.push(targetId);
                });
            }

            return reachable;
        }

        function inflateRect(rect, amount) {
            return {
                left: rect.left - amount,
                top: rect.top - amount,
                right: rect.right + amount,
                bottom: rect.bottom + amount,
                width: rect.width + amount * 2,
                height: rect.height + amount * 2,
                centerX: rect.centerX,
                centerY: rect.centerY
            };
        }

        function segmentIntersectsRect(start, end, rect) {
            const steps = 24;

            for (let step = 1; step &lt; steps; step += 1) {
                const ratio = step &#x2F; steps;
                const x = start.x + (end.x - start.x) * ratio;
                const y = start.y + (end.y - start.y) * ratio;

                if (x &gt;= rect.left &amp;&amp; x &lt;= rect.right &amp;&amp; y &gt;= rect.top &amp;&amp; y &lt;= rect.bottom) {
                    return true;
                }
            }

            return false;
        }

        function rectCenter(rect) {
            return {
                x: rect.centerX,
                y: rect.centerY
            };
        }

        function getTargetTopBelowObstacle(sourcePoint, targetPoint, targetElement, obstacleRect, pathRect) {
            const gap = LAYOUT.boxGap;
            const obstaclePathRect = pathRect || obstacleRect;
            const fallbackTop = obstaclePathRect.bottom + gap;
            const dx = targetPoint.x - sourcePoint.x;

            if (Math.abs(dx) &lt; 1) {
                return fallbackTop;
            }

            const segmentLeft = Math.min(sourcePoint.x, targetPoint.x);
            const segmentRight = Math.max(sourcePoint.x, targetPoint.x);
            const overlapLeft = Math.max(segmentLeft, obstaclePathRect.left);
            const overlapRight = Math.min(segmentRight, obstaclePathRect.right);

            if (overlapLeft &gt; overlapRight) {
                return fallbackTop;
            }

            const ratios = [overlapLeft, overlapRight].map(function (x) {
                return (x - sourcePoint.x) &#x2F; dx;
            }).filter(function (ratio) {
                return ratio &gt; 0 &amp;&amp; ratio &lt; 1;
            });

            if (!ratios.length) {
                return fallbackTop;
            }

            const closestObstacleRatio = Math.min.apply(null, ratios);
            const requiredCenterY = sourcePoint.y
                + (obstaclePathRect.bottom + gap - sourcePoint.y) &#x2F; closestObstacleRatio;

            return Math.max(fallbackTop, requiredCenterY - targetElement.offsetHeight &#x2F; 2);
        }

        function moveElementBelow(element, obstacleRect, sourcePoint, targetPoint, pathRect) {
            const top = parseFloat(element.style.top) || 0;
            const targetTop = getTargetTopBelowObstacle(sourcePoint, targetPoint, element, obstacleRect, pathRect);

            if (top &lt; targetTop) {
                setNodePosition(element, parseFloat(element.style.left) || 0, targetTop);
                return true;
            }

            return false;
        }

        function resolveColumnVerticalOverlaps(columns) {
            columns.forEach(function (column) {
                let bottom = LAYOUT.outerGap;

                column.forEach(function (element) {
                    const top = parseFloat(element.style.top) || 0;

                    if (top &lt; bottom) {
                        setNodePosition(element, parseFloat(element.style.left) || 0, bottom);
                    }

                    bottom = (parseFloat(element.style.top) || 0) + element.offsetHeight + LAYOUT.boxGap;
                });
            });
        }

        function avoidAlternativePathCollisions(state, frameElements, objectElements, objectColumns) {
            const referencesByObject = buildObjectReferenceMap(state);
            const objectIds = state.objects.map(function (object) {
                return object.id;
            });
            const edges = [];

            state.frames.forEach(function (frame, frameIndex) {
                const reachable = collectReachableObjects(frame.bindings.map(function (binding) {
                    return binding.target;
                }), referencesByObject);

                frame.bindings.forEach(function (binding) {
                    edges.push({
                        sourceElement: frameElements[frameIndex],
                        sourceId: &quot;&quot;,
                        sourceReachable: reachable,
                        targetId: binding.target
                    });
                });
            });

            state.objects.forEach(function (object) {
                const reachable = collectReachableObjects([object.id], referencesByObject);

                object.collectionItems.forEach(function (reference) {
                    edges.push({
                        sourceElement: objectElements.get(object.id),
                        sourceId: object.id,
                        sourceReachable: reachable,
                        targetId: reference.target
                    });
                });
            });

            for (let pass = 0; pass &lt; state.objects.length; pass += 1) {
                let changed = false;

                edges.forEach(function (edge) {
                    const targetElement = objectElements.get(edge.targetId);

                    if (!edge.sourceElement || !targetElement) {
                        return;
                    }

                    const sourceRect = rectInStage(edge.sourceElement);
                    const targetRect = rectInStage(targetElement);
                    const sourcePoint = rectCenter(sourceRect);
                    const targetPoint = rectCenter(targetRect);

                    objectIds.forEach(function (obstacleId) {
                        if (obstacleId === edge.sourceId || obstacleId === edge.targetId) {
                            return;
                        }

                        if (!edge.sourceReachable.has(obstacleId)) {
                            return;
                        }

                        if (!objectReachesTarget(referencesByObject, obstacleId, edge.targetId, new Set())) {
                            return;
                        }

                        const obstacleElement = objectElements.get(obstacleId);

                        if (!obstacleElement) {
                            return;
                        }

                        const obstacleRect = rectInStage(obstacleElement);
                        const pathRect = inflateRect(obstacleRect, LAYOUT.alternativePathClearance);

                        if (segmentIntersectsRect(sourcePoint, targetPoint, pathRect)) {
                            changed = moveElementBelow(targetElement, obstacleRect, sourcePoint, targetPoint, pathRect) || changed;
                        }
                    });
                });

                if (changed) {
                    resolveColumnVerticalOverlaps(objectColumns);
                } else {
                    break;
                }
            }
        }

        function positionDefault(state, frameElements, objectElements) {
            const objectLevels = computeObjectLevels(state);
            const columns = [frameElements].concat(
                buildOrderedObjectColumns(state, objectElements, objectLevels)
            );
            const columnWidths = columns.map(function (column) {
                if (!column || !column.length) {
                    return 0;
                }

                return column.reduce(function (width, element) {
                    return Math.max(width, element.offsetWidth);
                }, 0);
            });
            const columnHeights = columns.map(function (column) {
                if (!column || !column.length) {
                    return 0;
                }

                return column.reduce(function (height, element) {
                    return height + element.offsetHeight;
                }, 0) + LAYOUT.boxGap * (column.length - 1);
            });
            const objectColumns = columns.slice(1);
            const objectColumnWidths = columnWidths.slice(1);
            const regularColumnGap = Math.max(44, Math.round(getStageWidth() * 0.07));
            const objectGaps = objectColumns.slice(0, -1).map(function () {
                return regularColumnGap;
            });
            const objectContentWidth = objectColumnWidths.reduce(function (sum, width) {
                return sum + width;
            }, 0) + objectGaps.reduce(function (sum, gap) {
                return sum + gap;
            }, 0);
            const contentHeight = columnHeights.reduce(function (height, columnHeight) {
                return Math.max(height, columnHeight);
            }, 0);
            const frameContentWidth = columnWidths[0] || 0;
            const requiredUsableWidth = Math.max(
                frameContentWidth * 3,
                objectContentWidth * 1.5
            );
            const stageWidth = Math.max(
                getStageWidth(),
                Math.ceil(requiredUsableWidth + LAYOUT.outerGap * 2)
            );
            const stageHeight = Math.max(LAYOUT.minHeight, contentHeight + LAYOUT.outerGap * 2);
            const usableWidth = stageWidth - LAYOUT.outerGap * 2;
            const frameAreaLeft = LAYOUT.outerGap;
            const frameAreaWidth = usableWidth &#x2F; 3;
            const objectAreaLeft = frameAreaLeft + frameAreaWidth;
            const objectAreaWidth = usableWidth * 2 &#x2F; 3;
            const yStart = Math.max(LAYOUT.outerGap, Math.round((stageHeight - contentHeight) &#x2F; 2));

            updateStageWidth(stageWidth);
            stage.style.height = stageHeight + &quot;px&quot;;

            if (columns[0] &amp;&amp; columns[0].length) {
                let frameY = yStart;
                const frameX = Math.round(
                    frameAreaLeft + Math.max(0, (frameAreaWidth - frameContentWidth) &#x2F; 2)
                );

                columns[0].forEach(function (element) {
                    setNodePosition(element, frameX, frameY);
                    frameY += element.offsetHeight + LAYOUT.boxGap;
                });
            }

            let objectX = Math.round(
                objectAreaLeft + Math.max(0, (objectAreaWidth - objectContentWidth) &#x2F; 2)
            );

            objectColumns.forEach(function (column, index) {
                if (!column || !column.length) {
                    return;
                }

                let y = yStart;
                column.forEach(function (element) {
                    setNodePosition(element, objectX, y);
                    y += element.offsetHeight + LAYOUT.boxGap;
                });

                objectX += objectColumnWidths[index] + (objectGaps[index] || 0);
            });

            avoidAlternativePathCollisions(state, frameElements, objectElements, objectColumns);
        }

        function updateStageHeight(nodeElements) {
            let bottom = isFullscreen()
                ? Math.max(LAYOUT.minHeight, viewport.clientHeight)
                : LAYOUT.minHeight;

            nodeElements.forEach(function (element) {
                const rect = rectInStage(element);
                bottom = Math.max(bottom, rect.bottom + LAYOUT.outerGap);
            });

            stage.style.height = bottom + &quot;px&quot;;
            arrowsSvg.setAttribute(&quot;viewBox&quot;, &quot;0 0 &quot; + stage.offsetWidth + &quot; &quot; + bottom);
        }

        function updateStageWidthToFit(nodeElements) {
            let right = LAYOUT.minWidth;

            nodeElements.forEach(function (element) {
                const rect = rectInStage(element);
                right = Math.max(right, rect.right + LAYOUT.outerGap);
            });

            updateStageWidth(right);
        }

        function sideNormal(side) {
            if (side === &quot;left&quot;) {
                return { x: -1, y: 0 };
            }

            if (side === &quot;right&quot;) {
                return { x: 1, y: 0 };
            }

            if (side === &quot;top&quot;) {
                return { x: 0, y: -1 };
            }

            return { x: 0, y: 1 };
        }

        function chooseConnectionSide(fromRect, toRect) {
            if (toRect.right &lt; fromRect.left) {
                return &quot;left&quot;;
            }

            if (toRect.left &gt; fromRect.right) {
                return &quot;right&quot;;
            }

            if (toRect.bottom &lt; fromRect.top) {
                return &quot;top&quot;;
            }

            if (toRect.top &gt; fromRect.bottom) {
                return &quot;bottom&quot;;
            }

            const dx = toRect.centerX - fromRect.centerX;
            const dy = toRect.centerY - fromRect.centerY;

            if (Math.abs(dx) &gt;= Math.abs(dy)) {
                return dx &gt;= 0 ? &quot;right&quot; : &quot;left&quot;;
            }

            return dy &gt;= 0 ? &quot;bottom&quot; : &quot;top&quot;;
        }

        function chooseReferenceSide(sourceRect, targetRect) {
            return targetRect.centerX &lt; sourceRect.centerX ? &quot;left&quot; : &quot;right&quot;;
        }

        function chooseReferenceSourceSide(reference, sourceRect, targetRect) {
            if (reference.classList.contains(&quot;python-state-shortcode__reference--collection-item&quot;)) {
                return chooseConnectionSide(rectInStage(reference), targetRect);
            }

            return chooseReferenceSide(sourceRect, targetRect);
        }

        function borderPointForSide(rect, side, anchor) {
            const horizontalPadding = Math.min(LAYOUT.cornerGap, rect.width &#x2F; 2);
            const verticalPadding = Math.min(LAYOUT.cornerGap, rect.height &#x2F; 2);

            if (side === &quot;left&quot; || side === &quot;right&quot;) {
                return {
                    x: side === &quot;left&quot; ? rect.left : rect.right,
                    y: clamp(anchor.y, rect.top + verticalPadding, rect.bottom - verticalPadding),
                    side: side
                };
            }

            return {
                x: clamp(anchor.x, rect.left + horizontalPadding, rect.right - horizontalPadding),
                y: side === &quot;top&quot; ? rect.top : rect.bottom,
                side: side
            };
        }

        function outsidePoint(point, amount) {
            const normal = sideNormal(point.side);

            return {
                x: point.x + normal.x * amount,
                y: point.y + normal.y * amount,
                side: point.side
            };
        }

        function pointFromPort(portRect, side) {
            if (side === &quot;bottom&quot;) {
                return {
                    x: portRect.centerX,
                    y: portRect.centerY,
                    side: side
                };
            }

            if (side === &quot;top&quot;) {
                return {
                    x: portRect.centerX,
                    y: portRect.centerY,
                    side: side
                };
            }

            if (side === &quot;left&quot;) {
                return {
                    x: portRect.left,
                    y: portRect.centerY,
                    side: side
                };
            }

            return {
                x: portRect.right,
                y: portRect.centerY,
                side: side
            };
        }

        function createArrowGeometry(start, end, arrowheadLength) {
            const dx = end.x - start.x;
            const dy = end.y - start.y;
            const distance = Math.max(70, Math.sqrt(dx * dx + dy * dy));
            const bend = Math.min(90, distance * 0.32);
            const startNormal = sideNormal(start.side);
            const endNormal = sideNormal(end.side);
            const lineEnd = {
                x: end.x + endNormal.x * arrowheadLength,
                y: end.y + endNormal.y * arrowheadLength,
                side: end.side
            };
            const control1 = {
                x: start.x + startNormal.x * bend,
                y: start.y + startNormal.y * bend
            };
            const control2 = {
                x: end.x + endNormal.x * bend,
                y: end.y + endNormal.y * bend
            };

            return {
                path: [
                    &quot;M&quot;, start.x, start.y,
                    &quot;C&quot;, control1.x, control1.y, control2.x, control2.y, lineEnd.x, lineEnd.y
                ].join(&quot; &quot;),
                control2: control2
            };
        }

        function createArrowheadPoints(end, controlPoint, length, width) {
            const dx = end.x - controlPoint.x;
            const dy = end.y - controlPoint.y;
            const distance = Math.sqrt(dx * dx + dy * dy) || 1;
            const ux = dx &#x2F; distance;
            const uy = dy &#x2F; distance;
            const nx = -uy;
            const ny = ux;
            const baseX = end.x - ux * length;
            const baseY = end.y - uy * length;

            return [
                [end.x, end.y],
                [baseX + nx * width, baseY + ny * width],
                [baseX - nx * width, baseY - ny * width]
            ].map(function (point) {
                return point[0] + &quot;,&quot; + point[1];
            }).join(&quot; &quot;);
        }

        function svgElement(tagName, className) {
            const element = document.createElementNS(&quot;http:&#x2F;&#x2F;www.w3.org&#x2F;2000&#x2F;svg&quot;, tagName);
            element.classList.add(className);
            return element;
        }

        function createEdgePath(className, pathData) {
            const path = svgElement(&quot;path&quot;, className);

            path.setAttribute(&quot;d&quot;, pathData);

            return path;
        }

        function createEdgeArrowhead(className, points) {
            const arrowhead = svgElement(&quot;polygon&quot;, className);

            arrowhead.setAttribute(&quot;points&quot;, points);

            return arrowhead;
        }

        function createEdgeCircle(className, point, radius) {
            const circle = svgElement(&quot;circle&quot;, className);

            circle.setAttribute(&quot;cx&quot;, point.x);
            circle.setAttribute(&quot;cy&quot;, point.y);
            circle.setAttribute(&quot;r&quot;, radius);

            return circle;
        }

        function setEdgeActive(edgeId, active) {
            const edge = arrowsSvg.querySelector(&quot;[data-python-state-edge-id=\&quot;&quot; + edgeId + &quot;\&quot;]&quot;);

            if (edge) {
                edge.classList.toggle(&quot;python-state-shortcode__edge--active&quot;, active);
            }
        }

        function drawArrows(objectElements) {
            arrowsSvg.querySelectorAll(&quot;.python-state-shortcode__edge&quot;).forEach(function (edge) {
                edge.remove();
            });

            root.querySelectorAll(&quot;[data-python-state-target]&quot;).forEach(function (reference, index) {
                const target = reference.dataset.pythonStateTarget;
                const object = objectElements.get(target);

                if (!target || !object) {
                    return;
                }

                const port = reference.querySelector(&quot;.python-state-shortcode__reference-port&quot;);
                const sourceBox = reference.closest(&quot;.python-state-shortcode__box&quot;);
                const sourceRect = rectInStage(sourceBox);
                const objectRect = rectInStage(object);
                const sourceSide = chooseReferenceSourceSide(reference, sourceRect, objectRect);

                reference.dataset.pythonStateReferenceSide = sourceSide;

                const portRect = rectInStage(port || reference);
                const portAnchor = { x: portRect.centerX, y: portRect.centerY };
                const targetSide = chooseConnectionSide(objectRect, portRect);
                const targetBorder = borderPointForSide(objectRect, targetSide, portAnchor);
                const visualPortPoint = rectCenter(portRect);
                const linePortPoint = pointFromPort(portRect, sourceSide);
                const start = outsidePoint(linePortPoint, LAYOUT.arrowGap);
                const end = outsidePoint(targetBorder, LAYOUT.arrowGap);
                const arrowheadLength = 8;
                const arrowGeometry = createArrowGeometry(start, end, arrowheadLength);
                const pathData = arrowGeometry.path;
                const arrowheadPoints = createArrowheadPoints(end, arrowGeometry.control2, arrowheadLength, 4);
                const edge = svgElement(&quot;g&quot;, &quot;python-state-shortcode__edge&quot;);
                const edgeId = &quot;edge-&quot; + index;

                edge.setAttribute(&quot;aria-hidden&quot;, &quot;true&quot;);
                edge.dataset.pythonStateEdgeId = edgeId;
                reference.dataset.pythonStateEdgeId = edgeId;
                reference.onmouseenter = function () {
                    setEdgeActive(edgeId, true);
                };
                reference.onmouseleave = function () {
                    setEdgeActive(edgeId, false);
                };
                edge.addEventListener(&quot;mouseenter&quot;, function () {
                    setEdgeActive(edgeId, true);
                });
                edge.addEventListener(&quot;mouseleave&quot;, function () {
                    setEdgeActive(edgeId, false);
                });
                edge.appendChild(createEdgePath(&quot;python-state-shortcode__edge-hit&quot;, pathData));
                edge.appendChild(createEdgePath(&quot;python-state-shortcode__edge-halo&quot;, pathData));
                edge.appendChild(createEdgePath(&quot;python-state-shortcode__edge-line&quot;, pathData));
                edge.appendChild(createEdgeArrowhead(&quot;python-state-shortcode__edge-arrowhead-halo&quot;, arrowheadPoints));
                edge.appendChild(createEdgeArrowhead(&quot;python-state-shortcode__edge-arrowhead&quot;, arrowheadPoints));
                edge.appendChild(createEdgeCircle(&quot;python-state-shortcode__edge-port-halo&quot;, visualPortPoint, 5.2));
                edge.appendChild(createEdgeCircle(&quot;python-state-shortcode__edge-port&quot;, visualPortPoint, 4.2));
                arrowsSvg.appendChild(edge);
            });
        }

        function isFullscreen() {
            return root.classList.contains(&quot;python-state-shortcode--fullscreen&quot;);
        }

        function isTransparentColor(value) {
            return !value || value === &quot;transparent&quot; || value === &quot;rgba(0, 0, 0, 0)&quot;;
        }

        function getPageBackgroundColor() {
            const candidates = [document.body, document.documentElement, root.parentElement].filter(Boolean);

            for (let index = 0; index &lt; candidates.length; index += 1) {
                const background = window.getComputedStyle(candidates[index]).backgroundColor;

                if (!isTransparentColor(background)) {
                    return background;
                }
            }

            return &quot;transparent&quot;;
        }

        function updateFullpageMetrics() {
            const nav = document.querySelector(&quot;nav&quot;);
            const navRect = nav ? nav.getBoundingClientRect() : null;
            const top = navRect ? Math.max(0, Math.round(navRect.bottom)) : 0;

            root.style.setProperty(&quot;--python-state-fullpage-top&quot;, top + &quot;px&quot;);
            root.style.setProperty(&quot;--python-state-fullpage-bg&quot;, getPageBackgroundColor());
        }

        function setPageScrollLocked(locked) {
            if (locked) {
                savedDocumentOverflow = document.documentElement.style.overflow;
                savedBodyOverflow = document.body.style.overflow;
                document.documentElement.style.overflow = &quot;hidden&quot;;
                document.body.style.overflow = &quot;hidden&quot;;
                return;
            }

            document.documentElement.style.overflow = savedDocumentOverflow;
            document.body.style.overflow = savedBodyOverflow;
        }

        function refreshGeometry(allNodeElements, objectElements) {
            updateStageHeight(allNodeElements);
            drawArrows(objectElements);
        }

        function setFullscreenMode(enabled, allNodeElements, objectElements) {
            if (enabled === isFullscreen()) {
                return;
            }

            const preTogglePositions = captureNodeViewportPositions(allNodeElements);

            if (enabled) {
                updateFullpageMetrics();
            }

            root.classList.toggle(&quot;python-state-shortcode--fullscreen&quot;, enabled);
            fullscreenButton.setAttribute(&quot;aria-pressed&quot;, enabled ? &quot;true&quot; : &quot;false&quot;);
            fullscreenButton.textContent = enabled ? &quot;Exit&quot; : &quot;Full page&quot;;
            setPageScrollLocked(enabled);

            window.requestAnimationFrame(function () {
                if (enabled) {
                    applyNodeViewportPositions(preTogglePositions);
                    updateStageWidthToFit(allNodeElements);
                } else {
                    resetStageWidthToViewport();
                    applyNodeViewportPositions(preTogglePositions);
                    allNodeElements.forEach(fitNodeIntoCompactStage);
                }

                refreshGeometry(allNodeElements, objectElements);
            });
        }

        function makeDraggable(element, objectElements) {
            let offsetX = 0;
            let offsetY = 0;
            let dragging = false;

            element.addEventListener(&quot;pointerdown&quot;, function (event) {
                dragging = true;
                element.classList.add(&quot;python-state-shortcode__box--dragging&quot;);
                element.setPointerCapture(event.pointerId);

                const rect = element.getBoundingClientRect();
                offsetX = event.clientX - rect.left;
                offsetY = event.clientY - rect.top;
            });

            element.addEventListener(&quot;pointermove&quot;, function (event) {
                if (!dragging) {
                    return;
                }

                const stageRect = stage.getBoundingClientRect();
                const maxX = Math.max(0, stage.clientWidth - element.offsetWidth);
                const maxY = Math.max(0, stage.clientHeight - element.offsetHeight);
                const x = clamp(event.clientX - stageRect.left - offsetX, 0, maxX);
                const y = clamp(event.clientY - stageRect.top - offsetY, 0, maxY);

                setNodePosition(element, x, y);
                drawArrows(objectElements);
            });

            function stopDragging() {
                dragging = false;
                element.classList.remove(&quot;python-state-shortcode__box--dragging&quot;);
            }

            element.addEventListener(&quot;pointerup&quot;, stopDragging);
            element.addEventListener(&quot;pointercancel&quot;, stopDragging);
        }

        function render(state) {
            const frameElements = state.frames.map(function (frame) {
                const frameElement = renderFrame(frame);
                nodes.appendChild(frameElement);
                return frameElement;
            });
            const objectElements = new Map();

            state.objects.forEach(function (object) {
                const objectElement = renderObject(object);
                objectElements.set(object.id, objectElement);
                nodes.appendChild(objectElement);
            });

            const allNodeElements = frameElements.concat(Array.from(objectElements.values()));

            positionDefault(state, frameElements, objectElements);
            updateStageHeight(allNodeElements);
            drawArrows(objectElements);

            allNodeElements.forEach(function (element) {
                makeDraggable(element, objectElements);
            });

            fullscreenButton.addEventListener(&quot;click&quot;, function () {
                setFullscreenMode(!isFullscreen(), allNodeElements, objectElements);
            });

            document.addEventListener(&quot;keydown&quot;, function (event) {
                if (event.key === &quot;Escape&quot; &amp;&amp; isFullscreen()) {
                    setFullscreenMode(false, allNodeElements, objectElements);
                }
            });

            window.addEventListener(&quot;resize&quot;, function () {
                if (isFullscreen()) {
                    updateFullpageMetrics();
                    updateStageWidthToFit(allNodeElements);
                } else {
                    resetStageWidthToViewport();
                }

                allNodeElements.forEach(clampNodePosition);
                refreshGeometry(allNodeElements, objectElements);
            });
        }

        const parsed = parseState();

        if (parsed.error) {
            renderError(parsed.error);
        } else {
            render(parsed.state);
        }
    }());
    &lt;&#x2F;script&gt;
&lt;&#x2F;div&gt;
&lt;p&gt;На второй строке, во-первых, в результате вычисления выражения &lt;code&gt;a + 1&lt;&#x2F;code&gt; мы получили новый объект, с типом &lt;code&gt;int&lt;&#x2F;code&gt; и значением &lt;code&gt;два&lt;&#x2F;code&gt;. Во-вторых, мы изменили ссылку переменной &lt;code&gt;a&lt;&#x2F;code&gt;: теперь она указывает не на объект &lt;code&gt;1&lt;&#x2F;code&gt;, а на объект &lt;code&gt;2&lt;&#x2F;code&gt;. При этом мы никак не изменяли сами объекты.&lt;&#x2F;p&gt;




&lt;style&gt;
.python-state-shortcode {
    position: relative;
    border: 1px solid white;
    border-radius: 2px;
    overflow: hidden;
    padding: 1em;
    margin: 2em 0;
}

.python-state-shortcode__header {
    position: absolute;
    top: 0.75rem;
    right: 0.75rem;
    z-index: 4;
    pointer-events: none;
}

.python-state-shortcode__fullscreen-button {
    border: 1px solid white;
    border-radius: 2px;
    background: transparent;
    color: inherit;
    padding: 0.3em 0.55em;
    font: inherit;
    font-size: 0.78rem;
    line-height: 1;
    cursor: pointer;
    pointer-events: auto;
}

.python-state-shortcode__fullscreen-button:hover,
.python-state-shortcode__fullscreen-button:focus-visible {
    background: rgba(255, 255, 255, 0.12);
}

.python-state-shortcode__viewport {
    overflow-x: auto;
}

.python-state-shortcode__stage {
    position: relative;
    min-width: 560px;
    min-height: 240px;
    overflow: hidden;
}

.python-state-shortcode__arrows {
    position: absolute;
    inset: 0;
    width: 100%;
    height: 100%;
    pointer-events: auto;
    z-index: 1;
}

.python-state-shortcode__nodes {
    position: absolute;
    inset: 0;
    z-index: 2;
}

.python-state-shortcode__box {
    position: absolute;
    box-sizing: border-box;
    width: max-content;
    min-width: 4.5rem;
    max-width: 18rem;
    border: 1px solid white;
    border-radius: 2px;
    background: var(--bg, transparent);
    padding: 0.55em 0.7em;
    cursor: grab;
    user-select: none;
    touch-action: none;
}

.python-state-shortcode__box:active {
    cursor: grabbing;
}

.python-state-shortcode__box--dragging {
    z-index: 3;
}

.python-state-shortcode__frame-title {
    font-weight: 600;
    line-height: 1.2;
    margin-bottom: 0.45em;
    white-space: nowrap;
}

.python-state-shortcode__object-type {
    line-height: 1.2;
    margin-bottom: 0.45em;
    white-space: nowrap;
}

.python-state-shortcode__bindings,
.python-state-shortcode__object-content {
    display: grid;
    gap: 0.3em;
    margin: 0;
    padding: 0;
    list-style: none;
}

.python-state-shortcode__collection {
    display: flex;
    flex-flow: row nowrap;
    gap: 0;
    margin: 0;
    padding: 0.15em 0 0.25em 0;
    list-style: none;
}

.python-state-shortcode__reference {
    position: relative;
    display: block;
    box-sizing: border-box;
    width: 100%;
    padding-right: 1.05em;
    min-height: 1.35em;
    line-height: 1.25;
}

.python-state-shortcode__reference-label,
.python-state-shortcode__literal,
.python-state-shortcode__object-line {
    overflow-wrap: anywhere;
    font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, &quot;Liberation Mono&quot;, monospace;
    font-size: 0.9rem;
}

.python-state-shortcode__reference-label {
    display: block;
    white-space: nowrap;
}

.python-state-shortcode__reference-port {
    position: absolute;
    top: 50%;
    right: 0.2em;
    width: 0.48em;
    height: 0.48em;
    transform: translateY(-50%);
    opacity: 0;
    pointer-events: none;
}

.python-state-shortcode__reference[data-python-state-reference-side=&quot;left&quot;] {
    padding-right: 0;
    padding-left: 1.05em;
}

.python-state-shortcode__reference[data-python-state-reference-side=&quot;left&quot;] .python-state-shortcode__reference-port {
    right: auto;
    left: 0.2em;
}

.python-state-shortcode__reference--collection-item {
    display: flex;
    align-items: center;
    justify-content: center;
    width: 2.15em;
    height: 2.15em;
    min-height: 0;
    padding: 0;
    border: 1px solid white;
    box-sizing: border-box;
}

.python-state-shortcode__reference--collection-item + .python-state-shortcode__reference--collection-item {
    border-left: 0;
}

.python-state-shortcode__reference--collection-item .python-state-shortcode__reference-label {
    display: none;
}

.python-state-shortcode__reference--collection-item .python-state-shortcode__reference-port,
.python-state-shortcode__reference--collection-item[data-python-state-reference-side=&quot;left&quot;] .python-state-shortcode__reference-port {
    top: 50%;
    right: auto;
    bottom: auto;
    left: 50%;
    transform: translate(-50%, -50%);
}

.python-state-shortcode__literal {
    line-height: 1.35;
}

.python-state-shortcode__object--missing {
    border-style: dashed;
}

.python-state-shortcode__edge {
    pointer-events: visibleStroke;
    color: #60a5fa;
}

.python-state-shortcode__edge-hit {
    fill: none;
    stroke: transparent;
    stroke-width: 14;
    stroke-linecap: round;
    stroke-linejoin: round;
    pointer-events: stroke;
}

.python-state-shortcode__edge-halo {
    fill: none;
    stroke: rgba(255, 255, 255, 0);
    stroke-width: 4;
    stroke-linecap: round;
    stroke-linejoin: round;
    transition: stroke 140ms ease;
}

.python-state-shortcode__edge-line {
    fill: none;
    stroke: currentColor;
    stroke-width: 1.5;
    stroke-linecap: round;
    stroke-linejoin: round;
    transition: stroke 140ms ease;
}

.python-state-shortcode__edge-port-halo {
    fill: none;
    stroke: rgba(255, 255, 255, 0);
    stroke-width: 5;
    transition: stroke 140ms ease;
}

.python-state-shortcode__edge-arrowhead-halo {
    fill: none;
    stroke: rgba(255, 255, 255, 0);
    stroke-width: 3;
    stroke-linejoin: round;
    transition: stroke 140ms ease;
}

.python-state-shortcode__edge-arrowhead {
    fill: currentColor;
    transition: fill 140ms ease;
}

.python-state-shortcode__edge-port {
    fill: currentColor;
    stroke: currentColor;
    stroke-width: 1;
    transition: fill 140ms ease, stroke 140ms ease, r 140ms ease;
}

.python-state-shortcode__edge:hover,
.python-state-shortcode__edge:focus-visible,
.python-state-shortcode__edge--active {
    color: #93c5fd;
    outline: none;
}

.python-state-shortcode__edge:hover .python-state-shortcode__edge-halo,
.python-state-shortcode__edge:focus-visible .python-state-shortcode__edge-halo,
.python-state-shortcode__edge--active .python-state-shortcode__edge-halo,
.python-state-shortcode__edge:hover .python-state-shortcode__edge-port-halo,
.python-state-shortcode__edge:focus-visible .python-state-shortcode__edge-port-halo,
.python-state-shortcode__edge--active .python-state-shortcode__edge-port-halo {
    stroke: rgba(255, 255, 255, 0.92);
}

.python-state-shortcode__edge:hover .python-state-shortcode__edge-arrowhead-halo,
.python-state-shortcode__edge:focus-visible .python-state-shortcode__edge-arrowhead-halo,
.python-state-shortcode__edge--active .python-state-shortcode__edge-arrowhead-halo {
    stroke: rgba(255, 255, 255, 0.92);
}

.python-state-shortcode__error {
    border: 1px solid white;
    border-radius: 2px;
    padding: 0.75em 0.9em;
    max-width: 42rem;
    line-height: 1.35;
}

.python-state-shortcode__error-title {
    margin-bottom: 0.45em;
    font-weight: 600;
}

.python-state-shortcode__error-detail {
    margin: 0;
    white-space: pre-wrap;
    overflow-wrap: anywhere;
    font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, &quot;Liberation Mono&quot;, monospace;
    font-size: 0.9rem;
}

.python-state-shortcode--fullscreen {
    position: fixed;
    top: var(--python-state-fullpage-top, 0);
    right: 0;
    bottom: 0;
    left: 0;
    z-index: 1000;
    display: flex;
    flex-direction: column;
    box-sizing: border-box;
    width: 100vw;
    height: calc(100vh - var(--python-state-fullpage-top, 0px));
    height: calc(100dvh - var(--python-state-fullpage-top, 0px));
    margin: 0;
    border: 0;
    border-radius: 0;
    padding: 0;
    background: var(--python-state-fullpage-bg, transparent);
    overflow: hidden;
}

.python-state-shortcode--fullscreen .python-state-shortcode__header {
    margin: 0;
}

.python-state-shortcode--fullscreen .python-state-shortcode__viewport {
    flex: 1 1 auto;
    min-height: 0;
    overflow: auto;
}

.python-state-shortcode--fullscreen .python-state-shortcode__stage {
    min-height: 100%;
}
&lt;&#x2F;style&gt;

&lt;div class=&quot;python-state-shortcode&quot; data-python-state-shortcode&gt;
    &lt;div class=&quot;python-state-shortcode__header&quot;&gt;
        &lt;button class=&quot;python-state-shortcode__fullscreen-button&quot; type=&quot;button&quot; data-python-state-fullscreen-button aria-pressed=&quot;false&quot;&gt;Full page&lt;&#x2F;button&gt;
    &lt;&#x2F;div&gt;

    &lt;div class=&quot;python-state-shortcode__viewport&quot;&gt;
        &lt;div class=&quot;python-state-shortcode__stage&quot; data-python-state-stage aria-label=&quot;Python state&quot;&gt;
            &lt;svg class=&quot;python-state-shortcode__arrows&quot; data-python-state-arrows aria-hidden=&quot;true&quot;&gt;&lt;&#x2F;svg&gt;
            &lt;div class=&quot;python-state-shortcode__nodes&quot; data-python-state-nodes&gt;&lt;&#x2F;div&gt;
        &lt;&#x2F;div&gt;
    &lt;&#x2F;div&gt;

    &lt;script type=&quot;text&#x2F;plain&quot; class=&quot;python-state-shortcode__data&quot; data-python-state-data&gt;
frames:
  global:
    name: Global frame
    bindings:
      - name: a
        object: a
objects:
  a:
    type: int
    value: 2
    &lt;&#x2F;script&gt;

    &lt;script&gt;
    (function () {
        const currentScript = document.currentScript;
        const root = currentScript.closest(&quot;[data-python-state-shortcode]&quot;);
        const viewport = root.querySelector(&quot;.python-state-shortcode__viewport&quot;);
        const stage = root.querySelector(&quot;[data-python-state-stage]&quot;);
        const nodes = root.querySelector(&quot;[data-python-state-nodes]&quot;);
        const arrowsSvg = root.querySelector(&quot;[data-python-state-arrows]&quot;);
        const dataNode = root.querySelector(&quot;[data-python-state-data]&quot;);
        const fullscreenButton = root.querySelector(&quot;[data-python-state-fullscreen-button]&quot;);
        let savedDocumentOverflow = &quot;&quot;;
        let savedBodyOverflow = &quot;&quot;;

        const LAYOUT = {
            minWidth: 560,
            minHeight: 240,
            outerGap: 18,
            boxGap: 14,
            arrowGap: 7,
            cornerGap: 10,
            alternativePathClearance: 18
        };

        function stableId(value, fallback) {
            const raw = String(value || fallback || &quot;item&quot;).trim().toLowerCase();
            const slug = raw.replace(&#x2F;[^a-z0-9_-]+&#x2F;g, &quot;-&quot;).replace(&#x2F;^-+|-+$&#x2F;g, &quot;&quot;);
            return slug || String(fallback || &quot;item&quot;);
        }

        function clamp(value, min, max) {
            return Math.max(min, Math.min(max, value));
        }

        function parseState() {
            const raw = dataNode.textContent.trim();

            if (!raw) {
                return {
                    error: &quot;python_state shortcode expects a JSON or YAML body.&quot;
                };
            }

            try {
                const parsed = raw[0] === &quot;{&quot; || raw[0] === &quot;[&quot;
                    ? JSON.parse(raw)
                    : parseYamlSubset(raw);

                return {
                    state: validateState(parsed)
                };
            } catch (error) {
                return {
                    error: &quot;Invalid python_state shortcode data:\n&quot; + error.message
                };
            }
        }

        function dataError(path, message) {
            throw new Error(path + &quot;: &quot; + message);
        }

        function isPlainObject(value) {
            return Boolean(value) &amp;&amp; typeof value === &quot;object&quot; &amp;&amp; !Array.isArray(value);
        }

        function assertPlainObject(value, path) {
            if (!isPlainObject(value)) {
                dataError(path, &quot;expected an object&quot;);
            }
        }

        function assertArray(value, path) {
            if (!Array.isArray(value)) {
                dataError(path, &quot;expected an array&quot;);
            }
        }

        function assertAllowedKeys(value, path, allowedKeys) {
            const allowed = new Set(allowedKeys);

            Object.keys(value).forEach(function (key) {
                if (!allowed.has(key)) {
                    dataError(path + &quot;.&quot; + key, &quot;unknown field&quot;);
                }
            });
        }

        function assertId(value, path) {
            if (typeof value !== &quot;string&quot; || !&#x2F;^[A-Za-z_][A-Za-z0-9_-]*$&#x2F;.test(value)) {
                dataError(path, &quot;expected an object id like items or value_1&quot;);
            }
        }

        function assertString(value, path) {
            if (typeof value !== &quot;string&quot; || !value.trim()) {
                dataError(path, &quot;expected a non-empty string&quot;);
            }
        }

        function parseYamlSubset(raw) {
            const lines = raw.replace(&#x2F;\r\n?&#x2F;g, &quot;\n&quot;).split(&quot;\n&quot;);
            const tokens = [];

            lines.forEach(function (line, index) {
                if (!line.trim() || &#x2F;^\s*#&#x2F;.test(line)) {
                    return;
                }

                if (&#x2F;^\s*\t&#x2F;.test(line)) {
                    dataError(&quot;line &quot; + (index + 1), &quot;tabs are not allowed for indentation&quot;);
                }

                const indent = line.match(&#x2F;^ *&#x2F;)[0].length;

                if (indent % 2 !== 0) {
                    dataError(&quot;line &quot; + (index + 1), &quot;indentation must use multiples of two spaces&quot;);
                }

                tokens.push({
                    indent: indent,
                    text: line.slice(indent),
                    line: index + 1
                });
            });

            if (!tokens.length) {
                dataError(&quot;YAML&quot;, &quot;expected data&quot;);
            }

            const parsed = parseYamlBlock(tokens, 0, tokens[0].indent);

            if (parsed.index !== tokens.length) {
                dataError(&quot;line &quot; + tokens[parsed.index].line, &quot;unexpected content&quot;);
            }

            return parsed.value;
        }

        function parseYamlBlock(tokens, index, indent) {
            if (index &gt;= tokens.length) {
                dataError(&quot;YAML&quot;, &quot;expected block&quot;);
            }

            if (tokens[index].indent !== indent) {
                dataError(&quot;line &quot; + tokens[index].line, &quot;unexpected indentation&quot;);
            }

            if (tokens[index].text.startsWith(&quot;-&quot;)) {
                return parseYamlSequence(tokens, index, indent);
            }

            return parseYamlMap(tokens, index, indent);
        }

        function parseYamlMap(tokens, index, indent) {
            const value = {};

            while (index &lt; tokens.length &amp;&amp; tokens[index].indent === indent &amp;&amp; !tokens[index].text.startsWith(&quot;-&quot;)) {
                const token = tokens[index];
                const match = token.text.match(&#x2F;^([A-Za-z_][A-Za-z0-9_-]*):(.*)$&#x2F;);

                if (!match) {
                    dataError(&quot;line &quot; + token.line, &quot;expected key: value&quot;);
                }

                const key = match[1];
                const scalar = match[2].trim();

                if (Object.prototype.hasOwnProperty.call(value, key)) {
                    dataError(&quot;line &quot; + token.line, &quot;duplicate key &quot; + key);
                }

                index += 1;

                if (scalar) {
                    value[key] = parseYamlScalar(scalar, token.line);
                } else {
                    if (index &gt;= tokens.length || tokens[index].indent &lt;= indent) {
                        dataError(&quot;line &quot; + token.line, &quot;expected nested value for &quot; + key);
                    }

                    const child = parseYamlBlock(tokens, index, tokens[index].indent);
                    value[key] = child.value;
                    index = child.index;
                }
            }

            return { value: value, index: index };
        }

        function parseYamlSequence(tokens, index, indent) {
            const value = [];

            while (index &lt; tokens.length &amp;&amp; tokens[index].indent === indent &amp;&amp; tokens[index].text.startsWith(&quot;-&quot;)) {
                const token = tokens[index];
                const rest = token.text.slice(1).trim();

                index += 1;

                if (!rest) {
                    if (index &gt;= tokens.length || tokens[index].indent &lt;= indent) {
                        dataError(&quot;line &quot; + token.line, &quot;expected nested sequence item&quot;);
                    }

                    const child = parseYamlBlock(tokens, index, tokens[index].indent);
                    value.push(child.value);
                    index = child.index;
                    continue;
                }

                const pair = rest.match(&#x2F;^([A-Za-z_][A-Za-z0-9_-]*):(.*)$&#x2F;);

                if (pair) {
                    const item = {};
                    item[pair[1]] = parseYamlScalar(pair[2].trim(), token.line);

                    while (index &lt; tokens.length &amp;&amp; tokens[index].indent === indent + 2 &amp;&amp; !tokens[index].text.startsWith(&quot;-&quot;)) {
                        const propertyToken = tokens[index];
                        const property = propertyToken.text.match(&#x2F;^([A-Za-z_][A-Za-z0-9_-]*):(.*)$&#x2F;);

                        if (!property) {
                            dataError(&quot;line &quot; + propertyToken.line, &quot;expected key: value&quot;);
                        }

                        if (Object.prototype.hasOwnProperty.call(item, property[1])) {
                            dataError(&quot;line &quot; + propertyToken.line, &quot;duplicate key &quot; + property[1]);
                        }

                        const propertyValue = property[2].trim();
                        index += 1;

                        if (propertyValue) {
                            item[property[1]] = parseYamlScalar(propertyValue, propertyToken.line);
                        } else {
                            if (index &gt;= tokens.length || tokens[index].indent &lt;= propertyToken.indent) {
                                dataError(&quot;line &quot; + propertyToken.line, &quot;expected nested value for &quot; + property[1]);
                            }

                            const child = parseYamlBlock(tokens, index, tokens[index].indent);
                            item[property[1]] = child.value;
                            index = child.index;
                        }
                    }

                    value.push(item);
                    continue;
                }

                value.push(parseYamlScalar(rest, token.line));
            }

            return { value: value, index: index };
        }

        function parseYamlScalar(value, line) {
            if (!value) {
                dataError(&quot;line &quot; + line, &quot;expected scalar value&quot;);
            }

            if (value[0] === &quot;\&quot;&quot; || value[0] === &quot;&#x27;&quot;) {
                return parseQuotedYamlScalar(value, line);
            }

            if (value === &quot;true&quot;) {
                return true;
            }

            if (value === &quot;false&quot;) {
                return false;
            }

            if (value === &quot;null&quot; || value === &quot;None&quot;) {
                return null;
            }

            if (value === &quot;[]&quot;) {
                return [];
            }

            if (&#x2F;^-?(0|[1-9][0-9]*)(\.[0-9]+)?$&#x2F;.test(value)) {
                return Number(value);
            }

            if (&#x2F;[\[\]{}]&#x2F;.test(value)) {
                dataError(&quot;line &quot; + line, &quot;inline arrays and objects are not supported; use indented blocks&quot;);
            }

            return value;
        }

        function parseQuotedYamlScalar(value, line) {
            const quote = value[0];

            if (value[value.length - 1] !== quote) {
                dataError(&quot;line &quot; + line, &quot;unterminated quoted string&quot;);
            }

            if (quote === &quot;\&quot;&quot;) {
                try {
                    return JSON.parse(value);
                } catch (error) {
                    dataError(&quot;line &quot; + line, &quot;invalid quoted string&quot;);
                }
            }

            return value.slice(1, -1).replace(&#x2F;&#x27;&#x27;&#x2F;g, &quot;&#x27;&quot;);
        }

        function formatPythonLiteral(value, type) {
            if (type === &quot;str&quot;) {
                return JSON.stringify(value === undefined ? &quot;&quot; : String(value));
            }

            if (type === &quot;bool&quot;) {
                return value ? &quot;True&quot; : &quot;False&quot;;
            }

            if (type === &quot;NoneType&quot; || value === null) {
                return &quot;None&quot;;
            }

            if (value !== undefined) {
                return String(value);
            }

            return &quot;&quot;;
        }

        function normalizeDetails(value, path) {
            if (Array.isArray(value)) {
                return value.map(function (line, index) {
                    if (typeof line !== &quot;string&quot;) {
                        dataError(path + &quot;[&quot; + index + &quot;]&quot;, &quot;expected a string&quot;);
                    }

                    return line;
                });
            }

            if (value &amp;&amp; typeof value === &quot;object&quot;) {
                return Object.keys(value).map(function (key) {
                    return key + &quot;: &quot; + value[key];
                });
            }

            if (value !== undefined &amp;&amp; value !== null &amp;&amp; value !== &quot;&quot;) {
                return [String(value)];
            }

            return [];
        }

        function validateEntries(value, path) {
            if (Array.isArray(value)) {
                return value.map(function (item, index) {
                    const itemPath = path + &quot;[&quot; + index + &quot;]&quot;;

                    assertPlainObject(item, itemPath);
                    assertString(item.id, itemPath + &quot;.id&quot;);
                    assertId(item.id, itemPath + &quot;.id&quot;);

                    return {
                        id: item.id,
                        value: item,
                        path: itemPath
                    };
                });
            }

            assertPlainObject(value, path);

            return Object.keys(value).map(function (id) {
                assertId(id, path + &quot;.&quot; + id);
                assertPlainObject(value[id], path + &quot;.&quot; + id);

                return {
                    id: id,
                    value: value[id],
                    path: path + &quot;.&quot; + id
                };
            });
        }

        function validateReference(reference, path, label) {
            assertPlainObject(reference, path);
            assertAllowedKeys(reference, path, [&quot;object&quot;]);
            assertString(reference.object, path + &quot;.object&quot;);
            assertId(reference.object, path + &quot;.object&quot;);

            return {
                id: stableId(label, &quot;reference&quot;),
                label: String(label),
                target: reference.object,
                note: &quot;&quot;
            };
        }

        function validateBinding(binding, path, index) {
            assertPlainObject(binding, path);
            assertAllowedKeys(binding, path, [&quot;name&quot;, &quot;object&quot;]);
            assertString(binding.name, path + &quot;.name&quot;);

            return validateReference({ object: binding.object }, path, binding.name || index);
        }

        function validateFrame(entry) {
            const frame = entry.value;

            assertAllowedKeys(frame, entry.path, [&quot;id&quot;, &quot;name&quot;, &quot;bindings&quot;]);

            if (frame.id !== undefined &amp;&amp; frame.id !== entry.id) {
                dataError(entry.path + &quot;.id&quot;, &quot;must match key &quot; + entry.id);
            }

            if (frame.name !== undefined) {
                assertString(frame.name, entry.path + &quot;.name&quot;);
            }

            assertArray(frame.bindings, entry.path + &quot;.bindings&quot;);

            const bindingNames = new Set();

            return {
                id: entry.id,
                name: frame.name || entry.id,
                bindings: frame.bindings.map(function (binding, index) {
                    if (isPlainObject(binding) &amp;&amp; bindingNames.has(binding.name)) {
                        dataError(entry.path + &quot;.bindings[&quot; + index + &quot;].name&quot;, &quot;duplicate binding name &quot; + binding.name);
                    }

                    if (isPlainObject(binding)) {
                        bindingNames.add(binding.name);
                    }

                    return validateBinding(binding, entry.path + &quot;.bindings[&quot; + index + &quot;]&quot;, index);
                })
            };
        }

        function validateObjectValue(type, value, path) {
            if (type === &quot;int&quot;) {
                if (!Number.isInteger(value)) {
                    dataError(path, &quot;expected an integer for type int&quot;);
                }

                return;
            }

            if (type === &quot;float&quot;) {
                if (typeof value !== &quot;number&quot;) {
                    dataError(path, &quot;expected a number for type float&quot;);
                }

                return;
            }

            if (type === &quot;bool&quot;) {
                if (typeof value !== &quot;boolean&quot;) {
                    dataError(path, &quot;expected true or false for type bool&quot;);
                }

                return;
            }

            if (type === &quot;str&quot;) {
                if (typeof value !== &quot;string&quot;) {
                    dataError(path, &quot;expected a string for type str&quot;);
                }

                return;
            }

            if (type === &quot;NoneType&quot;) {
                if (value !== null) {
                    dataError(path, &quot;expected null for type NoneType&quot;);
                }
            }
        }

        function validateObject(entry) {
            const object = entry.value;
            const type = object.type || object.kind;
            let collectionItems = [];
            const supportedTypes = new Set([&quot;int&quot;, &quot;float&quot;, &quot;bool&quot;, &quot;NoneType&quot;, &quot;str&quot;, &quot;list&quot;, &quot;object&quot;]);

            assertAllowedKeys(object, entry.path, [&quot;id&quot;, &quot;type&quot;, &quot;kind&quot;, &quot;value&quot;, &quot;details&quot;]);

            if (object.id !== undefined &amp;&amp; object.id !== entry.id) {
                dataError(entry.path + &quot;.id&quot;, &quot;must match key &quot; + entry.id);
            }

            assertString(type, entry.path + &quot;.type&quot;);

            if (!supportedTypes.has(type)) {
                dataError(entry.path + &quot;.type&quot;, &quot;unsupported type &quot; + type);
            }

            if (object.type !== undefined &amp;&amp; object.kind !== undefined) {
                dataError(entry.path + &quot;.kind&quot;, &quot;use either type or kind, not both&quot;);
            }

            if (type === &quot;list&quot;) {
                assertArray(object.value, entry.path + &quot;.value&quot;);
                collectionItems = object.value.map(function (reference, index) {
                    return validateReference(reference, entry.path + &quot;.value[&quot; + index + &quot;]&quot;, index);
                });
            } else {
                if (object.value === undefined) {
                    dataError(entry.path + &quot;.value&quot;, &quot;expected a value&quot;);
                }

                validateObjectValue(type, object.value, entry.path + &quot;.value&quot;);
            }

            return {
                id: entry.id,
                type: type,
                value: object.value,
                details: normalizeDetails(object.details, entry.path + &quot;.details&quot;),
                collectionItems: collectionItems,
                missing: false
            };
        }

        function validateState(rawState) {
            assertPlainObject(rawState, &quot;root&quot;);
            assertAllowedKeys(rawState, &quot;root&quot;, [&quot;frames&quot;, &quot;objects&quot;]);

            if (rawState.frames === undefined) {
                dataError(&quot;root.frames&quot;, &quot;expected frames&quot;);
            }

            if (rawState.objects === undefined) {
                dataError(&quot;root.objects&quot;, &quot;expected objects&quot;);
            }

            const frameEntries = validateEntries(rawState.frames, &quot;frames&quot;);
            const objectEntries = validateEntries(rawState.objects, &quot;objects&quot;);
            const seenFrameIds = new Set();
            const seenIds = new Set();
            const frames = frameEntries.map(function (entry) {
                if (seenFrameIds.has(entry.id)) {
                    dataError(entry.path, &quot;duplicate frame id &quot; + entry.id);
                }

                seenFrameIds.add(entry.id);
                return validateFrame(entry);
            });
            const objects = objectEntries.map(function (entry) {
                if (seenIds.has(entry.id)) {
                    dataError(entry.path, &quot;duplicate object id &quot; + entry.id);
                }

                seenIds.add(entry.id);
                return validateObject(entry);
            });

            frames.forEach(function (frame) {
                frame.bindings.forEach(function (binding) {
                    if (!seenIds.has(binding.target)) {
                        dataError(&quot;frames.&quot; + frame.id + &quot;.bindings.&quot; + binding.label, &quot;unknown object &quot; + binding.target);
                    }
                });
            });

            objects.forEach(function (object) {
                object.collectionItems.forEach(function (item) {
                    if (!seenIds.has(item.target)) {
                        dataError(&quot;objects.&quot; + object.id + &quot;.value[&quot; + item.label + &quot;]&quot;, &quot;unknown object &quot; + item.target);
                    }
                });
            });

            return { frames: frames, objects: objects };
        }

        function textElement(tagName, className, text) {
            const element = document.createElement(tagName);
            element.className = className;
            element.textContent = text;
            return element;
        }

        function renderError(message) {
            const errorBox = document.createElement(&quot;div&quot;);
            errorBox.className = &quot;python-state-shortcode__error&quot;;
            errorBox.appendChild(textElement(&quot;div&quot;, &quot;python-state-shortcode__error-title&quot;, &quot;Python state data error&quot;));
            errorBox.appendChild(textElement(&quot;pre&quot;, &quot;python-state-shortcode__error-detail&quot;, message));
            nodes.appendChild(errorBox);

            fullscreenButton.disabled = true;
            stage.style.height = LAYOUT.minHeight + &quot;px&quot;;
        }

        function renderReference(reference, options) {
            const row = document.createElement(&quot;li&quot;);
            const isCollectionItem = options &amp;&amp; options.collectionItem;

            row.className = &quot;python-state-shortcode__reference&quot;
                + (isCollectionItem ? &quot; python-state-shortcode__reference--collection-item&quot; : &quot;&quot;);
            row.dataset.pythonStateTarget = reference.target;

            const label = textElement(&quot;span&quot;, &quot;python-state-shortcode__reference-label&quot;, reference.label);
            row.appendChild(label);

            const port = document.createElement(&quot;span&quot;);
            port.className = &quot;python-state-shortcode__reference-port&quot;;
            row.appendChild(port);

            return row;
        }

        function renderFrame(frame) {
            const box = document.createElement(&quot;section&quot;);
            box.className = &quot;python-state-shortcode__box python-state-shortcode__frame&quot;;
            box.dataset.pythonStateNodeId = frame.id;

            box.appendChild(textElement(&quot;div&quot;, &quot;python-state-shortcode__frame-title&quot;, frame.name));

            const list = document.createElement(&quot;ul&quot;);
            list.className = &quot;python-state-shortcode__bindings&quot;;

            frame.bindings.forEach(function (binding) {
                list.appendChild(renderReference(binding));
            });

            box.appendChild(list);
            return box;
        }

        function renderObjectContent(object, box) {
            const typeLine = textElement(&quot;div&quot;, &quot;python-state-shortcode__object-type&quot;, &quot;type: &quot; + object.type);
            box.appendChild(typeLine);

            const content = document.createElement(&quot;div&quot;);
            content.className = &quot;python-state-shortcode__object-content&quot;;

            if (object.collectionItems.length) {
                const collection = document.createElement(&quot;ul&quot;);
                collection.className = &quot;python-state-shortcode__collection&quot;;

                object.collectionItems.forEach(function (item) {
                    collection.appendChild(renderReference(item, { collectionItem: true }));
                });

                content.appendChild(collection);
            } else if (object.type === &quot;int&quot; || object.type === &quot;float&quot; || object.type === &quot;bool&quot; || object.type === &quot;NoneType&quot; || object.type === &quot;str&quot;) {
                content.appendChild(textElement(&quot;div&quot;, &quot;python-state-shortcode__literal&quot;, formatPythonLiteral(object.value, object.type)));
            } else if (object.details.length) {
                object.details.forEach(function (line) {
                    content.appendChild(textElement(&quot;div&quot;, &quot;python-state-shortcode__object-line&quot;, line));
                });
            } else if (object.value !== undefined) {
                content.appendChild(textElement(&quot;div&quot;, &quot;python-state-shortcode__literal&quot;, formatPythonLiteral(object.value, object.type)));
            }

            if (content.childNodes.length) {
                box.appendChild(content);
            }
        }

        function renderObject(object) {
            const box = document.createElement(&quot;section&quot;);
            const missingClass = object.missing ? &quot; python-state-shortcode__object--missing&quot; : &quot;&quot;;
            box.className = &quot;python-state-shortcode__box python-state-shortcode__object&quot; + missingClass;
            box.dataset.pythonStateNodeId = object.id;
            renderObjectContent(object, box);
            return box;
        }

        function rectInStage(element) {
            const stageRect = stage.getBoundingClientRect();
            const rect = element.getBoundingClientRect();

            return {
                left: rect.left - stageRect.left,
                top: rect.top - stageRect.top,
                right: rect.right - stageRect.left,
                bottom: rect.bottom - stageRect.top,
                width: rect.width,
                height: rect.height,
                centerX: rect.left - stageRect.left + rect.width &#x2F; 2,
                centerY: rect.top - stageRect.top + rect.height &#x2F; 2
            };
        }

        function getStageWidth() {
            const viewportWidth = viewport.clientWidth;
            return Math.max(viewportWidth, LAYOUT.minWidth);
        }

        function updateStageWidth(width) {
            stage.style.width = Math.max(getStageWidth(), width || 0) + &quot;px&quot;;
        }

        function resetStageWidthToViewport() {
            stage.style.width = getStageWidth() + &quot;px&quot;;
        }

        function setNodePosition(element, left, top) {
            element.style.left = left + &quot;px&quot;;
            element.style.top = top + &quot;px&quot;;
        }

        function captureNodeViewportPositions(nodeElements) {
            const positions = new Map();

            nodeElements.forEach(function (element) {
                const rect = element.getBoundingClientRect();
                positions.set(element, {
                    left: rect.left,
                    top: rect.top
                });
            });

            return positions;
        }

        function applyNodeViewportPositions(positions) {
            const stageRect = stage.getBoundingClientRect();

            positions.forEach(function (position, element) {
                setNodePosition(
                    element,
                    position.left - stageRect.left,
                    position.top - stageRect.top
                );
            });
        }

        function clampNodePosition(element) {
            const left = parseFloat(element.style.left) || 0;
            const top = parseFloat(element.style.top) || 0;
            const maxX = Math.max(0, stage.clientWidth - element.offsetWidth);
            const maxY = Math.max(0, stage.clientHeight - element.offsetHeight);

            setNodePosition(
                element,
                clamp(left, 0, maxX),
                clamp(top, 0, maxY)
            );
        }

        function fitNodeIntoCompactStage(element) {
            const left = parseFloat(element.style.left) || 0;
            const top = parseFloat(element.style.top) || 0;
            const maxX = Math.max(0, stage.clientWidth - element.offsetWidth);

            setNodePosition(
                element,
                clamp(left, 0, maxX),
                Math.max(0, top)
            );
        }

        function computeObjectLevels(state) {
            const levels = new Map();
            const objectsById = new Map();
            const referencesByObject = new Map();

            state.objects.forEach(function (object) {
                objectsById.set(object.id, object);
                referencesByObject.set(object.id, object.collectionItems.map(function (reference) {
                    return reference.target;
                }));
            });

            function reaches(startId, targetId, visited) {
                if (startId === targetId) {
                    return true;
                }

                if (visited.has(startId)) {
                    return false;
                }

                visited.add(startId);

                return (referencesByObject.get(startId) || []).some(function (nextId) {
                    return reaches(nextId, targetId, visited);
                });
            }

            state.frames.forEach(function (frame) {
                frame.bindings.forEach(function (binding) {
                    if (objectsById.has(binding.target) &amp;&amp; !levels.has(binding.target)) {
                        levels.set(binding.target, 1);
                    }
                });
            });

            state.objects.forEach(function (object) {
                if (!levels.has(object.id)) {
                    levels.set(object.id, 1);
                }
            });

            for (let pass = 0; pass &lt; state.objects.length; pass += 1) {
                let changed = false;

                state.objects.forEach(function (object) {
                    const sourceLevel = levels.get(object.id) || 1;

                    object.collectionItems.forEach(function (reference) {
                        if (reference.target === object.id || reaches(reference.target, object.id, new Set())) {
                            return;
                        }

                        const targetLevel = levels.get(reference.target) || 1;
                        const requiredLevel = sourceLevel + 1;

                        if (targetLevel &lt; requiredLevel) {
                            levels.set(reference.target, requiredLevel);
                            changed = true;
                        }
                    });
                });

                if (!changed) {
                    break;
                }
            }

            return levels;
        }

        function getObjectOriginalOrder(state) {
            const order = new Map();

            state.objects.forEach(function (object, index) {
                order.set(object.id, index);
            });

            return order;
        }

        function addReferenceScore(stats, target, candidateIds, score) {
            if (!candidateIds.has(target)) {
                return;
            }

            if (!stats.has(target)) {
                stats.set(target, {
                    count: 0,
                    sum: 0
                });
            }

            const targetStats = stats.get(target);
            targetStats.count += 1;
            targetStats.sum += score;
        }

        function getIncomingReferenceStats(state, objectLevels, objectColumnOrder, level, candidates) {
            const candidateIds = new Set(candidates.map(function (object) {
                return object.id;
            }));
            const stats = new Map();

            if (level === 1) {
                state.frames.forEach(function (frame, frameIndex) {
                    const referenceCount = Math.max(1, frame.bindings.length + 1);

                    frame.bindings.forEach(function (binding, bindingIndex) {
                        addReferenceScore(
                            stats,
                            binding.target,
                            candidateIds,
                            frameIndex + (bindingIndex + 1) &#x2F; referenceCount
                        );
                    });
                });
            }

            state.objects.forEach(function (object) {
                const sourceLevel = objectLevels.get(object.id) || 1;
                const sourceOrder = objectColumnOrder.get(object.id);

                if (sourceLevel &gt;= level || sourceOrder === undefined) {
                    return;
                }

                const referenceCount = Math.max(1, object.collectionItems.length + 1);

                object.collectionItems.forEach(function (reference, referenceIndex) {
                    addReferenceScore(
                        stats,
                        reference.target,
                        candidateIds,
                        sourceOrder + (referenceIndex + 1) &#x2F; referenceCount
                    );
                });
            });

            return stats;
        }

        function buildOrderedObjectColumns(state, objectElements, objectLevels) {
            const originalOrder = getObjectOriginalOrder(state);
            const objectColumnOrder = new Map();
            const objectsByLevel = new Map();
            const columns = [];
            let maxLevel = 0;

            state.objects.forEach(function (object) {
                if (!objectElements.has(object.id)) {
                    return;
                }

                const level = objectLevels.get(object.id) || 1;
                maxLevel = Math.max(maxLevel, level);

                if (!objectsByLevel.has(level)) {
                    objectsByLevel.set(level, []);
                }

                objectsByLevel.get(level).push(object);
            });

            for (let level = 1; level &lt;= maxLevel; level += 1) {
                const objects = objectsByLevel.get(level) || [];
                const incomingStats = getIncomingReferenceStats(
                    state,
                    objectLevels,
                    objectColumnOrder,
                    level,
                    objects
                );

                objects.sort(function (left, right) {
                    const leftStats = incomingStats.get(left.id);
                    const rightStats = incomingStats.get(right.id);
                    const leftScore = leftStats ? leftStats.sum &#x2F; leftStats.count : Number.POSITIVE_INFINITY;
                    const rightScore = rightStats ? rightStats.sum &#x2F; rightStats.count : Number.POSITIVE_INFINITY;

                    return leftScore - rightScore
                        || (originalOrder.get(left.id) || 0) - (originalOrder.get(right.id) || 0);
                });

                objects.forEach(function (object, index) {
                    objectColumnOrder.set(object.id, index);
                });

                if (objects.length) {
                    columns.push(objects.map(function (object) {
                        return objectElements.get(object.id);
                    }));
                }
            }

            return columns;
        }

        function buildObjectReferenceMap(state) {
            const referencesByObject = new Map();

            state.objects.forEach(function (object) {
                referencesByObject.set(object.id, object.collectionItems.map(function (reference) {
                    return reference.target;
                }));
            });

            return referencesByObject;
        }

        function objectReachesTarget(referencesByObject, startId, targetId, visited) {
            if (startId === targetId) {
                return true;
            }

            if (visited.has(startId)) {
                return false;
            }

            visited.add(startId);

            return (referencesByObject.get(startId) || []).some(function (nextId) {
                return objectReachesTarget(referencesByObject, nextId, targetId, visited);
            });
        }

        function collectReachableObjects(startIds, referencesByObject) {
            const reachable = new Set();
            const queue = startIds.slice();

            while (queue.length) {
                const id = queue.shift();

                if (reachable.has(id)) {
                    continue;
                }

                reachable.add(id);

                (referencesByObject.get(id) || []).forEach(function (targetId) {
                    queue.push(targetId);
                });
            }

            return reachable;
        }

        function inflateRect(rect, amount) {
            return {
                left: rect.left - amount,
                top: rect.top - amount,
                right: rect.right + amount,
                bottom: rect.bottom + amount,
                width: rect.width + amount * 2,
                height: rect.height + amount * 2,
                centerX: rect.centerX,
                centerY: rect.centerY
            };
        }

        function segmentIntersectsRect(start, end, rect) {
            const steps = 24;

            for (let step = 1; step &lt; steps; step += 1) {
                const ratio = step &#x2F; steps;
                const x = start.x + (end.x - start.x) * ratio;
                const y = start.y + (end.y - start.y) * ratio;

                if (x &gt;= rect.left &amp;&amp; x &lt;= rect.right &amp;&amp; y &gt;= rect.top &amp;&amp; y &lt;= rect.bottom) {
                    return true;
                }
            }

            return false;
        }

        function rectCenter(rect) {
            return {
                x: rect.centerX,
                y: rect.centerY
            };
        }

        function getTargetTopBelowObstacle(sourcePoint, targetPoint, targetElement, obstacleRect, pathRect) {
            const gap = LAYOUT.boxGap;
            const obstaclePathRect = pathRect || obstacleRect;
            const fallbackTop = obstaclePathRect.bottom + gap;
            const dx = targetPoint.x - sourcePoint.x;

            if (Math.abs(dx) &lt; 1) {
                return fallbackTop;
            }

            const segmentLeft = Math.min(sourcePoint.x, targetPoint.x);
            const segmentRight = Math.max(sourcePoint.x, targetPoint.x);
            const overlapLeft = Math.max(segmentLeft, obstaclePathRect.left);
            const overlapRight = Math.min(segmentRight, obstaclePathRect.right);

            if (overlapLeft &gt; overlapRight) {
                return fallbackTop;
            }

            const ratios = [overlapLeft, overlapRight].map(function (x) {
                return (x - sourcePoint.x) &#x2F; dx;
            }).filter(function (ratio) {
                return ratio &gt; 0 &amp;&amp; ratio &lt; 1;
            });

            if (!ratios.length) {
                return fallbackTop;
            }

            const closestObstacleRatio = Math.min.apply(null, ratios);
            const requiredCenterY = sourcePoint.y
                + (obstaclePathRect.bottom + gap - sourcePoint.y) &#x2F; closestObstacleRatio;

            return Math.max(fallbackTop, requiredCenterY - targetElement.offsetHeight &#x2F; 2);
        }

        function moveElementBelow(element, obstacleRect, sourcePoint, targetPoint, pathRect) {
            const top = parseFloat(element.style.top) || 0;
            const targetTop = getTargetTopBelowObstacle(sourcePoint, targetPoint, element, obstacleRect, pathRect);

            if (top &lt; targetTop) {
                setNodePosition(element, parseFloat(element.style.left) || 0, targetTop);
                return true;
            }

            return false;
        }

        function resolveColumnVerticalOverlaps(columns) {
            columns.forEach(function (column) {
                let bottom = LAYOUT.outerGap;

                column.forEach(function (element) {
                    const top = parseFloat(element.style.top) || 0;

                    if (top &lt; bottom) {
                        setNodePosition(element, parseFloat(element.style.left) || 0, bottom);
                    }

                    bottom = (parseFloat(element.style.top) || 0) + element.offsetHeight + LAYOUT.boxGap;
                });
            });
        }

        function avoidAlternativePathCollisions(state, frameElements, objectElements, objectColumns) {
            const referencesByObject = buildObjectReferenceMap(state);
            const objectIds = state.objects.map(function (object) {
                return object.id;
            });
            const edges = [];

            state.frames.forEach(function (frame, frameIndex) {
                const reachable = collectReachableObjects(frame.bindings.map(function (binding) {
                    return binding.target;
                }), referencesByObject);

                frame.bindings.forEach(function (binding) {
                    edges.push({
                        sourceElement: frameElements[frameIndex],
                        sourceId: &quot;&quot;,
                        sourceReachable: reachable,
                        targetId: binding.target
                    });
                });
            });

            state.objects.forEach(function (object) {
                const reachable = collectReachableObjects([object.id], referencesByObject);

                object.collectionItems.forEach(function (reference) {
                    edges.push({
                        sourceElement: objectElements.get(object.id),
                        sourceId: object.id,
                        sourceReachable: reachable,
                        targetId: reference.target
                    });
                });
            });

            for (let pass = 0; pass &lt; state.objects.length; pass += 1) {
                let changed = false;

                edges.forEach(function (edge) {
                    const targetElement = objectElements.get(edge.targetId);

                    if (!edge.sourceElement || !targetElement) {
                        return;
                    }

                    const sourceRect = rectInStage(edge.sourceElement);
                    const targetRect = rectInStage(targetElement);
                    const sourcePoint = rectCenter(sourceRect);
                    const targetPoint = rectCenter(targetRect);

                    objectIds.forEach(function (obstacleId) {
                        if (obstacleId === edge.sourceId || obstacleId === edge.targetId) {
                            return;
                        }

                        if (!edge.sourceReachable.has(obstacleId)) {
                            return;
                        }

                        if (!objectReachesTarget(referencesByObject, obstacleId, edge.targetId, new Set())) {
                            return;
                        }

                        const obstacleElement = objectElements.get(obstacleId);

                        if (!obstacleElement) {
                            return;
                        }

                        const obstacleRect = rectInStage(obstacleElement);
                        const pathRect = inflateRect(obstacleRect, LAYOUT.alternativePathClearance);

                        if (segmentIntersectsRect(sourcePoint, targetPoint, pathRect)) {
                            changed = moveElementBelow(targetElement, obstacleRect, sourcePoint, targetPoint, pathRect) || changed;
                        }
                    });
                });

                if (changed) {
                    resolveColumnVerticalOverlaps(objectColumns);
                } else {
                    break;
                }
            }
        }

        function positionDefault(state, frameElements, objectElements) {
            const objectLevels = computeObjectLevels(state);
            const columns = [frameElements].concat(
                buildOrderedObjectColumns(state, objectElements, objectLevels)
            );
            const columnWidths = columns.map(function (column) {
                if (!column || !column.length) {
                    return 0;
                }

                return column.reduce(function (width, element) {
                    return Math.max(width, element.offsetWidth);
                }, 0);
            });
            const columnHeights = columns.map(function (column) {
                if (!column || !column.length) {
                    return 0;
                }

                return column.reduce(function (height, element) {
                    return height + element.offsetHeight;
                }, 0) + LAYOUT.boxGap * (column.length - 1);
            });
            const objectColumns = columns.slice(1);
            const objectColumnWidths = columnWidths.slice(1);
            const regularColumnGap = Math.max(44, Math.round(getStageWidth() * 0.07));
            const objectGaps = objectColumns.slice(0, -1).map(function () {
                return regularColumnGap;
            });
            const objectContentWidth = objectColumnWidths.reduce(function (sum, width) {
                return sum + width;
            }, 0) + objectGaps.reduce(function (sum, gap) {
                return sum + gap;
            }, 0);
            const contentHeight = columnHeights.reduce(function (height, columnHeight) {
                return Math.max(height, columnHeight);
            }, 0);
            const frameContentWidth = columnWidths[0] || 0;
            const requiredUsableWidth = Math.max(
                frameContentWidth * 3,
                objectContentWidth * 1.5
            );
            const stageWidth = Math.max(
                getStageWidth(),
                Math.ceil(requiredUsableWidth + LAYOUT.outerGap * 2)
            );
            const stageHeight = Math.max(LAYOUT.minHeight, contentHeight + LAYOUT.outerGap * 2);
            const usableWidth = stageWidth - LAYOUT.outerGap * 2;
            const frameAreaLeft = LAYOUT.outerGap;
            const frameAreaWidth = usableWidth &#x2F; 3;
            const objectAreaLeft = frameAreaLeft + frameAreaWidth;
            const objectAreaWidth = usableWidth * 2 &#x2F; 3;
            const yStart = Math.max(LAYOUT.outerGap, Math.round((stageHeight - contentHeight) &#x2F; 2));

            updateStageWidth(stageWidth);
            stage.style.height = stageHeight + &quot;px&quot;;

            if (columns[0] &amp;&amp; columns[0].length) {
                let frameY = yStart;
                const frameX = Math.round(
                    frameAreaLeft + Math.max(0, (frameAreaWidth - frameContentWidth) &#x2F; 2)
                );

                columns[0].forEach(function (element) {
                    setNodePosition(element, frameX, frameY);
                    frameY += element.offsetHeight + LAYOUT.boxGap;
                });
            }

            let objectX = Math.round(
                objectAreaLeft + Math.max(0, (objectAreaWidth - objectContentWidth) &#x2F; 2)
            );

            objectColumns.forEach(function (column, index) {
                if (!column || !column.length) {
                    return;
                }

                let y = yStart;
                column.forEach(function (element) {
                    setNodePosition(element, objectX, y);
                    y += element.offsetHeight + LAYOUT.boxGap;
                });

                objectX += objectColumnWidths[index] + (objectGaps[index] || 0);
            });

            avoidAlternativePathCollisions(state, frameElements, objectElements, objectColumns);
        }

        function updateStageHeight(nodeElements) {
            let bottom = isFullscreen()
                ? Math.max(LAYOUT.minHeight, viewport.clientHeight)
                : LAYOUT.minHeight;

            nodeElements.forEach(function (element) {
                const rect = rectInStage(element);
                bottom = Math.max(bottom, rect.bottom + LAYOUT.outerGap);
            });

            stage.style.height = bottom + &quot;px&quot;;
            arrowsSvg.setAttribute(&quot;viewBox&quot;, &quot;0 0 &quot; + stage.offsetWidth + &quot; &quot; + bottom);
        }

        function updateStageWidthToFit(nodeElements) {
            let right = LAYOUT.minWidth;

            nodeElements.forEach(function (element) {
                const rect = rectInStage(element);
                right = Math.max(right, rect.right + LAYOUT.outerGap);
            });

            updateStageWidth(right);
        }

        function sideNormal(side) {
            if (side === &quot;left&quot;) {
                return { x: -1, y: 0 };
            }

            if (side === &quot;right&quot;) {
                return { x: 1, y: 0 };
            }

            if (side === &quot;top&quot;) {
                return { x: 0, y: -1 };
            }

            return { x: 0, y: 1 };
        }

        function chooseConnectionSide(fromRect, toRect) {
            if (toRect.right &lt; fromRect.left) {
                return &quot;left&quot;;
            }

            if (toRect.left &gt; fromRect.right) {
                return &quot;right&quot;;
            }

            if (toRect.bottom &lt; fromRect.top) {
                return &quot;top&quot;;
            }

            if (toRect.top &gt; fromRect.bottom) {
                return &quot;bottom&quot;;
            }

            const dx = toRect.centerX - fromRect.centerX;
            const dy = toRect.centerY - fromRect.centerY;

            if (Math.abs(dx) &gt;= Math.abs(dy)) {
                return dx &gt;= 0 ? &quot;right&quot; : &quot;left&quot;;
            }

            return dy &gt;= 0 ? &quot;bottom&quot; : &quot;top&quot;;
        }

        function chooseReferenceSide(sourceRect, targetRect) {
            return targetRect.centerX &lt; sourceRect.centerX ? &quot;left&quot; : &quot;right&quot;;
        }

        function chooseReferenceSourceSide(reference, sourceRect, targetRect) {
            if (reference.classList.contains(&quot;python-state-shortcode__reference--collection-item&quot;)) {
                return chooseConnectionSide(rectInStage(reference), targetRect);
            }

            return chooseReferenceSide(sourceRect, targetRect);
        }

        function borderPointForSide(rect, side, anchor) {
            const horizontalPadding = Math.min(LAYOUT.cornerGap, rect.width &#x2F; 2);
            const verticalPadding = Math.min(LAYOUT.cornerGap, rect.height &#x2F; 2);

            if (side === &quot;left&quot; || side === &quot;right&quot;) {
                return {
                    x: side === &quot;left&quot; ? rect.left : rect.right,
                    y: clamp(anchor.y, rect.top + verticalPadding, rect.bottom - verticalPadding),
                    side: side
                };
            }

            return {
                x: clamp(anchor.x, rect.left + horizontalPadding, rect.right - horizontalPadding),
                y: side === &quot;top&quot; ? rect.top : rect.bottom,
                side: side
            };
        }

        function outsidePoint(point, amount) {
            const normal = sideNormal(point.side);

            return {
                x: point.x + normal.x * amount,
                y: point.y + normal.y * amount,
                side: point.side
            };
        }

        function pointFromPort(portRect, side) {
            if (side === &quot;bottom&quot;) {
                return {
                    x: portRect.centerX,
                    y: portRect.centerY,
                    side: side
                };
            }

            if (side === &quot;top&quot;) {
                return {
                    x: portRect.centerX,
                    y: portRect.centerY,
                    side: side
                };
            }

            if (side === &quot;left&quot;) {
                return {
                    x: portRect.left,
                    y: portRect.centerY,
                    side: side
                };
            }

            return {
                x: portRect.right,
                y: portRect.centerY,
                side: side
            };
        }

        function createArrowGeometry(start, end, arrowheadLength) {
            const dx = end.x - start.x;
            const dy = end.y - start.y;
            const distance = Math.max(70, Math.sqrt(dx * dx + dy * dy));
            const bend = Math.min(90, distance * 0.32);
            const startNormal = sideNormal(start.side);
            const endNormal = sideNormal(end.side);
            const lineEnd = {
                x: end.x + endNormal.x * arrowheadLength,
                y: end.y + endNormal.y * arrowheadLength,
                side: end.side
            };
            const control1 = {
                x: start.x + startNormal.x * bend,
                y: start.y + startNormal.y * bend
            };
            const control2 = {
                x: end.x + endNormal.x * bend,
                y: end.y + endNormal.y * bend
            };

            return {
                path: [
                    &quot;M&quot;, start.x, start.y,
                    &quot;C&quot;, control1.x, control1.y, control2.x, control2.y, lineEnd.x, lineEnd.y
                ].join(&quot; &quot;),
                control2: control2
            };
        }

        function createArrowheadPoints(end, controlPoint, length, width) {
            const dx = end.x - controlPoint.x;
            const dy = end.y - controlPoint.y;
            const distance = Math.sqrt(dx * dx + dy * dy) || 1;
            const ux = dx &#x2F; distance;
            const uy = dy &#x2F; distance;
            const nx = -uy;
            const ny = ux;
            const baseX = end.x - ux * length;
            const baseY = end.y - uy * length;

            return [
                [end.x, end.y],
                [baseX + nx * width, baseY + ny * width],
                [baseX - nx * width, baseY - ny * width]
            ].map(function (point) {
                return point[0] + &quot;,&quot; + point[1];
            }).join(&quot; &quot;);
        }

        function svgElement(tagName, className) {
            const element = document.createElementNS(&quot;http:&#x2F;&#x2F;www.w3.org&#x2F;2000&#x2F;svg&quot;, tagName);
            element.classList.add(className);
            return element;
        }

        function createEdgePath(className, pathData) {
            const path = svgElement(&quot;path&quot;, className);

            path.setAttribute(&quot;d&quot;, pathData);

            return path;
        }

        function createEdgeArrowhead(className, points) {
            const arrowhead = svgElement(&quot;polygon&quot;, className);

            arrowhead.setAttribute(&quot;points&quot;, points);

            return arrowhead;
        }

        function createEdgeCircle(className, point, radius) {
            const circle = svgElement(&quot;circle&quot;, className);

            circle.setAttribute(&quot;cx&quot;, point.x);
            circle.setAttribute(&quot;cy&quot;, point.y);
            circle.setAttribute(&quot;r&quot;, radius);

            return circle;
        }

        function setEdgeActive(edgeId, active) {
            const edge = arrowsSvg.querySelector(&quot;[data-python-state-edge-id=\&quot;&quot; + edgeId + &quot;\&quot;]&quot;);

            if (edge) {
                edge.classList.toggle(&quot;python-state-shortcode__edge--active&quot;, active);
            }
        }

        function drawArrows(objectElements) {
            arrowsSvg.querySelectorAll(&quot;.python-state-shortcode__edge&quot;).forEach(function (edge) {
                edge.remove();
            });

            root.querySelectorAll(&quot;[data-python-state-target]&quot;).forEach(function (reference, index) {
                const target = reference.dataset.pythonStateTarget;
                const object = objectElements.get(target);

                if (!target || !object) {
                    return;
                }

                const port = reference.querySelector(&quot;.python-state-shortcode__reference-port&quot;);
                const sourceBox = reference.closest(&quot;.python-state-shortcode__box&quot;);
                const sourceRect = rectInStage(sourceBox);
                const objectRect = rectInStage(object);
                const sourceSide = chooseReferenceSourceSide(reference, sourceRect, objectRect);

                reference.dataset.pythonStateReferenceSide = sourceSide;

                const portRect = rectInStage(port || reference);
                const portAnchor = { x: portRect.centerX, y: portRect.centerY };
                const targetSide = chooseConnectionSide(objectRect, portRect);
                const targetBorder = borderPointForSide(objectRect, targetSide, portAnchor);
                const visualPortPoint = rectCenter(portRect);
                const linePortPoint = pointFromPort(portRect, sourceSide);
                const start = outsidePoint(linePortPoint, LAYOUT.arrowGap);
                const end = outsidePoint(targetBorder, LAYOUT.arrowGap);
                const arrowheadLength = 8;
                const arrowGeometry = createArrowGeometry(start, end, arrowheadLength);
                const pathData = arrowGeometry.path;
                const arrowheadPoints = createArrowheadPoints(end, arrowGeometry.control2, arrowheadLength, 4);
                const edge = svgElement(&quot;g&quot;, &quot;python-state-shortcode__edge&quot;);
                const edgeId = &quot;edge-&quot; + index;

                edge.setAttribute(&quot;aria-hidden&quot;, &quot;true&quot;);
                edge.dataset.pythonStateEdgeId = edgeId;
                reference.dataset.pythonStateEdgeId = edgeId;
                reference.onmouseenter = function () {
                    setEdgeActive(edgeId, true);
                };
                reference.onmouseleave = function () {
                    setEdgeActive(edgeId, false);
                };
                edge.addEventListener(&quot;mouseenter&quot;, function () {
                    setEdgeActive(edgeId, true);
                });
                edge.addEventListener(&quot;mouseleave&quot;, function () {
                    setEdgeActive(edgeId, false);
                });
                edge.appendChild(createEdgePath(&quot;python-state-shortcode__edge-hit&quot;, pathData));
                edge.appendChild(createEdgePath(&quot;python-state-shortcode__edge-halo&quot;, pathData));
                edge.appendChild(createEdgePath(&quot;python-state-shortcode__edge-line&quot;, pathData));
                edge.appendChild(createEdgeArrowhead(&quot;python-state-shortcode__edge-arrowhead-halo&quot;, arrowheadPoints));
                edge.appendChild(createEdgeArrowhead(&quot;python-state-shortcode__edge-arrowhead&quot;, arrowheadPoints));
                edge.appendChild(createEdgeCircle(&quot;python-state-shortcode__edge-port-halo&quot;, visualPortPoint, 5.2));
                edge.appendChild(createEdgeCircle(&quot;python-state-shortcode__edge-port&quot;, visualPortPoint, 4.2));
                arrowsSvg.appendChild(edge);
            });
        }

        function isFullscreen() {
            return root.classList.contains(&quot;python-state-shortcode--fullscreen&quot;);
        }

        function isTransparentColor(value) {
            return !value || value === &quot;transparent&quot; || value === &quot;rgba(0, 0, 0, 0)&quot;;
        }

        function getPageBackgroundColor() {
            const candidates = [document.body, document.documentElement, root.parentElement].filter(Boolean);

            for (let index = 0; index &lt; candidates.length; index += 1) {
                const background = window.getComputedStyle(candidates[index]).backgroundColor;

                if (!isTransparentColor(background)) {
                    return background;
                }
            }

            return &quot;transparent&quot;;
        }

        function updateFullpageMetrics() {
            const nav = document.querySelector(&quot;nav&quot;);
            const navRect = nav ? nav.getBoundingClientRect() : null;
            const top = navRect ? Math.max(0, Math.round(navRect.bottom)) : 0;

            root.style.setProperty(&quot;--python-state-fullpage-top&quot;, top + &quot;px&quot;);
            root.style.setProperty(&quot;--python-state-fullpage-bg&quot;, getPageBackgroundColor());
        }

        function setPageScrollLocked(locked) {
            if (locked) {
                savedDocumentOverflow = document.documentElement.style.overflow;
                savedBodyOverflow = document.body.style.overflow;
                document.documentElement.style.overflow = &quot;hidden&quot;;
                document.body.style.overflow = &quot;hidden&quot;;
                return;
            }

            document.documentElement.style.overflow = savedDocumentOverflow;
            document.body.style.overflow = savedBodyOverflow;
        }

        function refreshGeometry(allNodeElements, objectElements) {
            updateStageHeight(allNodeElements);
            drawArrows(objectElements);
        }

        function setFullscreenMode(enabled, allNodeElements, objectElements) {
            if (enabled === isFullscreen()) {
                return;
            }

            const preTogglePositions = captureNodeViewportPositions(allNodeElements);

            if (enabled) {
                updateFullpageMetrics();
            }

            root.classList.toggle(&quot;python-state-shortcode--fullscreen&quot;, enabled);
            fullscreenButton.setAttribute(&quot;aria-pressed&quot;, enabled ? &quot;true&quot; : &quot;false&quot;);
            fullscreenButton.textContent = enabled ? &quot;Exit&quot; : &quot;Full page&quot;;
            setPageScrollLocked(enabled);

            window.requestAnimationFrame(function () {
                if (enabled) {
                    applyNodeViewportPositions(preTogglePositions);
                    updateStageWidthToFit(allNodeElements);
                } else {
                    resetStageWidthToViewport();
                    applyNodeViewportPositions(preTogglePositions);
                    allNodeElements.forEach(fitNodeIntoCompactStage);
                }

                refreshGeometry(allNodeElements, objectElements);
            });
        }

        function makeDraggable(element, objectElements) {
            let offsetX = 0;
            let offsetY = 0;
            let dragging = false;

            element.addEventListener(&quot;pointerdown&quot;, function (event) {
                dragging = true;
                element.classList.add(&quot;python-state-shortcode__box--dragging&quot;);
                element.setPointerCapture(event.pointerId);

                const rect = element.getBoundingClientRect();
                offsetX = event.clientX - rect.left;
                offsetY = event.clientY - rect.top;
            });

            element.addEventListener(&quot;pointermove&quot;, function (event) {
                if (!dragging) {
                    return;
                }

                const stageRect = stage.getBoundingClientRect();
                const maxX = Math.max(0, stage.clientWidth - element.offsetWidth);
                const maxY = Math.max(0, stage.clientHeight - element.offsetHeight);
                const x = clamp(event.clientX - stageRect.left - offsetX, 0, maxX);
                const y = clamp(event.clientY - stageRect.top - offsetY, 0, maxY);

                setNodePosition(element, x, y);
                drawArrows(objectElements);
            });

            function stopDragging() {
                dragging = false;
                element.classList.remove(&quot;python-state-shortcode__box--dragging&quot;);
            }

            element.addEventListener(&quot;pointerup&quot;, stopDragging);
            element.addEventListener(&quot;pointercancel&quot;, stopDragging);
        }

        function render(state) {
            const frameElements = state.frames.map(function (frame) {
                const frameElement = renderFrame(frame);
                nodes.appendChild(frameElement);
                return frameElement;
            });
            const objectElements = new Map();

            state.objects.forEach(function (object) {
                const objectElement = renderObject(object);
                objectElements.set(object.id, objectElement);
                nodes.appendChild(objectElement);
            });

            const allNodeElements = frameElements.concat(Array.from(objectElements.values()));

            positionDefault(state, frameElements, objectElements);
            updateStageHeight(allNodeElements);
            drawArrows(objectElements);

            allNodeElements.forEach(function (element) {
                makeDraggable(element, objectElements);
            });

            fullscreenButton.addEventListener(&quot;click&quot;, function () {
                setFullscreenMode(!isFullscreen(), allNodeElements, objectElements);
            });

            document.addEventListener(&quot;keydown&quot;, function (event) {
                if (event.key === &quot;Escape&quot; &amp;&amp; isFullscreen()) {
                    setFullscreenMode(false, allNodeElements, objectElements);
                }
            });

            window.addEventListener(&quot;resize&quot;, function () {
                if (isFullscreen()) {
                    updateFullpageMetrics();
                    updateStageWidthToFit(allNodeElements);
                } else {
                    resetStageWidthToViewport();
                }

                allNodeElements.forEach(clampNodePosition);
                refreshGeometry(allNodeElements, objectElements);
            });
        }

        const parsed = parseState();

        if (parsed.error) {
            renderError(parsed.error);
        } else {
            render(parsed.state);
        }
    }());
    &lt;&#x2F;script&gt;
&lt;&#x2F;div&gt;
&lt;p&gt;То же самое относится к любым операциям со строками: объект типа &lt;code&gt;str&lt;&#x2F;code&gt; не может быть изменён.&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo z-code&quot;&gt;&lt;code data-lang=&quot;python&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;a&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; =&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt; &amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;Hello&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;b&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; =&lt;&#x2F;span&gt;&lt;span&gt; a&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; +&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt; &amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; World!&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-comment&quot;&gt;  #&lt;&#x2F;span&gt;&lt;span class=&quot;z-comment&quot;&gt; объект, на который ссылается `a` остался неизменным&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;



&lt;style&gt;
.python-state-shortcode {
    position: relative;
    border: 1px solid white;
    border-radius: 2px;
    overflow: hidden;
    padding: 1em;
    margin: 2em 0;
}

.python-state-shortcode__header {
    position: absolute;
    top: 0.75rem;
    right: 0.75rem;
    z-index: 4;
    pointer-events: none;
}

.python-state-shortcode__fullscreen-button {
    border: 1px solid white;
    border-radius: 2px;
    background: transparent;
    color: inherit;
    padding: 0.3em 0.55em;
    font: inherit;
    font-size: 0.78rem;
    line-height: 1;
    cursor: pointer;
    pointer-events: auto;
}

.python-state-shortcode__fullscreen-button:hover,
.python-state-shortcode__fullscreen-button:focus-visible {
    background: rgba(255, 255, 255, 0.12);
}

.python-state-shortcode__viewport {
    overflow-x: auto;
}

.python-state-shortcode__stage {
    position: relative;
    min-width: 560px;
    min-height: 240px;
    overflow: hidden;
}

.python-state-shortcode__arrows {
    position: absolute;
    inset: 0;
    width: 100%;
    height: 100%;
    pointer-events: auto;
    z-index: 1;
}

.python-state-shortcode__nodes {
    position: absolute;
    inset: 0;
    z-index: 2;
}

.python-state-shortcode__box {
    position: absolute;
    box-sizing: border-box;
    width: max-content;
    min-width: 4.5rem;
    max-width: 18rem;
    border: 1px solid white;
    border-radius: 2px;
    background: var(--bg, transparent);
    padding: 0.55em 0.7em;
    cursor: grab;
    user-select: none;
    touch-action: none;
}

.python-state-shortcode__box:active {
    cursor: grabbing;
}

.python-state-shortcode__box--dragging {
    z-index: 3;
}

.python-state-shortcode__frame-title {
    font-weight: 600;
    line-height: 1.2;
    margin-bottom: 0.45em;
    white-space: nowrap;
}

.python-state-shortcode__object-type {
    line-height: 1.2;
    margin-bottom: 0.45em;
    white-space: nowrap;
}

.python-state-shortcode__bindings,
.python-state-shortcode__object-content {
    display: grid;
    gap: 0.3em;
    margin: 0;
    padding: 0;
    list-style: none;
}

.python-state-shortcode__collection {
    display: flex;
    flex-flow: row nowrap;
    gap: 0;
    margin: 0;
    padding: 0.15em 0 0.25em 0;
    list-style: none;
}

.python-state-shortcode__reference {
    position: relative;
    display: block;
    box-sizing: border-box;
    width: 100%;
    padding-right: 1.05em;
    min-height: 1.35em;
    line-height: 1.25;
}

.python-state-shortcode__reference-label,
.python-state-shortcode__literal,
.python-state-shortcode__object-line {
    overflow-wrap: anywhere;
    font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, &quot;Liberation Mono&quot;, monospace;
    font-size: 0.9rem;
}

.python-state-shortcode__reference-label {
    display: block;
    white-space: nowrap;
}

.python-state-shortcode__reference-port {
    position: absolute;
    top: 50%;
    right: 0.2em;
    width: 0.48em;
    height: 0.48em;
    transform: translateY(-50%);
    opacity: 0;
    pointer-events: none;
}

.python-state-shortcode__reference[data-python-state-reference-side=&quot;left&quot;] {
    padding-right: 0;
    padding-left: 1.05em;
}

.python-state-shortcode__reference[data-python-state-reference-side=&quot;left&quot;] .python-state-shortcode__reference-port {
    right: auto;
    left: 0.2em;
}

.python-state-shortcode__reference--collection-item {
    display: flex;
    align-items: center;
    justify-content: center;
    width: 2.15em;
    height: 2.15em;
    min-height: 0;
    padding: 0;
    border: 1px solid white;
    box-sizing: border-box;
}

.python-state-shortcode__reference--collection-item + .python-state-shortcode__reference--collection-item {
    border-left: 0;
}

.python-state-shortcode__reference--collection-item .python-state-shortcode__reference-label {
    display: none;
}

.python-state-shortcode__reference--collection-item .python-state-shortcode__reference-port,
.python-state-shortcode__reference--collection-item[data-python-state-reference-side=&quot;left&quot;] .python-state-shortcode__reference-port {
    top: 50%;
    right: auto;
    bottom: auto;
    left: 50%;
    transform: translate(-50%, -50%);
}

.python-state-shortcode__literal {
    line-height: 1.35;
}

.python-state-shortcode__object--missing {
    border-style: dashed;
}

.python-state-shortcode__edge {
    pointer-events: visibleStroke;
    color: #60a5fa;
}

.python-state-shortcode__edge-hit {
    fill: none;
    stroke: transparent;
    stroke-width: 14;
    stroke-linecap: round;
    stroke-linejoin: round;
    pointer-events: stroke;
}

.python-state-shortcode__edge-halo {
    fill: none;
    stroke: rgba(255, 255, 255, 0);
    stroke-width: 4;
    stroke-linecap: round;
    stroke-linejoin: round;
    transition: stroke 140ms ease;
}

.python-state-shortcode__edge-line {
    fill: none;
    stroke: currentColor;
    stroke-width: 1.5;
    stroke-linecap: round;
    stroke-linejoin: round;
    transition: stroke 140ms ease;
}

.python-state-shortcode__edge-port-halo {
    fill: none;
    stroke: rgba(255, 255, 255, 0);
    stroke-width: 5;
    transition: stroke 140ms ease;
}

.python-state-shortcode__edge-arrowhead-halo {
    fill: none;
    stroke: rgba(255, 255, 255, 0);
    stroke-width: 3;
    stroke-linejoin: round;
    transition: stroke 140ms ease;
}

.python-state-shortcode__edge-arrowhead {
    fill: currentColor;
    transition: fill 140ms ease;
}

.python-state-shortcode__edge-port {
    fill: currentColor;
    stroke: currentColor;
    stroke-width: 1;
    transition: fill 140ms ease, stroke 140ms ease, r 140ms ease;
}

.python-state-shortcode__edge:hover,
.python-state-shortcode__edge:focus-visible,
.python-state-shortcode__edge--active {
    color: #93c5fd;
    outline: none;
}

.python-state-shortcode__edge:hover .python-state-shortcode__edge-halo,
.python-state-shortcode__edge:focus-visible .python-state-shortcode__edge-halo,
.python-state-shortcode__edge--active .python-state-shortcode__edge-halo,
.python-state-shortcode__edge:hover .python-state-shortcode__edge-port-halo,
.python-state-shortcode__edge:focus-visible .python-state-shortcode__edge-port-halo,
.python-state-shortcode__edge--active .python-state-shortcode__edge-port-halo {
    stroke: rgba(255, 255, 255, 0.92);
}

.python-state-shortcode__edge:hover .python-state-shortcode__edge-arrowhead-halo,
.python-state-shortcode__edge:focus-visible .python-state-shortcode__edge-arrowhead-halo,
.python-state-shortcode__edge--active .python-state-shortcode__edge-arrowhead-halo {
    stroke: rgba(255, 255, 255, 0.92);
}

.python-state-shortcode__error {
    border: 1px solid white;
    border-radius: 2px;
    padding: 0.75em 0.9em;
    max-width: 42rem;
    line-height: 1.35;
}

.python-state-shortcode__error-title {
    margin-bottom: 0.45em;
    font-weight: 600;
}

.python-state-shortcode__error-detail {
    margin: 0;
    white-space: pre-wrap;
    overflow-wrap: anywhere;
    font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, &quot;Liberation Mono&quot;, monospace;
    font-size: 0.9rem;
}

.python-state-shortcode--fullscreen {
    position: fixed;
    top: var(--python-state-fullpage-top, 0);
    right: 0;
    bottom: 0;
    left: 0;
    z-index: 1000;
    display: flex;
    flex-direction: column;
    box-sizing: border-box;
    width: 100vw;
    height: calc(100vh - var(--python-state-fullpage-top, 0px));
    height: calc(100dvh - var(--python-state-fullpage-top, 0px));
    margin: 0;
    border: 0;
    border-radius: 0;
    padding: 0;
    background: var(--python-state-fullpage-bg, transparent);
    overflow: hidden;
}

.python-state-shortcode--fullscreen .python-state-shortcode__header {
    margin: 0;
}

.python-state-shortcode--fullscreen .python-state-shortcode__viewport {
    flex: 1 1 auto;
    min-height: 0;
    overflow: auto;
}

.python-state-shortcode--fullscreen .python-state-shortcode__stage {
    min-height: 100%;
}
&lt;&#x2F;style&gt;

&lt;div class=&quot;python-state-shortcode&quot; data-python-state-shortcode&gt;
    &lt;div class=&quot;python-state-shortcode__header&quot;&gt;
        &lt;button class=&quot;python-state-shortcode__fullscreen-button&quot; type=&quot;button&quot; data-python-state-fullscreen-button aria-pressed=&quot;false&quot;&gt;Full page&lt;&#x2F;button&gt;
    &lt;&#x2F;div&gt;

    &lt;div class=&quot;python-state-shortcode__viewport&quot;&gt;
        &lt;div class=&quot;python-state-shortcode__stage&quot; data-python-state-stage aria-label=&quot;Python state&quot;&gt;
            &lt;svg class=&quot;python-state-shortcode__arrows&quot; data-python-state-arrows aria-hidden=&quot;true&quot;&gt;&lt;&#x2F;svg&gt;
            &lt;div class=&quot;python-state-shortcode__nodes&quot; data-python-state-nodes&gt;&lt;&#x2F;div&gt;
        &lt;&#x2F;div&gt;
    &lt;&#x2F;div&gt;

    &lt;script type=&quot;text&#x2F;plain&quot; class=&quot;python-state-shortcode__data&quot; data-python-state-data&gt;
frames:
  global:
    name: Global frame
    bindings:
      - name: a
        object: a
      - name: b
        object: b
objects:
  a:
    type: str
    value: Hello
  b:
    type: str
    value: Hello World!
    &lt;&#x2F;script&gt;

    &lt;script&gt;
    (function () {
        const currentScript = document.currentScript;
        const root = currentScript.closest(&quot;[data-python-state-shortcode]&quot;);
        const viewport = root.querySelector(&quot;.python-state-shortcode__viewport&quot;);
        const stage = root.querySelector(&quot;[data-python-state-stage]&quot;);
        const nodes = root.querySelector(&quot;[data-python-state-nodes]&quot;);
        const arrowsSvg = root.querySelector(&quot;[data-python-state-arrows]&quot;);
        const dataNode = root.querySelector(&quot;[data-python-state-data]&quot;);
        const fullscreenButton = root.querySelector(&quot;[data-python-state-fullscreen-button]&quot;);
        let savedDocumentOverflow = &quot;&quot;;
        let savedBodyOverflow = &quot;&quot;;

        const LAYOUT = {
            minWidth: 560,
            minHeight: 240,
            outerGap: 18,
            boxGap: 14,
            arrowGap: 7,
            cornerGap: 10,
            alternativePathClearance: 18
        };

        function stableId(value, fallback) {
            const raw = String(value || fallback || &quot;item&quot;).trim().toLowerCase();
            const slug = raw.replace(&#x2F;[^a-z0-9_-]+&#x2F;g, &quot;-&quot;).replace(&#x2F;^-+|-+$&#x2F;g, &quot;&quot;);
            return slug || String(fallback || &quot;item&quot;);
        }

        function clamp(value, min, max) {
            return Math.max(min, Math.min(max, value));
        }

        function parseState() {
            const raw = dataNode.textContent.trim();

            if (!raw) {
                return {
                    error: &quot;python_state shortcode expects a JSON or YAML body.&quot;
                };
            }

            try {
                const parsed = raw[0] === &quot;{&quot; || raw[0] === &quot;[&quot;
                    ? JSON.parse(raw)
                    : parseYamlSubset(raw);

                return {
                    state: validateState(parsed)
                };
            } catch (error) {
                return {
                    error: &quot;Invalid python_state shortcode data:\n&quot; + error.message
                };
            }
        }

        function dataError(path, message) {
            throw new Error(path + &quot;: &quot; + message);
        }

        function isPlainObject(value) {
            return Boolean(value) &amp;&amp; typeof value === &quot;object&quot; &amp;&amp; !Array.isArray(value);
        }

        function assertPlainObject(value, path) {
            if (!isPlainObject(value)) {
                dataError(path, &quot;expected an object&quot;);
            }
        }

        function assertArray(value, path) {
            if (!Array.isArray(value)) {
                dataError(path, &quot;expected an array&quot;);
            }
        }

        function assertAllowedKeys(value, path, allowedKeys) {
            const allowed = new Set(allowedKeys);

            Object.keys(value).forEach(function (key) {
                if (!allowed.has(key)) {
                    dataError(path + &quot;.&quot; + key, &quot;unknown field&quot;);
                }
            });
        }

        function assertId(value, path) {
            if (typeof value !== &quot;string&quot; || !&#x2F;^[A-Za-z_][A-Za-z0-9_-]*$&#x2F;.test(value)) {
                dataError(path, &quot;expected an object id like items or value_1&quot;);
            }
        }

        function assertString(value, path) {
            if (typeof value !== &quot;string&quot; || !value.trim()) {
                dataError(path, &quot;expected a non-empty string&quot;);
            }
        }

        function parseYamlSubset(raw) {
            const lines = raw.replace(&#x2F;\r\n?&#x2F;g, &quot;\n&quot;).split(&quot;\n&quot;);
            const tokens = [];

            lines.forEach(function (line, index) {
                if (!line.trim() || &#x2F;^\s*#&#x2F;.test(line)) {
                    return;
                }

                if (&#x2F;^\s*\t&#x2F;.test(line)) {
                    dataError(&quot;line &quot; + (index + 1), &quot;tabs are not allowed for indentation&quot;);
                }

                const indent = line.match(&#x2F;^ *&#x2F;)[0].length;

                if (indent % 2 !== 0) {
                    dataError(&quot;line &quot; + (index + 1), &quot;indentation must use multiples of two spaces&quot;);
                }

                tokens.push({
                    indent: indent,
                    text: line.slice(indent),
                    line: index + 1
                });
            });

            if (!tokens.length) {
                dataError(&quot;YAML&quot;, &quot;expected data&quot;);
            }

            const parsed = parseYamlBlock(tokens, 0, tokens[0].indent);

            if (parsed.index !== tokens.length) {
                dataError(&quot;line &quot; + tokens[parsed.index].line, &quot;unexpected content&quot;);
            }

            return parsed.value;
        }

        function parseYamlBlock(tokens, index, indent) {
            if (index &gt;= tokens.length) {
                dataError(&quot;YAML&quot;, &quot;expected block&quot;);
            }

            if (tokens[index].indent !== indent) {
                dataError(&quot;line &quot; + tokens[index].line, &quot;unexpected indentation&quot;);
            }

            if (tokens[index].text.startsWith(&quot;-&quot;)) {
                return parseYamlSequence(tokens, index, indent);
            }

            return parseYamlMap(tokens, index, indent);
        }

        function parseYamlMap(tokens, index, indent) {
            const value = {};

            while (index &lt; tokens.length &amp;&amp; tokens[index].indent === indent &amp;&amp; !tokens[index].text.startsWith(&quot;-&quot;)) {
                const token = tokens[index];
                const match = token.text.match(&#x2F;^([A-Za-z_][A-Za-z0-9_-]*):(.*)$&#x2F;);

                if (!match) {
                    dataError(&quot;line &quot; + token.line, &quot;expected key: value&quot;);
                }

                const key = match[1];
                const scalar = match[2].trim();

                if (Object.prototype.hasOwnProperty.call(value, key)) {
                    dataError(&quot;line &quot; + token.line, &quot;duplicate key &quot; + key);
                }

                index += 1;

                if (scalar) {
                    value[key] = parseYamlScalar(scalar, token.line);
                } else {
                    if (index &gt;= tokens.length || tokens[index].indent &lt;= indent) {
                        dataError(&quot;line &quot; + token.line, &quot;expected nested value for &quot; + key);
                    }

                    const child = parseYamlBlock(tokens, index, tokens[index].indent);
                    value[key] = child.value;
                    index = child.index;
                }
            }

            return { value: value, index: index };
        }

        function parseYamlSequence(tokens, index, indent) {
            const value = [];

            while (index &lt; tokens.length &amp;&amp; tokens[index].indent === indent &amp;&amp; tokens[index].text.startsWith(&quot;-&quot;)) {
                const token = tokens[index];
                const rest = token.text.slice(1).trim();

                index += 1;

                if (!rest) {
                    if (index &gt;= tokens.length || tokens[index].indent &lt;= indent) {
                        dataError(&quot;line &quot; + token.line, &quot;expected nested sequence item&quot;);
                    }

                    const child = parseYamlBlock(tokens, index, tokens[index].indent);
                    value.push(child.value);
                    index = child.index;
                    continue;
                }

                const pair = rest.match(&#x2F;^([A-Za-z_][A-Za-z0-9_-]*):(.*)$&#x2F;);

                if (pair) {
                    const item = {};
                    item[pair[1]] = parseYamlScalar(pair[2].trim(), token.line);

                    while (index &lt; tokens.length &amp;&amp; tokens[index].indent === indent + 2 &amp;&amp; !tokens[index].text.startsWith(&quot;-&quot;)) {
                        const propertyToken = tokens[index];
                        const property = propertyToken.text.match(&#x2F;^([A-Za-z_][A-Za-z0-9_-]*):(.*)$&#x2F;);

                        if (!property) {
                            dataError(&quot;line &quot; + propertyToken.line, &quot;expected key: value&quot;);
                        }

                        if (Object.prototype.hasOwnProperty.call(item, property[1])) {
                            dataError(&quot;line &quot; + propertyToken.line, &quot;duplicate key &quot; + property[1]);
                        }

                        const propertyValue = property[2].trim();
                        index += 1;

                        if (propertyValue) {
                            item[property[1]] = parseYamlScalar(propertyValue, propertyToken.line);
                        } else {
                            if (index &gt;= tokens.length || tokens[index].indent &lt;= propertyToken.indent) {
                                dataError(&quot;line &quot; + propertyToken.line, &quot;expected nested value for &quot; + property[1]);
                            }

                            const child = parseYamlBlock(tokens, index, tokens[index].indent);
                            item[property[1]] = child.value;
                            index = child.index;
                        }
                    }

                    value.push(item);
                    continue;
                }

                value.push(parseYamlScalar(rest, token.line));
            }

            return { value: value, index: index };
        }

        function parseYamlScalar(value, line) {
            if (!value) {
                dataError(&quot;line &quot; + line, &quot;expected scalar value&quot;);
            }

            if (value[0] === &quot;\&quot;&quot; || value[0] === &quot;&#x27;&quot;) {
                return parseQuotedYamlScalar(value, line);
            }

            if (value === &quot;true&quot;) {
                return true;
            }

            if (value === &quot;false&quot;) {
                return false;
            }

            if (value === &quot;null&quot; || value === &quot;None&quot;) {
                return null;
            }

            if (value === &quot;[]&quot;) {
                return [];
            }

            if (&#x2F;^-?(0|[1-9][0-9]*)(\.[0-9]+)?$&#x2F;.test(value)) {
                return Number(value);
            }

            if (&#x2F;[\[\]{}]&#x2F;.test(value)) {
                dataError(&quot;line &quot; + line, &quot;inline arrays and objects are not supported; use indented blocks&quot;);
            }

            return value;
        }

        function parseQuotedYamlScalar(value, line) {
            const quote = value[0];

            if (value[value.length - 1] !== quote) {
                dataError(&quot;line &quot; + line, &quot;unterminated quoted string&quot;);
            }

            if (quote === &quot;\&quot;&quot;) {
                try {
                    return JSON.parse(value);
                } catch (error) {
                    dataError(&quot;line &quot; + line, &quot;invalid quoted string&quot;);
                }
            }

            return value.slice(1, -1).replace(&#x2F;&#x27;&#x27;&#x2F;g, &quot;&#x27;&quot;);
        }

        function formatPythonLiteral(value, type) {
            if (type === &quot;str&quot;) {
                return JSON.stringify(value === undefined ? &quot;&quot; : String(value));
            }

            if (type === &quot;bool&quot;) {
                return value ? &quot;True&quot; : &quot;False&quot;;
            }

            if (type === &quot;NoneType&quot; || value === null) {
                return &quot;None&quot;;
            }

            if (value !== undefined) {
                return String(value);
            }

            return &quot;&quot;;
        }

        function normalizeDetails(value, path) {
            if (Array.isArray(value)) {
                return value.map(function (line, index) {
                    if (typeof line !== &quot;string&quot;) {
                        dataError(path + &quot;[&quot; + index + &quot;]&quot;, &quot;expected a string&quot;);
                    }

                    return line;
                });
            }

            if (value &amp;&amp; typeof value === &quot;object&quot;) {
                return Object.keys(value).map(function (key) {
                    return key + &quot;: &quot; + value[key];
                });
            }

            if (value !== undefined &amp;&amp; value !== null &amp;&amp; value !== &quot;&quot;) {
                return [String(value)];
            }

            return [];
        }

        function validateEntries(value, path) {
            if (Array.isArray(value)) {
                return value.map(function (item, index) {
                    const itemPath = path + &quot;[&quot; + index + &quot;]&quot;;

                    assertPlainObject(item, itemPath);
                    assertString(item.id, itemPath + &quot;.id&quot;);
                    assertId(item.id, itemPath + &quot;.id&quot;);

                    return {
                        id: item.id,
                        value: item,
                        path: itemPath
                    };
                });
            }

            assertPlainObject(value, path);

            return Object.keys(value).map(function (id) {
                assertId(id, path + &quot;.&quot; + id);
                assertPlainObject(value[id], path + &quot;.&quot; + id);

                return {
                    id: id,
                    value: value[id],
                    path: path + &quot;.&quot; + id
                };
            });
        }

        function validateReference(reference, path, label) {
            assertPlainObject(reference, path);
            assertAllowedKeys(reference, path, [&quot;object&quot;]);
            assertString(reference.object, path + &quot;.object&quot;);
            assertId(reference.object, path + &quot;.object&quot;);

            return {
                id: stableId(label, &quot;reference&quot;),
                label: String(label),
                target: reference.object,
                note: &quot;&quot;
            };
        }

        function validateBinding(binding, path, index) {
            assertPlainObject(binding, path);
            assertAllowedKeys(binding, path, [&quot;name&quot;, &quot;object&quot;]);
            assertString(binding.name, path + &quot;.name&quot;);

            return validateReference({ object: binding.object }, path, binding.name || index);
        }

        function validateFrame(entry) {
            const frame = entry.value;

            assertAllowedKeys(frame, entry.path, [&quot;id&quot;, &quot;name&quot;, &quot;bindings&quot;]);

            if (frame.id !== undefined &amp;&amp; frame.id !== entry.id) {
                dataError(entry.path + &quot;.id&quot;, &quot;must match key &quot; + entry.id);
            }

            if (frame.name !== undefined) {
                assertString(frame.name, entry.path + &quot;.name&quot;);
            }

            assertArray(frame.bindings, entry.path + &quot;.bindings&quot;);

            const bindingNames = new Set();

            return {
                id: entry.id,
                name: frame.name || entry.id,
                bindings: frame.bindings.map(function (binding, index) {
                    if (isPlainObject(binding) &amp;&amp; bindingNames.has(binding.name)) {
                        dataError(entry.path + &quot;.bindings[&quot; + index + &quot;].name&quot;, &quot;duplicate binding name &quot; + binding.name);
                    }

                    if (isPlainObject(binding)) {
                        bindingNames.add(binding.name);
                    }

                    return validateBinding(binding, entry.path + &quot;.bindings[&quot; + index + &quot;]&quot;, index);
                })
            };
        }

        function validateObjectValue(type, value, path) {
            if (type === &quot;int&quot;) {
                if (!Number.isInteger(value)) {
                    dataError(path, &quot;expected an integer for type int&quot;);
                }

                return;
            }

            if (type === &quot;float&quot;) {
                if (typeof value !== &quot;number&quot;) {
                    dataError(path, &quot;expected a number for type float&quot;);
                }

                return;
            }

            if (type === &quot;bool&quot;) {
                if (typeof value !== &quot;boolean&quot;) {
                    dataError(path, &quot;expected true or false for type bool&quot;);
                }

                return;
            }

            if (type === &quot;str&quot;) {
                if (typeof value !== &quot;string&quot;) {
                    dataError(path, &quot;expected a string for type str&quot;);
                }

                return;
            }

            if (type === &quot;NoneType&quot;) {
                if (value !== null) {
                    dataError(path, &quot;expected null for type NoneType&quot;);
                }
            }
        }

        function validateObject(entry) {
            const object = entry.value;
            const type = object.type || object.kind;
            let collectionItems = [];
            const supportedTypes = new Set([&quot;int&quot;, &quot;float&quot;, &quot;bool&quot;, &quot;NoneType&quot;, &quot;str&quot;, &quot;list&quot;, &quot;object&quot;]);

            assertAllowedKeys(object, entry.path, [&quot;id&quot;, &quot;type&quot;, &quot;kind&quot;, &quot;value&quot;, &quot;details&quot;]);

            if (object.id !== undefined &amp;&amp; object.id !== entry.id) {
                dataError(entry.path + &quot;.id&quot;, &quot;must match key &quot; + entry.id);
            }

            assertString(type, entry.path + &quot;.type&quot;);

            if (!supportedTypes.has(type)) {
                dataError(entry.path + &quot;.type&quot;, &quot;unsupported type &quot; + type);
            }

            if (object.type !== undefined &amp;&amp; object.kind !== undefined) {
                dataError(entry.path + &quot;.kind&quot;, &quot;use either type or kind, not both&quot;);
            }

            if (type === &quot;list&quot;) {
                assertArray(object.value, entry.path + &quot;.value&quot;);
                collectionItems = object.value.map(function (reference, index) {
                    return validateReference(reference, entry.path + &quot;.value[&quot; + index + &quot;]&quot;, index);
                });
            } else {
                if (object.value === undefined) {
                    dataError(entry.path + &quot;.value&quot;, &quot;expected a value&quot;);
                }

                validateObjectValue(type, object.value, entry.path + &quot;.value&quot;);
            }

            return {
                id: entry.id,
                type: type,
                value: object.value,
                details: normalizeDetails(object.details, entry.path + &quot;.details&quot;),
                collectionItems: collectionItems,
                missing: false
            };
        }

        function validateState(rawState) {
            assertPlainObject(rawState, &quot;root&quot;);
            assertAllowedKeys(rawState, &quot;root&quot;, [&quot;frames&quot;, &quot;objects&quot;]);

            if (rawState.frames === undefined) {
                dataError(&quot;root.frames&quot;, &quot;expected frames&quot;);
            }

            if (rawState.objects === undefined) {
                dataError(&quot;root.objects&quot;, &quot;expected objects&quot;);
            }

            const frameEntries = validateEntries(rawState.frames, &quot;frames&quot;);
            const objectEntries = validateEntries(rawState.objects, &quot;objects&quot;);
            const seenFrameIds = new Set();
            const seenIds = new Set();
            const frames = frameEntries.map(function (entry) {
                if (seenFrameIds.has(entry.id)) {
                    dataError(entry.path, &quot;duplicate frame id &quot; + entry.id);
                }

                seenFrameIds.add(entry.id);
                return validateFrame(entry);
            });
            const objects = objectEntries.map(function (entry) {
                if (seenIds.has(entry.id)) {
                    dataError(entry.path, &quot;duplicate object id &quot; + entry.id);
                }

                seenIds.add(entry.id);
                return validateObject(entry);
            });

            frames.forEach(function (frame) {
                frame.bindings.forEach(function (binding) {
                    if (!seenIds.has(binding.target)) {
                        dataError(&quot;frames.&quot; + frame.id + &quot;.bindings.&quot; + binding.label, &quot;unknown object &quot; + binding.target);
                    }
                });
            });

            objects.forEach(function (object) {
                object.collectionItems.forEach(function (item) {
                    if (!seenIds.has(item.target)) {
                        dataError(&quot;objects.&quot; + object.id + &quot;.value[&quot; + item.label + &quot;]&quot;, &quot;unknown object &quot; + item.target);
                    }
                });
            });

            return { frames: frames, objects: objects };
        }

        function textElement(tagName, className, text) {
            const element = document.createElement(tagName);
            element.className = className;
            element.textContent = text;
            return element;
        }

        function renderError(message) {
            const errorBox = document.createElement(&quot;div&quot;);
            errorBox.className = &quot;python-state-shortcode__error&quot;;
            errorBox.appendChild(textElement(&quot;div&quot;, &quot;python-state-shortcode__error-title&quot;, &quot;Python state data error&quot;));
            errorBox.appendChild(textElement(&quot;pre&quot;, &quot;python-state-shortcode__error-detail&quot;, message));
            nodes.appendChild(errorBox);

            fullscreenButton.disabled = true;
            stage.style.height = LAYOUT.minHeight + &quot;px&quot;;
        }

        function renderReference(reference, options) {
            const row = document.createElement(&quot;li&quot;);
            const isCollectionItem = options &amp;&amp; options.collectionItem;

            row.className = &quot;python-state-shortcode__reference&quot;
                + (isCollectionItem ? &quot; python-state-shortcode__reference--collection-item&quot; : &quot;&quot;);
            row.dataset.pythonStateTarget = reference.target;

            const label = textElement(&quot;span&quot;, &quot;python-state-shortcode__reference-label&quot;, reference.label);
            row.appendChild(label);

            const port = document.createElement(&quot;span&quot;);
            port.className = &quot;python-state-shortcode__reference-port&quot;;
            row.appendChild(port);

            return row;
        }

        function renderFrame(frame) {
            const box = document.createElement(&quot;section&quot;);
            box.className = &quot;python-state-shortcode__box python-state-shortcode__frame&quot;;
            box.dataset.pythonStateNodeId = frame.id;

            box.appendChild(textElement(&quot;div&quot;, &quot;python-state-shortcode__frame-title&quot;, frame.name));

            const list = document.createElement(&quot;ul&quot;);
            list.className = &quot;python-state-shortcode__bindings&quot;;

            frame.bindings.forEach(function (binding) {
                list.appendChild(renderReference(binding));
            });

            box.appendChild(list);
            return box;
        }

        function renderObjectContent(object, box) {
            const typeLine = textElement(&quot;div&quot;, &quot;python-state-shortcode__object-type&quot;, &quot;type: &quot; + object.type);
            box.appendChild(typeLine);

            const content = document.createElement(&quot;div&quot;);
            content.className = &quot;python-state-shortcode__object-content&quot;;

            if (object.collectionItems.length) {
                const collection = document.createElement(&quot;ul&quot;);
                collection.className = &quot;python-state-shortcode__collection&quot;;

                object.collectionItems.forEach(function (item) {
                    collection.appendChild(renderReference(item, { collectionItem: true }));
                });

                content.appendChild(collection);
            } else if (object.type === &quot;int&quot; || object.type === &quot;float&quot; || object.type === &quot;bool&quot; || object.type === &quot;NoneType&quot; || object.type === &quot;str&quot;) {
                content.appendChild(textElement(&quot;div&quot;, &quot;python-state-shortcode__literal&quot;, formatPythonLiteral(object.value, object.type)));
            } else if (object.details.length) {
                object.details.forEach(function (line) {
                    content.appendChild(textElement(&quot;div&quot;, &quot;python-state-shortcode__object-line&quot;, line));
                });
            } else if (object.value !== undefined) {
                content.appendChild(textElement(&quot;div&quot;, &quot;python-state-shortcode__literal&quot;, formatPythonLiteral(object.value, object.type)));
            }

            if (content.childNodes.length) {
                box.appendChild(content);
            }
        }

        function renderObject(object) {
            const box = document.createElement(&quot;section&quot;);
            const missingClass = object.missing ? &quot; python-state-shortcode__object--missing&quot; : &quot;&quot;;
            box.className = &quot;python-state-shortcode__box python-state-shortcode__object&quot; + missingClass;
            box.dataset.pythonStateNodeId = object.id;
            renderObjectContent(object, box);
            return box;
        }

        function rectInStage(element) {
            const stageRect = stage.getBoundingClientRect();
            const rect = element.getBoundingClientRect();

            return {
                left: rect.left - stageRect.left,
                top: rect.top - stageRect.top,
                right: rect.right - stageRect.left,
                bottom: rect.bottom - stageRect.top,
                width: rect.width,
                height: rect.height,
                centerX: rect.left - stageRect.left + rect.width &#x2F; 2,
                centerY: rect.top - stageRect.top + rect.height &#x2F; 2
            };
        }

        function getStageWidth() {
            const viewportWidth = viewport.clientWidth;
            return Math.max(viewportWidth, LAYOUT.minWidth);
        }

        function updateStageWidth(width) {
            stage.style.width = Math.max(getStageWidth(), width || 0) + &quot;px&quot;;
        }

        function resetStageWidthToViewport() {
            stage.style.width = getStageWidth() + &quot;px&quot;;
        }

        function setNodePosition(element, left, top) {
            element.style.left = left + &quot;px&quot;;
            element.style.top = top + &quot;px&quot;;
        }

        function captureNodeViewportPositions(nodeElements) {
            const positions = new Map();

            nodeElements.forEach(function (element) {
                const rect = element.getBoundingClientRect();
                positions.set(element, {
                    left: rect.left,
                    top: rect.top
                });
            });

            return positions;
        }

        function applyNodeViewportPositions(positions) {
            const stageRect = stage.getBoundingClientRect();

            positions.forEach(function (position, element) {
                setNodePosition(
                    element,
                    position.left - stageRect.left,
                    position.top - stageRect.top
                );
            });
        }

        function clampNodePosition(element) {
            const left = parseFloat(element.style.left) || 0;
            const top = parseFloat(element.style.top) || 0;
            const maxX = Math.max(0, stage.clientWidth - element.offsetWidth);
            const maxY = Math.max(0, stage.clientHeight - element.offsetHeight);

            setNodePosition(
                element,
                clamp(left, 0, maxX),
                clamp(top, 0, maxY)
            );
        }

        function fitNodeIntoCompactStage(element) {
            const left = parseFloat(element.style.left) || 0;
            const top = parseFloat(element.style.top) || 0;
            const maxX = Math.max(0, stage.clientWidth - element.offsetWidth);

            setNodePosition(
                element,
                clamp(left, 0, maxX),
                Math.max(0, top)
            );
        }

        function computeObjectLevels(state) {
            const levels = new Map();
            const objectsById = new Map();
            const referencesByObject = new Map();

            state.objects.forEach(function (object) {
                objectsById.set(object.id, object);
                referencesByObject.set(object.id, object.collectionItems.map(function (reference) {
                    return reference.target;
                }));
            });

            function reaches(startId, targetId, visited) {
                if (startId === targetId) {
                    return true;
                }

                if (visited.has(startId)) {
                    return false;
                }

                visited.add(startId);

                return (referencesByObject.get(startId) || []).some(function (nextId) {
                    return reaches(nextId, targetId, visited);
                });
            }

            state.frames.forEach(function (frame) {
                frame.bindings.forEach(function (binding) {
                    if (objectsById.has(binding.target) &amp;&amp; !levels.has(binding.target)) {
                        levels.set(binding.target, 1);
                    }
                });
            });

            state.objects.forEach(function (object) {
                if (!levels.has(object.id)) {
                    levels.set(object.id, 1);
                }
            });

            for (let pass = 0; pass &lt; state.objects.length; pass += 1) {
                let changed = false;

                state.objects.forEach(function (object) {
                    const sourceLevel = levels.get(object.id) || 1;

                    object.collectionItems.forEach(function (reference) {
                        if (reference.target === object.id || reaches(reference.target, object.id, new Set())) {
                            return;
                        }

                        const targetLevel = levels.get(reference.target) || 1;
                        const requiredLevel = sourceLevel + 1;

                        if (targetLevel &lt; requiredLevel) {
                            levels.set(reference.target, requiredLevel);
                            changed = true;
                        }
                    });
                });

                if (!changed) {
                    break;
                }
            }

            return levels;
        }

        function getObjectOriginalOrder(state) {
            const order = new Map();

            state.objects.forEach(function (object, index) {
                order.set(object.id, index);
            });

            return order;
        }

        function addReferenceScore(stats, target, candidateIds, score) {
            if (!candidateIds.has(target)) {
                return;
            }

            if (!stats.has(target)) {
                stats.set(target, {
                    count: 0,
                    sum: 0
                });
            }

            const targetStats = stats.get(target);
            targetStats.count += 1;
            targetStats.sum += score;
        }

        function getIncomingReferenceStats(state, objectLevels, objectColumnOrder, level, candidates) {
            const candidateIds = new Set(candidates.map(function (object) {
                return object.id;
            }));
            const stats = new Map();

            if (level === 1) {
                state.frames.forEach(function (frame, frameIndex) {
                    const referenceCount = Math.max(1, frame.bindings.length + 1);

                    frame.bindings.forEach(function (binding, bindingIndex) {
                        addReferenceScore(
                            stats,
                            binding.target,
                            candidateIds,
                            frameIndex + (bindingIndex + 1) &#x2F; referenceCount
                        );
                    });
                });
            }

            state.objects.forEach(function (object) {
                const sourceLevel = objectLevels.get(object.id) || 1;
                const sourceOrder = objectColumnOrder.get(object.id);

                if (sourceLevel &gt;= level || sourceOrder === undefined) {
                    return;
                }

                const referenceCount = Math.max(1, object.collectionItems.length + 1);

                object.collectionItems.forEach(function (reference, referenceIndex) {
                    addReferenceScore(
                        stats,
                        reference.target,
                        candidateIds,
                        sourceOrder + (referenceIndex + 1) &#x2F; referenceCount
                    );
                });
            });

            return stats;
        }

        function buildOrderedObjectColumns(state, objectElements, objectLevels) {
            const originalOrder = getObjectOriginalOrder(state);
            const objectColumnOrder = new Map();
            const objectsByLevel = new Map();
            const columns = [];
            let maxLevel = 0;

            state.objects.forEach(function (object) {
                if (!objectElements.has(object.id)) {
                    return;
                }

                const level = objectLevels.get(object.id) || 1;
                maxLevel = Math.max(maxLevel, level);

                if (!objectsByLevel.has(level)) {
                    objectsByLevel.set(level, []);
                }

                objectsByLevel.get(level).push(object);
            });

            for (let level = 1; level &lt;= maxLevel; level += 1) {
                const objects = objectsByLevel.get(level) || [];
                const incomingStats = getIncomingReferenceStats(
                    state,
                    objectLevels,
                    objectColumnOrder,
                    level,
                    objects
                );

                objects.sort(function (left, right) {
                    const leftStats = incomingStats.get(left.id);
                    const rightStats = incomingStats.get(right.id);
                    const leftScore = leftStats ? leftStats.sum &#x2F; leftStats.count : Number.POSITIVE_INFINITY;
                    const rightScore = rightStats ? rightStats.sum &#x2F; rightStats.count : Number.POSITIVE_INFINITY;

                    return leftScore - rightScore
                        || (originalOrder.get(left.id) || 0) - (originalOrder.get(right.id) || 0);
                });

                objects.forEach(function (object, index) {
                    objectColumnOrder.set(object.id, index);
                });

                if (objects.length) {
                    columns.push(objects.map(function (object) {
                        return objectElements.get(object.id);
                    }));
                }
            }

            return columns;
        }

        function buildObjectReferenceMap(state) {
            const referencesByObject = new Map();

            state.objects.forEach(function (object) {
                referencesByObject.set(object.id, object.collectionItems.map(function (reference) {
                    return reference.target;
                }));
            });

            return referencesByObject;
        }

        function objectReachesTarget(referencesByObject, startId, targetId, visited) {
            if (startId === targetId) {
                return true;
            }

            if (visited.has(startId)) {
                return false;
            }

            visited.add(startId);

            return (referencesByObject.get(startId) || []).some(function (nextId) {
                return objectReachesTarget(referencesByObject, nextId, targetId, visited);
            });
        }

        function collectReachableObjects(startIds, referencesByObject) {
            const reachable = new Set();
            const queue = startIds.slice();

            while (queue.length) {
                const id = queue.shift();

                if (reachable.has(id)) {
                    continue;
                }

                reachable.add(id);

                (referencesByObject.get(id) || []).forEach(function (targetId) {
                    queue.push(targetId);
                });
            }

            return reachable;
        }

        function inflateRect(rect, amount) {
            return {
                left: rect.left - amount,
                top: rect.top - amount,
                right: rect.right + amount,
                bottom: rect.bottom + amount,
                width: rect.width + amount * 2,
                height: rect.height + amount * 2,
                centerX: rect.centerX,
                centerY: rect.centerY
            };
        }

        function segmentIntersectsRect(start, end, rect) {
            const steps = 24;

            for (let step = 1; step &lt; steps; step += 1) {
                const ratio = step &#x2F; steps;
                const x = start.x + (end.x - start.x) * ratio;
                const y = start.y + (end.y - start.y) * ratio;

                if (x &gt;= rect.left &amp;&amp; x &lt;= rect.right &amp;&amp; y &gt;= rect.top &amp;&amp; y &lt;= rect.bottom) {
                    return true;
                }
            }

            return false;
        }

        function rectCenter(rect) {
            return {
                x: rect.centerX,
                y: rect.centerY
            };
        }

        function getTargetTopBelowObstacle(sourcePoint, targetPoint, targetElement, obstacleRect, pathRect) {
            const gap = LAYOUT.boxGap;
            const obstaclePathRect = pathRect || obstacleRect;
            const fallbackTop = obstaclePathRect.bottom + gap;
            const dx = targetPoint.x - sourcePoint.x;

            if (Math.abs(dx) &lt; 1) {
                return fallbackTop;
            }

            const segmentLeft = Math.min(sourcePoint.x, targetPoint.x);
            const segmentRight = Math.max(sourcePoint.x, targetPoint.x);
            const overlapLeft = Math.max(segmentLeft, obstaclePathRect.left);
            const overlapRight = Math.min(segmentRight, obstaclePathRect.right);

            if (overlapLeft &gt; overlapRight) {
                return fallbackTop;
            }

            const ratios = [overlapLeft, overlapRight].map(function (x) {
                return (x - sourcePoint.x) &#x2F; dx;
            }).filter(function (ratio) {
                return ratio &gt; 0 &amp;&amp; ratio &lt; 1;
            });

            if (!ratios.length) {
                return fallbackTop;
            }

            const closestObstacleRatio = Math.min.apply(null, ratios);
            const requiredCenterY = sourcePoint.y
                + (obstaclePathRect.bottom + gap - sourcePoint.y) &#x2F; closestObstacleRatio;

            return Math.max(fallbackTop, requiredCenterY - targetElement.offsetHeight &#x2F; 2);
        }

        function moveElementBelow(element, obstacleRect, sourcePoint, targetPoint, pathRect) {
            const top = parseFloat(element.style.top) || 0;
            const targetTop = getTargetTopBelowObstacle(sourcePoint, targetPoint, element, obstacleRect, pathRect);

            if (top &lt; targetTop) {
                setNodePosition(element, parseFloat(element.style.left) || 0, targetTop);
                return true;
            }

            return false;
        }

        function resolveColumnVerticalOverlaps(columns) {
            columns.forEach(function (column) {
                let bottom = LAYOUT.outerGap;

                column.forEach(function (element) {
                    const top = parseFloat(element.style.top) || 0;

                    if (top &lt; bottom) {
                        setNodePosition(element, parseFloat(element.style.left) || 0, bottom);
                    }

                    bottom = (parseFloat(element.style.top) || 0) + element.offsetHeight + LAYOUT.boxGap;
                });
            });
        }

        function avoidAlternativePathCollisions(state, frameElements, objectElements, objectColumns) {
            const referencesByObject = buildObjectReferenceMap(state);
            const objectIds = state.objects.map(function (object) {
                return object.id;
            });
            const edges = [];

            state.frames.forEach(function (frame, frameIndex) {
                const reachable = collectReachableObjects(frame.bindings.map(function (binding) {
                    return binding.target;
                }), referencesByObject);

                frame.bindings.forEach(function (binding) {
                    edges.push({
                        sourceElement: frameElements[frameIndex],
                        sourceId: &quot;&quot;,
                        sourceReachable: reachable,
                        targetId: binding.target
                    });
                });
            });

            state.objects.forEach(function (object) {
                const reachable = collectReachableObjects([object.id], referencesByObject);

                object.collectionItems.forEach(function (reference) {
                    edges.push({
                        sourceElement: objectElements.get(object.id),
                        sourceId: object.id,
                        sourceReachable: reachable,
                        targetId: reference.target
                    });
                });
            });

            for (let pass = 0; pass &lt; state.objects.length; pass += 1) {
                let changed = false;

                edges.forEach(function (edge) {
                    const targetElement = objectElements.get(edge.targetId);

                    if (!edge.sourceElement || !targetElement) {
                        return;
                    }

                    const sourceRect = rectInStage(edge.sourceElement);
                    const targetRect = rectInStage(targetElement);
                    const sourcePoint = rectCenter(sourceRect);
                    const targetPoint = rectCenter(targetRect);

                    objectIds.forEach(function (obstacleId) {
                        if (obstacleId === edge.sourceId || obstacleId === edge.targetId) {
                            return;
                        }

                        if (!edge.sourceReachable.has(obstacleId)) {
                            return;
                        }

                        if (!objectReachesTarget(referencesByObject, obstacleId, edge.targetId, new Set())) {
                            return;
                        }

                        const obstacleElement = objectElements.get(obstacleId);

                        if (!obstacleElement) {
                            return;
                        }

                        const obstacleRect = rectInStage(obstacleElement);
                        const pathRect = inflateRect(obstacleRect, LAYOUT.alternativePathClearance);

                        if (segmentIntersectsRect(sourcePoint, targetPoint, pathRect)) {
                            changed = moveElementBelow(targetElement, obstacleRect, sourcePoint, targetPoint, pathRect) || changed;
                        }
                    });
                });

                if (changed) {
                    resolveColumnVerticalOverlaps(objectColumns);
                } else {
                    break;
                }
            }
        }

        function positionDefault(state, frameElements, objectElements) {
            const objectLevels = computeObjectLevels(state);
            const columns = [frameElements].concat(
                buildOrderedObjectColumns(state, objectElements, objectLevels)
            );
            const columnWidths = columns.map(function (column) {
                if (!column || !column.length) {
                    return 0;
                }

                return column.reduce(function (width, element) {
                    return Math.max(width, element.offsetWidth);
                }, 0);
            });
            const columnHeights = columns.map(function (column) {
                if (!column || !column.length) {
                    return 0;
                }

                return column.reduce(function (height, element) {
                    return height + element.offsetHeight;
                }, 0) + LAYOUT.boxGap * (column.length - 1);
            });
            const objectColumns = columns.slice(1);
            const objectColumnWidths = columnWidths.slice(1);
            const regularColumnGap = Math.max(44, Math.round(getStageWidth() * 0.07));
            const objectGaps = objectColumns.slice(0, -1).map(function () {
                return regularColumnGap;
            });
            const objectContentWidth = objectColumnWidths.reduce(function (sum, width) {
                return sum + width;
            }, 0) + objectGaps.reduce(function (sum, gap) {
                return sum + gap;
            }, 0);
            const contentHeight = columnHeights.reduce(function (height, columnHeight) {
                return Math.max(height, columnHeight);
            }, 0);
            const frameContentWidth = columnWidths[0] || 0;
            const requiredUsableWidth = Math.max(
                frameContentWidth * 3,
                objectContentWidth * 1.5
            );
            const stageWidth = Math.max(
                getStageWidth(),
                Math.ceil(requiredUsableWidth + LAYOUT.outerGap * 2)
            );
            const stageHeight = Math.max(LAYOUT.minHeight, contentHeight + LAYOUT.outerGap * 2);
            const usableWidth = stageWidth - LAYOUT.outerGap * 2;
            const frameAreaLeft = LAYOUT.outerGap;
            const frameAreaWidth = usableWidth &#x2F; 3;
            const objectAreaLeft = frameAreaLeft + frameAreaWidth;
            const objectAreaWidth = usableWidth * 2 &#x2F; 3;
            const yStart = Math.max(LAYOUT.outerGap, Math.round((stageHeight - contentHeight) &#x2F; 2));

            updateStageWidth(stageWidth);
            stage.style.height = stageHeight + &quot;px&quot;;

            if (columns[0] &amp;&amp; columns[0].length) {
                let frameY = yStart;
                const frameX = Math.round(
                    frameAreaLeft + Math.max(0, (frameAreaWidth - frameContentWidth) &#x2F; 2)
                );

                columns[0].forEach(function (element) {
                    setNodePosition(element, frameX, frameY);
                    frameY += element.offsetHeight + LAYOUT.boxGap;
                });
            }

            let objectX = Math.round(
                objectAreaLeft + Math.max(0, (objectAreaWidth - objectContentWidth) &#x2F; 2)
            );

            objectColumns.forEach(function (column, index) {
                if (!column || !column.length) {
                    return;
                }

                let y = yStart;
                column.forEach(function (element) {
                    setNodePosition(element, objectX, y);
                    y += element.offsetHeight + LAYOUT.boxGap;
                });

                objectX += objectColumnWidths[index] + (objectGaps[index] || 0);
            });

            avoidAlternativePathCollisions(state, frameElements, objectElements, objectColumns);
        }

        function updateStageHeight(nodeElements) {
            let bottom = isFullscreen()
                ? Math.max(LAYOUT.minHeight, viewport.clientHeight)
                : LAYOUT.minHeight;

            nodeElements.forEach(function (element) {
                const rect = rectInStage(element);
                bottom = Math.max(bottom, rect.bottom + LAYOUT.outerGap);
            });

            stage.style.height = bottom + &quot;px&quot;;
            arrowsSvg.setAttribute(&quot;viewBox&quot;, &quot;0 0 &quot; + stage.offsetWidth + &quot; &quot; + bottom);
        }

        function updateStageWidthToFit(nodeElements) {
            let right = LAYOUT.minWidth;

            nodeElements.forEach(function (element) {
                const rect = rectInStage(element);
                right = Math.max(right, rect.right + LAYOUT.outerGap);
            });

            updateStageWidth(right);
        }

        function sideNormal(side) {
            if (side === &quot;left&quot;) {
                return { x: -1, y: 0 };
            }

            if (side === &quot;right&quot;) {
                return { x: 1, y: 0 };
            }

            if (side === &quot;top&quot;) {
                return { x: 0, y: -1 };
            }

            return { x: 0, y: 1 };
        }

        function chooseConnectionSide(fromRect, toRect) {
            if (toRect.right &lt; fromRect.left) {
                return &quot;left&quot;;
            }

            if (toRect.left &gt; fromRect.right) {
                return &quot;right&quot;;
            }

            if (toRect.bottom &lt; fromRect.top) {
                return &quot;top&quot;;
            }

            if (toRect.top &gt; fromRect.bottom) {
                return &quot;bottom&quot;;
            }

            const dx = toRect.centerX - fromRect.centerX;
            const dy = toRect.centerY - fromRect.centerY;

            if (Math.abs(dx) &gt;= Math.abs(dy)) {
                return dx &gt;= 0 ? &quot;right&quot; : &quot;left&quot;;
            }

            return dy &gt;= 0 ? &quot;bottom&quot; : &quot;top&quot;;
        }

        function chooseReferenceSide(sourceRect, targetRect) {
            return targetRect.centerX &lt; sourceRect.centerX ? &quot;left&quot; : &quot;right&quot;;
        }

        function chooseReferenceSourceSide(reference, sourceRect, targetRect) {
            if (reference.classList.contains(&quot;python-state-shortcode__reference--collection-item&quot;)) {
                return chooseConnectionSide(rectInStage(reference), targetRect);
            }

            return chooseReferenceSide(sourceRect, targetRect);
        }

        function borderPointForSide(rect, side, anchor) {
            const horizontalPadding = Math.min(LAYOUT.cornerGap, rect.width &#x2F; 2);
            const verticalPadding = Math.min(LAYOUT.cornerGap, rect.height &#x2F; 2);

            if (side === &quot;left&quot; || side === &quot;right&quot;) {
                return {
                    x: side === &quot;left&quot; ? rect.left : rect.right,
                    y: clamp(anchor.y, rect.top + verticalPadding, rect.bottom - verticalPadding),
                    side: side
                };
            }

            return {
                x: clamp(anchor.x, rect.left + horizontalPadding, rect.right - horizontalPadding),
                y: side === &quot;top&quot; ? rect.top : rect.bottom,
                side: side
            };
        }

        function outsidePoint(point, amount) {
            const normal = sideNormal(point.side);

            return {
                x: point.x + normal.x * amount,
                y: point.y + normal.y * amount,
                side: point.side
            };
        }

        function pointFromPort(portRect, side) {
            if (side === &quot;bottom&quot;) {
                return {
                    x: portRect.centerX,
                    y: portRect.centerY,
                    side: side
                };
            }

            if (side === &quot;top&quot;) {
                return {
                    x: portRect.centerX,
                    y: portRect.centerY,
                    side: side
                };
            }

            if (side === &quot;left&quot;) {
                return {
                    x: portRect.left,
                    y: portRect.centerY,
                    side: side
                };
            }

            return {
                x: portRect.right,
                y: portRect.centerY,
                side: side
            };
        }

        function createArrowGeometry(start, end, arrowheadLength) {
            const dx = end.x - start.x;
            const dy = end.y - start.y;
            const distance = Math.max(70, Math.sqrt(dx * dx + dy * dy));
            const bend = Math.min(90, distance * 0.32);
            const startNormal = sideNormal(start.side);
            const endNormal = sideNormal(end.side);
            const lineEnd = {
                x: end.x + endNormal.x * arrowheadLength,
                y: end.y + endNormal.y * arrowheadLength,
                side: end.side
            };
            const control1 = {
                x: start.x + startNormal.x * bend,
                y: start.y + startNormal.y * bend
            };
            const control2 = {
                x: end.x + endNormal.x * bend,
                y: end.y + endNormal.y * bend
            };

            return {
                path: [
                    &quot;M&quot;, start.x, start.y,
                    &quot;C&quot;, control1.x, control1.y, control2.x, control2.y, lineEnd.x, lineEnd.y
                ].join(&quot; &quot;),
                control2: control2
            };
        }

        function createArrowheadPoints(end, controlPoint, length, width) {
            const dx = end.x - controlPoint.x;
            const dy = end.y - controlPoint.y;
            const distance = Math.sqrt(dx * dx + dy * dy) || 1;
            const ux = dx &#x2F; distance;
            const uy = dy &#x2F; distance;
            const nx = -uy;
            const ny = ux;
            const baseX = end.x - ux * length;
            const baseY = end.y - uy * length;

            return [
                [end.x, end.y],
                [baseX + nx * width, baseY + ny * width],
                [baseX - nx * width, baseY - ny * width]
            ].map(function (point) {
                return point[0] + &quot;,&quot; + point[1];
            }).join(&quot; &quot;);
        }

        function svgElement(tagName, className) {
            const element = document.createElementNS(&quot;http:&#x2F;&#x2F;www.w3.org&#x2F;2000&#x2F;svg&quot;, tagName);
            element.classList.add(className);
            return element;
        }

        function createEdgePath(className, pathData) {
            const path = svgElement(&quot;path&quot;, className);

            path.setAttribute(&quot;d&quot;, pathData);

            return path;
        }

        function createEdgeArrowhead(className, points) {
            const arrowhead = svgElement(&quot;polygon&quot;, className);

            arrowhead.setAttribute(&quot;points&quot;, points);

            return arrowhead;
        }

        function createEdgeCircle(className, point, radius) {
            const circle = svgElement(&quot;circle&quot;, className);

            circle.setAttribute(&quot;cx&quot;, point.x);
            circle.setAttribute(&quot;cy&quot;, point.y);
            circle.setAttribute(&quot;r&quot;, radius);

            return circle;
        }

        function setEdgeActive(edgeId, active) {
            const edge = arrowsSvg.querySelector(&quot;[data-python-state-edge-id=\&quot;&quot; + edgeId + &quot;\&quot;]&quot;);

            if (edge) {
                edge.classList.toggle(&quot;python-state-shortcode__edge--active&quot;, active);
            }
        }

        function drawArrows(objectElements) {
            arrowsSvg.querySelectorAll(&quot;.python-state-shortcode__edge&quot;).forEach(function (edge) {
                edge.remove();
            });

            root.querySelectorAll(&quot;[data-python-state-target]&quot;).forEach(function (reference, index) {
                const target = reference.dataset.pythonStateTarget;
                const object = objectElements.get(target);

                if (!target || !object) {
                    return;
                }

                const port = reference.querySelector(&quot;.python-state-shortcode__reference-port&quot;);
                const sourceBox = reference.closest(&quot;.python-state-shortcode__box&quot;);
                const sourceRect = rectInStage(sourceBox);
                const objectRect = rectInStage(object);
                const sourceSide = chooseReferenceSourceSide(reference, sourceRect, objectRect);

                reference.dataset.pythonStateReferenceSide = sourceSide;

                const portRect = rectInStage(port || reference);
                const portAnchor = { x: portRect.centerX, y: portRect.centerY };
                const targetSide = chooseConnectionSide(objectRect, portRect);
                const targetBorder = borderPointForSide(objectRect, targetSide, portAnchor);
                const visualPortPoint = rectCenter(portRect);
                const linePortPoint = pointFromPort(portRect, sourceSide);
                const start = outsidePoint(linePortPoint, LAYOUT.arrowGap);
                const end = outsidePoint(targetBorder, LAYOUT.arrowGap);
                const arrowheadLength = 8;
                const arrowGeometry = createArrowGeometry(start, end, arrowheadLength);
                const pathData = arrowGeometry.path;
                const arrowheadPoints = createArrowheadPoints(end, arrowGeometry.control2, arrowheadLength, 4);
                const edge = svgElement(&quot;g&quot;, &quot;python-state-shortcode__edge&quot;);
                const edgeId = &quot;edge-&quot; + index;

                edge.setAttribute(&quot;aria-hidden&quot;, &quot;true&quot;);
                edge.dataset.pythonStateEdgeId = edgeId;
                reference.dataset.pythonStateEdgeId = edgeId;
                reference.onmouseenter = function () {
                    setEdgeActive(edgeId, true);
                };
                reference.onmouseleave = function () {
                    setEdgeActive(edgeId, false);
                };
                edge.addEventListener(&quot;mouseenter&quot;, function () {
                    setEdgeActive(edgeId, true);
                });
                edge.addEventListener(&quot;mouseleave&quot;, function () {
                    setEdgeActive(edgeId, false);
                });
                edge.appendChild(createEdgePath(&quot;python-state-shortcode__edge-hit&quot;, pathData));
                edge.appendChild(createEdgePath(&quot;python-state-shortcode__edge-halo&quot;, pathData));
                edge.appendChild(createEdgePath(&quot;python-state-shortcode__edge-line&quot;, pathData));
                edge.appendChild(createEdgeArrowhead(&quot;python-state-shortcode__edge-arrowhead-halo&quot;, arrowheadPoints));
                edge.appendChild(createEdgeArrowhead(&quot;python-state-shortcode__edge-arrowhead&quot;, arrowheadPoints));
                edge.appendChild(createEdgeCircle(&quot;python-state-shortcode__edge-port-halo&quot;, visualPortPoint, 5.2));
                edge.appendChild(createEdgeCircle(&quot;python-state-shortcode__edge-port&quot;, visualPortPoint, 4.2));
                arrowsSvg.appendChild(edge);
            });
        }

        function isFullscreen() {
            return root.classList.contains(&quot;python-state-shortcode--fullscreen&quot;);
        }

        function isTransparentColor(value) {
            return !value || value === &quot;transparent&quot; || value === &quot;rgba(0, 0, 0, 0)&quot;;
        }

        function getPageBackgroundColor() {
            const candidates = [document.body, document.documentElement, root.parentElement].filter(Boolean);

            for (let index = 0; index &lt; candidates.length; index += 1) {
                const background = window.getComputedStyle(candidates[index]).backgroundColor;

                if (!isTransparentColor(background)) {
                    return background;
                }
            }

            return &quot;transparent&quot;;
        }

        function updateFullpageMetrics() {
            const nav = document.querySelector(&quot;nav&quot;);
            const navRect = nav ? nav.getBoundingClientRect() : null;
            const top = navRect ? Math.max(0, Math.round(navRect.bottom)) : 0;

            root.style.setProperty(&quot;--python-state-fullpage-top&quot;, top + &quot;px&quot;);
            root.style.setProperty(&quot;--python-state-fullpage-bg&quot;, getPageBackgroundColor());
        }

        function setPageScrollLocked(locked) {
            if (locked) {
                savedDocumentOverflow = document.documentElement.style.overflow;
                savedBodyOverflow = document.body.style.overflow;
                document.documentElement.style.overflow = &quot;hidden&quot;;
                document.body.style.overflow = &quot;hidden&quot;;
                return;
            }

            document.documentElement.style.overflow = savedDocumentOverflow;
            document.body.style.overflow = savedBodyOverflow;
        }

        function refreshGeometry(allNodeElements, objectElements) {
            updateStageHeight(allNodeElements);
            drawArrows(objectElements);
        }

        function setFullscreenMode(enabled, allNodeElements, objectElements) {
            if (enabled === isFullscreen()) {
                return;
            }

            const preTogglePositions = captureNodeViewportPositions(allNodeElements);

            if (enabled) {
                updateFullpageMetrics();
            }

            root.classList.toggle(&quot;python-state-shortcode--fullscreen&quot;, enabled);
            fullscreenButton.setAttribute(&quot;aria-pressed&quot;, enabled ? &quot;true&quot; : &quot;false&quot;);
            fullscreenButton.textContent = enabled ? &quot;Exit&quot; : &quot;Full page&quot;;
            setPageScrollLocked(enabled);

            window.requestAnimationFrame(function () {
                if (enabled) {
                    applyNodeViewportPositions(preTogglePositions);
                    updateStageWidthToFit(allNodeElements);
                } else {
                    resetStageWidthToViewport();
                    applyNodeViewportPositions(preTogglePositions);
                    allNodeElements.forEach(fitNodeIntoCompactStage);
                }

                refreshGeometry(allNodeElements, objectElements);
            });
        }

        function makeDraggable(element, objectElements) {
            let offsetX = 0;
            let offsetY = 0;
            let dragging = false;

            element.addEventListener(&quot;pointerdown&quot;, function (event) {
                dragging = true;
                element.classList.add(&quot;python-state-shortcode__box--dragging&quot;);
                element.setPointerCapture(event.pointerId);

                const rect = element.getBoundingClientRect();
                offsetX = event.clientX - rect.left;
                offsetY = event.clientY - rect.top;
            });

            element.addEventListener(&quot;pointermove&quot;, function (event) {
                if (!dragging) {
                    return;
                }

                const stageRect = stage.getBoundingClientRect();
                const maxX = Math.max(0, stage.clientWidth - element.offsetWidth);
                const maxY = Math.max(0, stage.clientHeight - element.offsetHeight);
                const x = clamp(event.clientX - stageRect.left - offsetX, 0, maxX);
                const y = clamp(event.clientY - stageRect.top - offsetY, 0, maxY);

                setNodePosition(element, x, y);
                drawArrows(objectElements);
            });

            function stopDragging() {
                dragging = false;
                element.classList.remove(&quot;python-state-shortcode__box--dragging&quot;);
            }

            element.addEventListener(&quot;pointerup&quot;, stopDragging);
            element.addEventListener(&quot;pointercancel&quot;, stopDragging);
        }

        function render(state) {
            const frameElements = state.frames.map(function (frame) {
                const frameElement = renderFrame(frame);
                nodes.appendChild(frameElement);
                return frameElement;
            });
            const objectElements = new Map();

            state.objects.forEach(function (object) {
                const objectElement = renderObject(object);
                objectElements.set(object.id, objectElement);
                nodes.appendChild(objectElement);
            });

            const allNodeElements = frameElements.concat(Array.from(objectElements.values()));

            positionDefault(state, frameElements, objectElements);
            updateStageHeight(allNodeElements);
            drawArrows(objectElements);

            allNodeElements.forEach(function (element) {
                makeDraggable(element, objectElements);
            });

            fullscreenButton.addEventListener(&quot;click&quot;, function () {
                setFullscreenMode(!isFullscreen(), allNodeElements, objectElements);
            });

            document.addEventListener(&quot;keydown&quot;, function (event) {
                if (event.key === &quot;Escape&quot; &amp;&amp; isFullscreen()) {
                    setFullscreenMode(false, allNodeElements, objectElements);
                }
            });

            window.addEventListener(&quot;resize&quot;, function () {
                if (isFullscreen()) {
                    updateFullpageMetrics();
                    updateStageWidthToFit(allNodeElements);
                } else {
                    resetStageWidthToViewport();
                }

                allNodeElements.forEach(clampNodePosition);
                refreshGeometry(allNodeElements, objectElements);
            });
        }

        const parsed = parseState();

        if (parsed.error) {
            renderError(parsed.error);
        } else {
            render(parsed.state);
        }
    }());
    &lt;&#x2F;script&gt;
&lt;&#x2F;div&gt;
&lt;p&gt;Важно понимать, что управление переменными и объектами происходит очень небольшим количеством способов — небольшим количеством механизмов языка. Сейчас мы рассмотрели&lt;&#x2F;p&gt;
&lt;ol&gt;
&lt;li&gt;Получение объектов через &lt;em&gt;вычисление выражений&lt;&#x2F;em&gt; (примеры таких выражений: &lt;code&gt;1&lt;&#x2F;code&gt;, &lt;code&gt;a + 1&lt;&#x2F;code&gt;).  Результат вычисления лежит в оперативной памяти и доступен нам как объект.&lt;&#x2F;li&gt;
&lt;li&gt;Создание или изменение ссылки переменной через &lt;em&gt;присваивание&lt;&#x2F;em&gt; (в общем виде, &lt;code&gt;имя = выражение&lt;&#x2F;code&gt;, например &lt;code&gt;a = 1&lt;&#x2F;code&gt;, &lt;code&gt;a = 1 + 2&lt;&#x2F;code&gt;)&lt;&#x2F;li&gt;
&lt;&#x2F;ol&gt;
&lt;p&gt;И, в целом, эти два механизма будут нас сопровождать при разработке на Python постоянно, потому важно уметь бегло считывать использование этих механизмов при чтении кода: видеть их и понимать, что именно они делают.&lt;&#x2F;p&gt;
&lt;h1 id=&quot;zadaniia&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#zadaniia&quot; aria-label=&quot;Anchor link for: zadaniia&quot;&gt;Задания&lt;&#x2F;a&gt;&lt;&#x2F;h1&gt;
&lt;h2 id=&quot;zadanie-1&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#zadanie-1&quot; aria-label=&quot;Anchor link for: zadanie-1&quot;&gt;Задание 1&lt;&#x2F;a&gt;&lt;&#x2F;h2&gt;
&lt;pre class=&quot;giallo z-code&quot;&gt;&lt;code data-lang=&quot;python&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;name&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; =&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt; &amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;Tom&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;age&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; =&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt; 20&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;message&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; =&lt;&#x2F;span&gt;&lt;span&gt; name&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; +&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt; &amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; is &lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; +&lt;&#x2F;span&gt;&lt;span class=&quot;z-support&quot;&gt; str&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span&gt;age&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;age&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; =&lt;&#x2F;span&gt;&lt;span&gt; age&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; +&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt; 1&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;name&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; =&lt;&#x2F;span&gt;&lt;span&gt; name&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; +&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt; &amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; Smith&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;final_message&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; =&lt;&#x2F;span&gt;&lt;span&gt; name&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; +&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt; &amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; is &lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; +&lt;&#x2F;span&gt;&lt;span class=&quot;z-support&quot;&gt; str&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span&gt;age&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Попробуйте самостоятельно подробно объяснить, что происходит на каждой строке:&lt;&#x2F;p&gt;
&lt;ol&gt;
&lt;li&gt;Какие объекты вычисляются (с какими типами и значениями)?&lt;&#x2F;li&gt;
&lt;li&gt;Какие переменные создаются?  Какие переменные начинают ссылаться на другой объект?&lt;&#x2F;li&gt;
&lt;li&gt;На какие объекты ссылаются переменные name, age, message и final_message после каждой строки?&lt;&#x2F;li&gt;
&lt;li&gt;Изменяется ли какой-нибудь уже существующий объект?&lt;&#x2F;li&gt;
&lt;&#x2F;ol&gt;
&lt;h2 id=&quot;zadanie-2&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#zadanie-2&quot; aria-label=&quot;Anchor link for: zadanie-2&quot;&gt;Задание 2&lt;&#x2F;a&gt;&lt;&#x2F;h2&gt;
&lt;p&gt;Напишите программу на Python которая создаёт следующие имена и объекты:&lt;&#x2F;p&gt;




&lt;style&gt;
.python-state-shortcode {
    position: relative;
    border: 1px solid white;
    border-radius: 2px;
    overflow: hidden;
    padding: 1em;
    margin: 2em 0;
}

.python-state-shortcode__header {
    position: absolute;
    top: 0.75rem;
    right: 0.75rem;
    z-index: 4;
    pointer-events: none;
}

.python-state-shortcode__fullscreen-button {
    border: 1px solid white;
    border-radius: 2px;
    background: transparent;
    color: inherit;
    padding: 0.3em 0.55em;
    font: inherit;
    font-size: 0.78rem;
    line-height: 1;
    cursor: pointer;
    pointer-events: auto;
}

.python-state-shortcode__fullscreen-button:hover,
.python-state-shortcode__fullscreen-button:focus-visible {
    background: rgba(255, 255, 255, 0.12);
}

.python-state-shortcode__viewport {
    overflow-x: auto;
}

.python-state-shortcode__stage {
    position: relative;
    min-width: 560px;
    min-height: 240px;
    overflow: hidden;
}

.python-state-shortcode__arrows {
    position: absolute;
    inset: 0;
    width: 100%;
    height: 100%;
    pointer-events: auto;
    z-index: 1;
}

.python-state-shortcode__nodes {
    position: absolute;
    inset: 0;
    z-index: 2;
}

.python-state-shortcode__box {
    position: absolute;
    box-sizing: border-box;
    width: max-content;
    min-width: 4.5rem;
    max-width: 18rem;
    border: 1px solid white;
    border-radius: 2px;
    background: var(--bg, transparent);
    padding: 0.55em 0.7em;
    cursor: grab;
    user-select: none;
    touch-action: none;
}

.python-state-shortcode__box:active {
    cursor: grabbing;
}

.python-state-shortcode__box--dragging {
    z-index: 3;
}

.python-state-shortcode__frame-title {
    font-weight: 600;
    line-height: 1.2;
    margin-bottom: 0.45em;
    white-space: nowrap;
}

.python-state-shortcode__object-type {
    line-height: 1.2;
    margin-bottom: 0.45em;
    white-space: nowrap;
}

.python-state-shortcode__bindings,
.python-state-shortcode__object-content {
    display: grid;
    gap: 0.3em;
    margin: 0;
    padding: 0;
    list-style: none;
}

.python-state-shortcode__collection {
    display: flex;
    flex-flow: row nowrap;
    gap: 0;
    margin: 0;
    padding: 0.15em 0 0.25em 0;
    list-style: none;
}

.python-state-shortcode__reference {
    position: relative;
    display: block;
    box-sizing: border-box;
    width: 100%;
    padding-right: 1.05em;
    min-height: 1.35em;
    line-height: 1.25;
}

.python-state-shortcode__reference-label,
.python-state-shortcode__literal,
.python-state-shortcode__object-line {
    overflow-wrap: anywhere;
    font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, &quot;Liberation Mono&quot;, monospace;
    font-size: 0.9rem;
}

.python-state-shortcode__reference-label {
    display: block;
    white-space: nowrap;
}

.python-state-shortcode__reference-port {
    position: absolute;
    top: 50%;
    right: 0.2em;
    width: 0.48em;
    height: 0.48em;
    transform: translateY(-50%);
    opacity: 0;
    pointer-events: none;
}

.python-state-shortcode__reference[data-python-state-reference-side=&quot;left&quot;] {
    padding-right: 0;
    padding-left: 1.05em;
}

.python-state-shortcode__reference[data-python-state-reference-side=&quot;left&quot;] .python-state-shortcode__reference-port {
    right: auto;
    left: 0.2em;
}

.python-state-shortcode__reference--collection-item {
    display: flex;
    align-items: center;
    justify-content: center;
    width: 2.15em;
    height: 2.15em;
    min-height: 0;
    padding: 0;
    border: 1px solid white;
    box-sizing: border-box;
}

.python-state-shortcode__reference--collection-item + .python-state-shortcode__reference--collection-item {
    border-left: 0;
}

.python-state-shortcode__reference--collection-item .python-state-shortcode__reference-label {
    display: none;
}

.python-state-shortcode__reference--collection-item .python-state-shortcode__reference-port,
.python-state-shortcode__reference--collection-item[data-python-state-reference-side=&quot;left&quot;] .python-state-shortcode__reference-port {
    top: 50%;
    right: auto;
    bottom: auto;
    left: 50%;
    transform: translate(-50%, -50%);
}

.python-state-shortcode__literal {
    line-height: 1.35;
}

.python-state-shortcode__object--missing {
    border-style: dashed;
}

.python-state-shortcode__edge {
    pointer-events: visibleStroke;
    color: #60a5fa;
}

.python-state-shortcode__edge-hit {
    fill: none;
    stroke: transparent;
    stroke-width: 14;
    stroke-linecap: round;
    stroke-linejoin: round;
    pointer-events: stroke;
}

.python-state-shortcode__edge-halo {
    fill: none;
    stroke: rgba(255, 255, 255, 0);
    stroke-width: 4;
    stroke-linecap: round;
    stroke-linejoin: round;
    transition: stroke 140ms ease;
}

.python-state-shortcode__edge-line {
    fill: none;
    stroke: currentColor;
    stroke-width: 1.5;
    stroke-linecap: round;
    stroke-linejoin: round;
    transition: stroke 140ms ease;
}

.python-state-shortcode__edge-port-halo {
    fill: none;
    stroke: rgba(255, 255, 255, 0);
    stroke-width: 5;
    transition: stroke 140ms ease;
}

.python-state-shortcode__edge-arrowhead-halo {
    fill: none;
    stroke: rgba(255, 255, 255, 0);
    stroke-width: 3;
    stroke-linejoin: round;
    transition: stroke 140ms ease;
}

.python-state-shortcode__edge-arrowhead {
    fill: currentColor;
    transition: fill 140ms ease;
}

.python-state-shortcode__edge-port {
    fill: currentColor;
    stroke: currentColor;
    stroke-width: 1;
    transition: fill 140ms ease, stroke 140ms ease, r 140ms ease;
}

.python-state-shortcode__edge:hover,
.python-state-shortcode__edge:focus-visible,
.python-state-shortcode__edge--active {
    color: #93c5fd;
    outline: none;
}

.python-state-shortcode__edge:hover .python-state-shortcode__edge-halo,
.python-state-shortcode__edge:focus-visible .python-state-shortcode__edge-halo,
.python-state-shortcode__edge--active .python-state-shortcode__edge-halo,
.python-state-shortcode__edge:hover .python-state-shortcode__edge-port-halo,
.python-state-shortcode__edge:focus-visible .python-state-shortcode__edge-port-halo,
.python-state-shortcode__edge--active .python-state-shortcode__edge-port-halo {
    stroke: rgba(255, 255, 255, 0.92);
}

.python-state-shortcode__edge:hover .python-state-shortcode__edge-arrowhead-halo,
.python-state-shortcode__edge:focus-visible .python-state-shortcode__edge-arrowhead-halo,
.python-state-shortcode__edge--active .python-state-shortcode__edge-arrowhead-halo {
    stroke: rgba(255, 255, 255, 0.92);
}

.python-state-shortcode__error {
    border: 1px solid white;
    border-radius: 2px;
    padding: 0.75em 0.9em;
    max-width: 42rem;
    line-height: 1.35;
}

.python-state-shortcode__error-title {
    margin-bottom: 0.45em;
    font-weight: 600;
}

.python-state-shortcode__error-detail {
    margin: 0;
    white-space: pre-wrap;
    overflow-wrap: anywhere;
    font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, &quot;Liberation Mono&quot;, monospace;
    font-size: 0.9rem;
}

.python-state-shortcode--fullscreen {
    position: fixed;
    top: var(--python-state-fullpage-top, 0);
    right: 0;
    bottom: 0;
    left: 0;
    z-index: 1000;
    display: flex;
    flex-direction: column;
    box-sizing: border-box;
    width: 100vw;
    height: calc(100vh - var(--python-state-fullpage-top, 0px));
    height: calc(100dvh - var(--python-state-fullpage-top, 0px));
    margin: 0;
    border: 0;
    border-radius: 0;
    padding: 0;
    background: var(--python-state-fullpage-bg, transparent);
    overflow: hidden;
}

.python-state-shortcode--fullscreen .python-state-shortcode__header {
    margin: 0;
}

.python-state-shortcode--fullscreen .python-state-shortcode__viewport {
    flex: 1 1 auto;
    min-height: 0;
    overflow: auto;
}

.python-state-shortcode--fullscreen .python-state-shortcode__stage {
    min-height: 100%;
}
&lt;&#x2F;style&gt;

&lt;div class=&quot;python-state-shortcode&quot; data-python-state-shortcode&gt;
    &lt;div class=&quot;python-state-shortcode__header&quot;&gt;
        &lt;button class=&quot;python-state-shortcode__fullscreen-button&quot; type=&quot;button&quot; data-python-state-fullscreen-button aria-pressed=&quot;false&quot;&gt;Full page&lt;&#x2F;button&gt;
    &lt;&#x2F;div&gt;

    &lt;div class=&quot;python-state-shortcode__viewport&quot;&gt;
        &lt;div class=&quot;python-state-shortcode__stage&quot; data-python-state-stage aria-label=&quot;Python state&quot;&gt;
            &lt;svg class=&quot;python-state-shortcode__arrows&quot; data-python-state-arrows aria-hidden=&quot;true&quot;&gt;&lt;&#x2F;svg&gt;
            &lt;div class=&quot;python-state-shortcode__nodes&quot; data-python-state-nodes&gt;&lt;&#x2F;div&gt;
        &lt;&#x2F;div&gt;
    &lt;&#x2F;div&gt;

    &lt;script type=&quot;text&#x2F;plain&quot; class=&quot;python-state-shortcode__data&quot; data-python-state-data&gt;
frames:
  global:
    name: Global frame
    bindings:
      - name: items
        object: items
      - name: a
        object: a
objects:
  items:
    type: list
    value:
      - object: one
      - object: a_copy
      - object: a
  a:
    type: str
    value: Hello
  a_copy:
    type: str
    value: Hello
  one:
    type: int
    value: 1
    &lt;&#x2F;script&gt;

    &lt;script&gt;
    (function () {
        const currentScript = document.currentScript;
        const root = currentScript.closest(&quot;[data-python-state-shortcode]&quot;);
        const viewport = root.querySelector(&quot;.python-state-shortcode__viewport&quot;);
        const stage = root.querySelector(&quot;[data-python-state-stage]&quot;);
        const nodes = root.querySelector(&quot;[data-python-state-nodes]&quot;);
        const arrowsSvg = root.querySelector(&quot;[data-python-state-arrows]&quot;);
        const dataNode = root.querySelector(&quot;[data-python-state-data]&quot;);
        const fullscreenButton = root.querySelector(&quot;[data-python-state-fullscreen-button]&quot;);
        let savedDocumentOverflow = &quot;&quot;;
        let savedBodyOverflow = &quot;&quot;;

        const LAYOUT = {
            minWidth: 560,
            minHeight: 240,
            outerGap: 18,
            boxGap: 14,
            arrowGap: 7,
            cornerGap: 10,
            alternativePathClearance: 18
        };

        function stableId(value, fallback) {
            const raw = String(value || fallback || &quot;item&quot;).trim().toLowerCase();
            const slug = raw.replace(&#x2F;[^a-z0-9_-]+&#x2F;g, &quot;-&quot;).replace(&#x2F;^-+|-+$&#x2F;g, &quot;&quot;);
            return slug || String(fallback || &quot;item&quot;);
        }

        function clamp(value, min, max) {
            return Math.max(min, Math.min(max, value));
        }

        function parseState() {
            const raw = dataNode.textContent.trim();

            if (!raw) {
                return {
                    error: &quot;python_state shortcode expects a JSON or YAML body.&quot;
                };
            }

            try {
                const parsed = raw[0] === &quot;{&quot; || raw[0] === &quot;[&quot;
                    ? JSON.parse(raw)
                    : parseYamlSubset(raw);

                return {
                    state: validateState(parsed)
                };
            } catch (error) {
                return {
                    error: &quot;Invalid python_state shortcode data:\n&quot; + error.message
                };
            }
        }

        function dataError(path, message) {
            throw new Error(path + &quot;: &quot; + message);
        }

        function isPlainObject(value) {
            return Boolean(value) &amp;&amp; typeof value === &quot;object&quot; &amp;&amp; !Array.isArray(value);
        }

        function assertPlainObject(value, path) {
            if (!isPlainObject(value)) {
                dataError(path, &quot;expected an object&quot;);
            }
        }

        function assertArray(value, path) {
            if (!Array.isArray(value)) {
                dataError(path, &quot;expected an array&quot;);
            }
        }

        function assertAllowedKeys(value, path, allowedKeys) {
            const allowed = new Set(allowedKeys);

            Object.keys(value).forEach(function (key) {
                if (!allowed.has(key)) {
                    dataError(path + &quot;.&quot; + key, &quot;unknown field&quot;);
                }
            });
        }

        function assertId(value, path) {
            if (typeof value !== &quot;string&quot; || !&#x2F;^[A-Za-z_][A-Za-z0-9_-]*$&#x2F;.test(value)) {
                dataError(path, &quot;expected an object id like items or value_1&quot;);
            }
        }

        function assertString(value, path) {
            if (typeof value !== &quot;string&quot; || !value.trim()) {
                dataError(path, &quot;expected a non-empty string&quot;);
            }
        }

        function parseYamlSubset(raw) {
            const lines = raw.replace(&#x2F;\r\n?&#x2F;g, &quot;\n&quot;).split(&quot;\n&quot;);
            const tokens = [];

            lines.forEach(function (line, index) {
                if (!line.trim() || &#x2F;^\s*#&#x2F;.test(line)) {
                    return;
                }

                if (&#x2F;^\s*\t&#x2F;.test(line)) {
                    dataError(&quot;line &quot; + (index + 1), &quot;tabs are not allowed for indentation&quot;);
                }

                const indent = line.match(&#x2F;^ *&#x2F;)[0].length;

                if (indent % 2 !== 0) {
                    dataError(&quot;line &quot; + (index + 1), &quot;indentation must use multiples of two spaces&quot;);
                }

                tokens.push({
                    indent: indent,
                    text: line.slice(indent),
                    line: index + 1
                });
            });

            if (!tokens.length) {
                dataError(&quot;YAML&quot;, &quot;expected data&quot;);
            }

            const parsed = parseYamlBlock(tokens, 0, tokens[0].indent);

            if (parsed.index !== tokens.length) {
                dataError(&quot;line &quot; + tokens[parsed.index].line, &quot;unexpected content&quot;);
            }

            return parsed.value;
        }

        function parseYamlBlock(tokens, index, indent) {
            if (index &gt;= tokens.length) {
                dataError(&quot;YAML&quot;, &quot;expected block&quot;);
            }

            if (tokens[index].indent !== indent) {
                dataError(&quot;line &quot; + tokens[index].line, &quot;unexpected indentation&quot;);
            }

            if (tokens[index].text.startsWith(&quot;-&quot;)) {
                return parseYamlSequence(tokens, index, indent);
            }

            return parseYamlMap(tokens, index, indent);
        }

        function parseYamlMap(tokens, index, indent) {
            const value = {};

            while (index &lt; tokens.length &amp;&amp; tokens[index].indent === indent &amp;&amp; !tokens[index].text.startsWith(&quot;-&quot;)) {
                const token = tokens[index];
                const match = token.text.match(&#x2F;^([A-Za-z_][A-Za-z0-9_-]*):(.*)$&#x2F;);

                if (!match) {
                    dataError(&quot;line &quot; + token.line, &quot;expected key: value&quot;);
                }

                const key = match[1];
                const scalar = match[2].trim();

                if (Object.prototype.hasOwnProperty.call(value, key)) {
                    dataError(&quot;line &quot; + token.line, &quot;duplicate key &quot; + key);
                }

                index += 1;

                if (scalar) {
                    value[key] = parseYamlScalar(scalar, token.line);
                } else {
                    if (index &gt;= tokens.length || tokens[index].indent &lt;= indent) {
                        dataError(&quot;line &quot; + token.line, &quot;expected nested value for &quot; + key);
                    }

                    const child = parseYamlBlock(tokens, index, tokens[index].indent);
                    value[key] = child.value;
                    index = child.index;
                }
            }

            return { value: value, index: index };
        }

        function parseYamlSequence(tokens, index, indent) {
            const value = [];

            while (index &lt; tokens.length &amp;&amp; tokens[index].indent === indent &amp;&amp; tokens[index].text.startsWith(&quot;-&quot;)) {
                const token = tokens[index];
                const rest = token.text.slice(1).trim();

                index += 1;

                if (!rest) {
                    if (index &gt;= tokens.length || tokens[index].indent &lt;= indent) {
                        dataError(&quot;line &quot; + token.line, &quot;expected nested sequence item&quot;);
                    }

                    const child = parseYamlBlock(tokens, index, tokens[index].indent);
                    value.push(child.value);
                    index = child.index;
                    continue;
                }

                const pair = rest.match(&#x2F;^([A-Za-z_][A-Za-z0-9_-]*):(.*)$&#x2F;);

                if (pair) {
                    const item = {};
                    item[pair[1]] = parseYamlScalar(pair[2].trim(), token.line);

                    while (index &lt; tokens.length &amp;&amp; tokens[index].indent === indent + 2 &amp;&amp; !tokens[index].text.startsWith(&quot;-&quot;)) {
                        const propertyToken = tokens[index];
                        const property = propertyToken.text.match(&#x2F;^([A-Za-z_][A-Za-z0-9_-]*):(.*)$&#x2F;);

                        if (!property) {
                            dataError(&quot;line &quot; + propertyToken.line, &quot;expected key: value&quot;);
                        }

                        if (Object.prototype.hasOwnProperty.call(item, property[1])) {
                            dataError(&quot;line &quot; + propertyToken.line, &quot;duplicate key &quot; + property[1]);
                        }

                        const propertyValue = property[2].trim();
                        index += 1;

                        if (propertyValue) {
                            item[property[1]] = parseYamlScalar(propertyValue, propertyToken.line);
                        } else {
                            if (index &gt;= tokens.length || tokens[index].indent &lt;= propertyToken.indent) {
                                dataError(&quot;line &quot; + propertyToken.line, &quot;expected nested value for &quot; + property[1]);
                            }

                            const child = parseYamlBlock(tokens, index, tokens[index].indent);
                            item[property[1]] = child.value;
                            index = child.index;
                        }
                    }

                    value.push(item);
                    continue;
                }

                value.push(parseYamlScalar(rest, token.line));
            }

            return { value: value, index: index };
        }

        function parseYamlScalar(value, line) {
            if (!value) {
                dataError(&quot;line &quot; + line, &quot;expected scalar value&quot;);
            }

            if (value[0] === &quot;\&quot;&quot; || value[0] === &quot;&#x27;&quot;) {
                return parseQuotedYamlScalar(value, line);
            }

            if (value === &quot;true&quot;) {
                return true;
            }

            if (value === &quot;false&quot;) {
                return false;
            }

            if (value === &quot;null&quot; || value === &quot;None&quot;) {
                return null;
            }

            if (value === &quot;[]&quot;) {
                return [];
            }

            if (&#x2F;^-?(0|[1-9][0-9]*)(\.[0-9]+)?$&#x2F;.test(value)) {
                return Number(value);
            }

            if (&#x2F;[\[\]{}]&#x2F;.test(value)) {
                dataError(&quot;line &quot; + line, &quot;inline arrays and objects are not supported; use indented blocks&quot;);
            }

            return value;
        }

        function parseQuotedYamlScalar(value, line) {
            const quote = value[0];

            if (value[value.length - 1] !== quote) {
                dataError(&quot;line &quot; + line, &quot;unterminated quoted string&quot;);
            }

            if (quote === &quot;\&quot;&quot;) {
                try {
                    return JSON.parse(value);
                } catch (error) {
                    dataError(&quot;line &quot; + line, &quot;invalid quoted string&quot;);
                }
            }

            return value.slice(1, -1).replace(&#x2F;&#x27;&#x27;&#x2F;g, &quot;&#x27;&quot;);
        }

        function formatPythonLiteral(value, type) {
            if (type === &quot;str&quot;) {
                return JSON.stringify(value === undefined ? &quot;&quot; : String(value));
            }

            if (type === &quot;bool&quot;) {
                return value ? &quot;True&quot; : &quot;False&quot;;
            }

            if (type === &quot;NoneType&quot; || value === null) {
                return &quot;None&quot;;
            }

            if (value !== undefined) {
                return String(value);
            }

            return &quot;&quot;;
        }

        function normalizeDetails(value, path) {
            if (Array.isArray(value)) {
                return value.map(function (line, index) {
                    if (typeof line !== &quot;string&quot;) {
                        dataError(path + &quot;[&quot; + index + &quot;]&quot;, &quot;expected a string&quot;);
                    }

                    return line;
                });
            }

            if (value &amp;&amp; typeof value === &quot;object&quot;) {
                return Object.keys(value).map(function (key) {
                    return key + &quot;: &quot; + value[key];
                });
            }

            if (value !== undefined &amp;&amp; value !== null &amp;&amp; value !== &quot;&quot;) {
                return [String(value)];
            }

            return [];
        }

        function validateEntries(value, path) {
            if (Array.isArray(value)) {
                return value.map(function (item, index) {
                    const itemPath = path + &quot;[&quot; + index + &quot;]&quot;;

                    assertPlainObject(item, itemPath);
                    assertString(item.id, itemPath + &quot;.id&quot;);
                    assertId(item.id, itemPath + &quot;.id&quot;);

                    return {
                        id: item.id,
                        value: item,
                        path: itemPath
                    };
                });
            }

            assertPlainObject(value, path);

            return Object.keys(value).map(function (id) {
                assertId(id, path + &quot;.&quot; + id);
                assertPlainObject(value[id], path + &quot;.&quot; + id);

                return {
                    id: id,
                    value: value[id],
                    path: path + &quot;.&quot; + id
                };
            });
        }

        function validateReference(reference, path, label) {
            assertPlainObject(reference, path);
            assertAllowedKeys(reference, path, [&quot;object&quot;]);
            assertString(reference.object, path + &quot;.object&quot;);
            assertId(reference.object, path + &quot;.object&quot;);

            return {
                id: stableId(label, &quot;reference&quot;),
                label: String(label),
                target: reference.object,
                note: &quot;&quot;
            };
        }

        function validateBinding(binding, path, index) {
            assertPlainObject(binding, path);
            assertAllowedKeys(binding, path, [&quot;name&quot;, &quot;object&quot;]);
            assertString(binding.name, path + &quot;.name&quot;);

            return validateReference({ object: binding.object }, path, binding.name || index);
        }

        function validateFrame(entry) {
            const frame = entry.value;

            assertAllowedKeys(frame, entry.path, [&quot;id&quot;, &quot;name&quot;, &quot;bindings&quot;]);

            if (frame.id !== undefined &amp;&amp; frame.id !== entry.id) {
                dataError(entry.path + &quot;.id&quot;, &quot;must match key &quot; + entry.id);
            }

            if (frame.name !== undefined) {
                assertString(frame.name, entry.path + &quot;.name&quot;);
            }

            assertArray(frame.bindings, entry.path + &quot;.bindings&quot;);

            const bindingNames = new Set();

            return {
                id: entry.id,
                name: frame.name || entry.id,
                bindings: frame.bindings.map(function (binding, index) {
                    if (isPlainObject(binding) &amp;&amp; bindingNames.has(binding.name)) {
                        dataError(entry.path + &quot;.bindings[&quot; + index + &quot;].name&quot;, &quot;duplicate binding name &quot; + binding.name);
                    }

                    if (isPlainObject(binding)) {
                        bindingNames.add(binding.name);
                    }

                    return validateBinding(binding, entry.path + &quot;.bindings[&quot; + index + &quot;]&quot;, index);
                })
            };
        }

        function validateObjectValue(type, value, path) {
            if (type === &quot;int&quot;) {
                if (!Number.isInteger(value)) {
                    dataError(path, &quot;expected an integer for type int&quot;);
                }

                return;
            }

            if (type === &quot;float&quot;) {
                if (typeof value !== &quot;number&quot;) {
                    dataError(path, &quot;expected a number for type float&quot;);
                }

                return;
            }

            if (type === &quot;bool&quot;) {
                if (typeof value !== &quot;boolean&quot;) {
                    dataError(path, &quot;expected true or false for type bool&quot;);
                }

                return;
            }

            if (type === &quot;str&quot;) {
                if (typeof value !== &quot;string&quot;) {
                    dataError(path, &quot;expected a string for type str&quot;);
                }

                return;
            }

            if (type === &quot;NoneType&quot;) {
                if (value !== null) {
                    dataError(path, &quot;expected null for type NoneType&quot;);
                }
            }
        }

        function validateObject(entry) {
            const object = entry.value;
            const type = object.type || object.kind;
            let collectionItems = [];
            const supportedTypes = new Set([&quot;int&quot;, &quot;float&quot;, &quot;bool&quot;, &quot;NoneType&quot;, &quot;str&quot;, &quot;list&quot;, &quot;object&quot;]);

            assertAllowedKeys(object, entry.path, [&quot;id&quot;, &quot;type&quot;, &quot;kind&quot;, &quot;value&quot;, &quot;details&quot;]);

            if (object.id !== undefined &amp;&amp; object.id !== entry.id) {
                dataError(entry.path + &quot;.id&quot;, &quot;must match key &quot; + entry.id);
            }

            assertString(type, entry.path + &quot;.type&quot;);

            if (!supportedTypes.has(type)) {
                dataError(entry.path + &quot;.type&quot;, &quot;unsupported type &quot; + type);
            }

            if (object.type !== undefined &amp;&amp; object.kind !== undefined) {
                dataError(entry.path + &quot;.kind&quot;, &quot;use either type or kind, not both&quot;);
            }

            if (type === &quot;list&quot;) {
                assertArray(object.value, entry.path + &quot;.value&quot;);
                collectionItems = object.value.map(function (reference, index) {
                    return validateReference(reference, entry.path + &quot;.value[&quot; + index + &quot;]&quot;, index);
                });
            } else {
                if (object.value === undefined) {
                    dataError(entry.path + &quot;.value&quot;, &quot;expected a value&quot;);
                }

                validateObjectValue(type, object.value, entry.path + &quot;.value&quot;);
            }

            return {
                id: entry.id,
                type: type,
                value: object.value,
                details: normalizeDetails(object.details, entry.path + &quot;.details&quot;),
                collectionItems: collectionItems,
                missing: false
            };
        }

        function validateState(rawState) {
            assertPlainObject(rawState, &quot;root&quot;);
            assertAllowedKeys(rawState, &quot;root&quot;, [&quot;frames&quot;, &quot;objects&quot;]);

            if (rawState.frames === undefined) {
                dataError(&quot;root.frames&quot;, &quot;expected frames&quot;);
            }

            if (rawState.objects === undefined) {
                dataError(&quot;root.objects&quot;, &quot;expected objects&quot;);
            }

            const frameEntries = validateEntries(rawState.frames, &quot;frames&quot;);
            const objectEntries = validateEntries(rawState.objects, &quot;objects&quot;);
            const seenFrameIds = new Set();
            const seenIds = new Set();
            const frames = frameEntries.map(function (entry) {
                if (seenFrameIds.has(entry.id)) {
                    dataError(entry.path, &quot;duplicate frame id &quot; + entry.id);
                }

                seenFrameIds.add(entry.id);
                return validateFrame(entry);
            });
            const objects = objectEntries.map(function (entry) {
                if (seenIds.has(entry.id)) {
                    dataError(entry.path, &quot;duplicate object id &quot; + entry.id);
                }

                seenIds.add(entry.id);
                return validateObject(entry);
            });

            frames.forEach(function (frame) {
                frame.bindings.forEach(function (binding) {
                    if (!seenIds.has(binding.target)) {
                        dataError(&quot;frames.&quot; + frame.id + &quot;.bindings.&quot; + binding.label, &quot;unknown object &quot; + binding.target);
                    }
                });
            });

            objects.forEach(function (object) {
                object.collectionItems.forEach(function (item) {
                    if (!seenIds.has(item.target)) {
                        dataError(&quot;objects.&quot; + object.id + &quot;.value[&quot; + item.label + &quot;]&quot;, &quot;unknown object &quot; + item.target);
                    }
                });
            });

            return { frames: frames, objects: objects };
        }

        function textElement(tagName, className, text) {
            const element = document.createElement(tagName);
            element.className = className;
            element.textContent = text;
            return element;
        }

        function renderError(message) {
            const errorBox = document.createElement(&quot;div&quot;);
            errorBox.className = &quot;python-state-shortcode__error&quot;;
            errorBox.appendChild(textElement(&quot;div&quot;, &quot;python-state-shortcode__error-title&quot;, &quot;Python state data error&quot;));
            errorBox.appendChild(textElement(&quot;pre&quot;, &quot;python-state-shortcode__error-detail&quot;, message));
            nodes.appendChild(errorBox);

            fullscreenButton.disabled = true;
            stage.style.height = LAYOUT.minHeight + &quot;px&quot;;
        }

        function renderReference(reference, options) {
            const row = document.createElement(&quot;li&quot;);
            const isCollectionItem = options &amp;&amp; options.collectionItem;

            row.className = &quot;python-state-shortcode__reference&quot;
                + (isCollectionItem ? &quot; python-state-shortcode__reference--collection-item&quot; : &quot;&quot;);
            row.dataset.pythonStateTarget = reference.target;

            const label = textElement(&quot;span&quot;, &quot;python-state-shortcode__reference-label&quot;, reference.label);
            row.appendChild(label);

            const port = document.createElement(&quot;span&quot;);
            port.className = &quot;python-state-shortcode__reference-port&quot;;
            row.appendChild(port);

            return row;
        }

        function renderFrame(frame) {
            const box = document.createElement(&quot;section&quot;);
            box.className = &quot;python-state-shortcode__box python-state-shortcode__frame&quot;;
            box.dataset.pythonStateNodeId = frame.id;

            box.appendChild(textElement(&quot;div&quot;, &quot;python-state-shortcode__frame-title&quot;, frame.name));

            const list = document.createElement(&quot;ul&quot;);
            list.className = &quot;python-state-shortcode__bindings&quot;;

            frame.bindings.forEach(function (binding) {
                list.appendChild(renderReference(binding));
            });

            box.appendChild(list);
            return box;
        }

        function renderObjectContent(object, box) {
            const typeLine = textElement(&quot;div&quot;, &quot;python-state-shortcode__object-type&quot;, &quot;type: &quot; + object.type);
            box.appendChild(typeLine);

            const content = document.createElement(&quot;div&quot;);
            content.className = &quot;python-state-shortcode__object-content&quot;;

            if (object.collectionItems.length) {
                const collection = document.createElement(&quot;ul&quot;);
                collection.className = &quot;python-state-shortcode__collection&quot;;

                object.collectionItems.forEach(function (item) {
                    collection.appendChild(renderReference(item, { collectionItem: true }));
                });

                content.appendChild(collection);
            } else if (object.type === &quot;int&quot; || object.type === &quot;float&quot; || object.type === &quot;bool&quot; || object.type === &quot;NoneType&quot; || object.type === &quot;str&quot;) {
                content.appendChild(textElement(&quot;div&quot;, &quot;python-state-shortcode__literal&quot;, formatPythonLiteral(object.value, object.type)));
            } else if (object.details.length) {
                object.details.forEach(function (line) {
                    content.appendChild(textElement(&quot;div&quot;, &quot;python-state-shortcode__object-line&quot;, line));
                });
            } else if (object.value !== undefined) {
                content.appendChild(textElement(&quot;div&quot;, &quot;python-state-shortcode__literal&quot;, formatPythonLiteral(object.value, object.type)));
            }

            if (content.childNodes.length) {
                box.appendChild(content);
            }
        }

        function renderObject(object) {
            const box = document.createElement(&quot;section&quot;);
            const missingClass = object.missing ? &quot; python-state-shortcode__object--missing&quot; : &quot;&quot;;
            box.className = &quot;python-state-shortcode__box python-state-shortcode__object&quot; + missingClass;
            box.dataset.pythonStateNodeId = object.id;
            renderObjectContent(object, box);
            return box;
        }

        function rectInStage(element) {
            const stageRect = stage.getBoundingClientRect();
            const rect = element.getBoundingClientRect();

            return {
                left: rect.left - stageRect.left,
                top: rect.top - stageRect.top,
                right: rect.right - stageRect.left,
                bottom: rect.bottom - stageRect.top,
                width: rect.width,
                height: rect.height,
                centerX: rect.left - stageRect.left + rect.width &#x2F; 2,
                centerY: rect.top - stageRect.top + rect.height &#x2F; 2
            };
        }

        function getStageWidth() {
            const viewportWidth = viewport.clientWidth;
            return Math.max(viewportWidth, LAYOUT.minWidth);
        }

        function updateStageWidth(width) {
            stage.style.width = Math.max(getStageWidth(), width || 0) + &quot;px&quot;;
        }

        function resetStageWidthToViewport() {
            stage.style.width = getStageWidth() + &quot;px&quot;;
        }

        function setNodePosition(element, left, top) {
            element.style.left = left + &quot;px&quot;;
            element.style.top = top + &quot;px&quot;;
        }

        function captureNodeViewportPositions(nodeElements) {
            const positions = new Map();

            nodeElements.forEach(function (element) {
                const rect = element.getBoundingClientRect();
                positions.set(element, {
                    left: rect.left,
                    top: rect.top
                });
            });

            return positions;
        }

        function applyNodeViewportPositions(positions) {
            const stageRect = stage.getBoundingClientRect();

            positions.forEach(function (position, element) {
                setNodePosition(
                    element,
                    position.left - stageRect.left,
                    position.top - stageRect.top
                );
            });
        }

        function clampNodePosition(element) {
            const left = parseFloat(element.style.left) || 0;
            const top = parseFloat(element.style.top) || 0;
            const maxX = Math.max(0, stage.clientWidth - element.offsetWidth);
            const maxY = Math.max(0, stage.clientHeight - element.offsetHeight);

            setNodePosition(
                element,
                clamp(left, 0, maxX),
                clamp(top, 0, maxY)
            );
        }

        function fitNodeIntoCompactStage(element) {
            const left = parseFloat(element.style.left) || 0;
            const top = parseFloat(element.style.top) || 0;
            const maxX = Math.max(0, stage.clientWidth - element.offsetWidth);

            setNodePosition(
                element,
                clamp(left, 0, maxX),
                Math.max(0, top)
            );
        }

        function computeObjectLevels(state) {
            const levels = new Map();
            const objectsById = new Map();
            const referencesByObject = new Map();

            state.objects.forEach(function (object) {
                objectsById.set(object.id, object);
                referencesByObject.set(object.id, object.collectionItems.map(function (reference) {
                    return reference.target;
                }));
            });

            function reaches(startId, targetId, visited) {
                if (startId === targetId) {
                    return true;
                }

                if (visited.has(startId)) {
                    return false;
                }

                visited.add(startId);

                return (referencesByObject.get(startId) || []).some(function (nextId) {
                    return reaches(nextId, targetId, visited);
                });
            }

            state.frames.forEach(function (frame) {
                frame.bindings.forEach(function (binding) {
                    if (objectsById.has(binding.target) &amp;&amp; !levels.has(binding.target)) {
                        levels.set(binding.target, 1);
                    }
                });
            });

            state.objects.forEach(function (object) {
                if (!levels.has(object.id)) {
                    levels.set(object.id, 1);
                }
            });

            for (let pass = 0; pass &lt; state.objects.length; pass += 1) {
                let changed = false;

                state.objects.forEach(function (object) {
                    const sourceLevel = levels.get(object.id) || 1;

                    object.collectionItems.forEach(function (reference) {
                        if (reference.target === object.id || reaches(reference.target, object.id, new Set())) {
                            return;
                        }

                        const targetLevel = levels.get(reference.target) || 1;
                        const requiredLevel = sourceLevel + 1;

                        if (targetLevel &lt; requiredLevel) {
                            levels.set(reference.target, requiredLevel);
                            changed = true;
                        }
                    });
                });

                if (!changed) {
                    break;
                }
            }

            return levels;
        }

        function getObjectOriginalOrder(state) {
            const order = new Map();

            state.objects.forEach(function (object, index) {
                order.set(object.id, index);
            });

            return order;
        }

        function addReferenceScore(stats, target, candidateIds, score) {
            if (!candidateIds.has(target)) {
                return;
            }

            if (!stats.has(target)) {
                stats.set(target, {
                    count: 0,
                    sum: 0
                });
            }

            const targetStats = stats.get(target);
            targetStats.count += 1;
            targetStats.sum += score;
        }

        function getIncomingReferenceStats(state, objectLevels, objectColumnOrder, level, candidates) {
            const candidateIds = new Set(candidates.map(function (object) {
                return object.id;
            }));
            const stats = new Map();

            if (level === 1) {
                state.frames.forEach(function (frame, frameIndex) {
                    const referenceCount = Math.max(1, frame.bindings.length + 1);

                    frame.bindings.forEach(function (binding, bindingIndex) {
                        addReferenceScore(
                            stats,
                            binding.target,
                            candidateIds,
                            frameIndex + (bindingIndex + 1) &#x2F; referenceCount
                        );
                    });
                });
            }

            state.objects.forEach(function (object) {
                const sourceLevel = objectLevels.get(object.id) || 1;
                const sourceOrder = objectColumnOrder.get(object.id);

                if (sourceLevel &gt;= level || sourceOrder === undefined) {
                    return;
                }

                const referenceCount = Math.max(1, object.collectionItems.length + 1);

                object.collectionItems.forEach(function (reference, referenceIndex) {
                    addReferenceScore(
                        stats,
                        reference.target,
                        candidateIds,
                        sourceOrder + (referenceIndex + 1) &#x2F; referenceCount
                    );
                });
            });

            return stats;
        }

        function buildOrderedObjectColumns(state, objectElements, objectLevels) {
            const originalOrder = getObjectOriginalOrder(state);
            const objectColumnOrder = new Map();
            const objectsByLevel = new Map();
            const columns = [];
            let maxLevel = 0;

            state.objects.forEach(function (object) {
                if (!objectElements.has(object.id)) {
                    return;
                }

                const level = objectLevels.get(object.id) || 1;
                maxLevel = Math.max(maxLevel, level);

                if (!objectsByLevel.has(level)) {
                    objectsByLevel.set(level, []);
                }

                objectsByLevel.get(level).push(object);
            });

            for (let level = 1; level &lt;= maxLevel; level += 1) {
                const objects = objectsByLevel.get(level) || [];
                const incomingStats = getIncomingReferenceStats(
                    state,
                    objectLevels,
                    objectColumnOrder,
                    level,
                    objects
                );

                objects.sort(function (left, right) {
                    const leftStats = incomingStats.get(left.id);
                    const rightStats = incomingStats.get(right.id);
                    const leftScore = leftStats ? leftStats.sum &#x2F; leftStats.count : Number.POSITIVE_INFINITY;
                    const rightScore = rightStats ? rightStats.sum &#x2F; rightStats.count : Number.POSITIVE_INFINITY;

                    return leftScore - rightScore
                        || (originalOrder.get(left.id) || 0) - (originalOrder.get(right.id) || 0);
                });

                objects.forEach(function (object, index) {
                    objectColumnOrder.set(object.id, index);
                });

                if (objects.length) {
                    columns.push(objects.map(function (object) {
                        return objectElements.get(object.id);
                    }));
                }
            }

            return columns;
        }

        function buildObjectReferenceMap(state) {
            const referencesByObject = new Map();

            state.objects.forEach(function (object) {
                referencesByObject.set(object.id, object.collectionItems.map(function (reference) {
                    return reference.target;
                }));
            });

            return referencesByObject;
        }

        function objectReachesTarget(referencesByObject, startId, targetId, visited) {
            if (startId === targetId) {
                return true;
            }

            if (visited.has(startId)) {
                return false;
            }

            visited.add(startId);

            return (referencesByObject.get(startId) || []).some(function (nextId) {
                return objectReachesTarget(referencesByObject, nextId, targetId, visited);
            });
        }

        function collectReachableObjects(startIds, referencesByObject) {
            const reachable = new Set();
            const queue = startIds.slice();

            while (queue.length) {
                const id = queue.shift();

                if (reachable.has(id)) {
                    continue;
                }

                reachable.add(id);

                (referencesByObject.get(id) || []).forEach(function (targetId) {
                    queue.push(targetId);
                });
            }

            return reachable;
        }

        function inflateRect(rect, amount) {
            return {
                left: rect.left - amount,
                top: rect.top - amount,
                right: rect.right + amount,
                bottom: rect.bottom + amount,
                width: rect.width + amount * 2,
                height: rect.height + amount * 2,
                centerX: rect.centerX,
                centerY: rect.centerY
            };
        }

        function segmentIntersectsRect(start, end, rect) {
            const steps = 24;

            for (let step = 1; step &lt; steps; step += 1) {
                const ratio = step &#x2F; steps;
                const x = start.x + (end.x - start.x) * ratio;
                const y = start.y + (end.y - start.y) * ratio;

                if (x &gt;= rect.left &amp;&amp; x &lt;= rect.right &amp;&amp; y &gt;= rect.top &amp;&amp; y &lt;= rect.bottom) {
                    return true;
                }
            }

            return false;
        }

        function rectCenter(rect) {
            return {
                x: rect.centerX,
                y: rect.centerY
            };
        }

        function getTargetTopBelowObstacle(sourcePoint, targetPoint, targetElement, obstacleRect, pathRect) {
            const gap = LAYOUT.boxGap;
            const obstaclePathRect = pathRect || obstacleRect;
            const fallbackTop = obstaclePathRect.bottom + gap;
            const dx = targetPoint.x - sourcePoint.x;

            if (Math.abs(dx) &lt; 1) {
                return fallbackTop;
            }

            const segmentLeft = Math.min(sourcePoint.x, targetPoint.x);
            const segmentRight = Math.max(sourcePoint.x, targetPoint.x);
            const overlapLeft = Math.max(segmentLeft, obstaclePathRect.left);
            const overlapRight = Math.min(segmentRight, obstaclePathRect.right);

            if (overlapLeft &gt; overlapRight) {
                return fallbackTop;
            }

            const ratios = [overlapLeft, overlapRight].map(function (x) {
                return (x - sourcePoint.x) &#x2F; dx;
            }).filter(function (ratio) {
                return ratio &gt; 0 &amp;&amp; ratio &lt; 1;
            });

            if (!ratios.length) {
                return fallbackTop;
            }

            const closestObstacleRatio = Math.min.apply(null, ratios);
            const requiredCenterY = sourcePoint.y
                + (obstaclePathRect.bottom + gap - sourcePoint.y) &#x2F; closestObstacleRatio;

            return Math.max(fallbackTop, requiredCenterY - targetElement.offsetHeight &#x2F; 2);
        }

        function moveElementBelow(element, obstacleRect, sourcePoint, targetPoint, pathRect) {
            const top = parseFloat(element.style.top) || 0;
            const targetTop = getTargetTopBelowObstacle(sourcePoint, targetPoint, element, obstacleRect, pathRect);

            if (top &lt; targetTop) {
                setNodePosition(element, parseFloat(element.style.left) || 0, targetTop);
                return true;
            }

            return false;
        }

        function resolveColumnVerticalOverlaps(columns) {
            columns.forEach(function (column) {
                let bottom = LAYOUT.outerGap;

                column.forEach(function (element) {
                    const top = parseFloat(element.style.top) || 0;

                    if (top &lt; bottom) {
                        setNodePosition(element, parseFloat(element.style.left) || 0, bottom);
                    }

                    bottom = (parseFloat(element.style.top) || 0) + element.offsetHeight + LAYOUT.boxGap;
                });
            });
        }

        function avoidAlternativePathCollisions(state, frameElements, objectElements, objectColumns) {
            const referencesByObject = buildObjectReferenceMap(state);
            const objectIds = state.objects.map(function (object) {
                return object.id;
            });
            const edges = [];

            state.frames.forEach(function (frame, frameIndex) {
                const reachable = collectReachableObjects(frame.bindings.map(function (binding) {
                    return binding.target;
                }), referencesByObject);

                frame.bindings.forEach(function (binding) {
                    edges.push({
                        sourceElement: frameElements[frameIndex],
                        sourceId: &quot;&quot;,
                        sourceReachable: reachable,
                        targetId: binding.target
                    });
                });
            });

            state.objects.forEach(function (object) {
                const reachable = collectReachableObjects([object.id], referencesByObject);

                object.collectionItems.forEach(function (reference) {
                    edges.push({
                        sourceElement: objectElements.get(object.id),
                        sourceId: object.id,
                        sourceReachable: reachable,
                        targetId: reference.target
                    });
                });
            });

            for (let pass = 0; pass &lt; state.objects.length; pass += 1) {
                let changed = false;

                edges.forEach(function (edge) {
                    const targetElement = objectElements.get(edge.targetId);

                    if (!edge.sourceElement || !targetElement) {
                        return;
                    }

                    const sourceRect = rectInStage(edge.sourceElement);
                    const targetRect = rectInStage(targetElement);
                    const sourcePoint = rectCenter(sourceRect);
                    const targetPoint = rectCenter(targetRect);

                    objectIds.forEach(function (obstacleId) {
                        if (obstacleId === edge.sourceId || obstacleId === edge.targetId) {
                            return;
                        }

                        if (!edge.sourceReachable.has(obstacleId)) {
                            return;
                        }

                        if (!objectReachesTarget(referencesByObject, obstacleId, edge.targetId, new Set())) {
                            return;
                        }

                        const obstacleElement = objectElements.get(obstacleId);

                        if (!obstacleElement) {
                            return;
                        }

                        const obstacleRect = rectInStage(obstacleElement);
                        const pathRect = inflateRect(obstacleRect, LAYOUT.alternativePathClearance);

                        if (segmentIntersectsRect(sourcePoint, targetPoint, pathRect)) {
                            changed = moveElementBelow(targetElement, obstacleRect, sourcePoint, targetPoint, pathRect) || changed;
                        }
                    });
                });

                if (changed) {
                    resolveColumnVerticalOverlaps(objectColumns);
                } else {
                    break;
                }
            }
        }

        function positionDefault(state, frameElements, objectElements) {
            const objectLevels = computeObjectLevels(state);
            const columns = [frameElements].concat(
                buildOrderedObjectColumns(state, objectElements, objectLevels)
            );
            const columnWidths = columns.map(function (column) {
                if (!column || !column.length) {
                    return 0;
                }

                return column.reduce(function (width, element) {
                    return Math.max(width, element.offsetWidth);
                }, 0);
            });
            const columnHeights = columns.map(function (column) {
                if (!column || !column.length) {
                    return 0;
                }

                return column.reduce(function (height, element) {
                    return height + element.offsetHeight;
                }, 0) + LAYOUT.boxGap * (column.length - 1);
            });
            const objectColumns = columns.slice(1);
            const objectColumnWidths = columnWidths.slice(1);
            const regularColumnGap = Math.max(44, Math.round(getStageWidth() * 0.07));
            const objectGaps = objectColumns.slice(0, -1).map(function () {
                return regularColumnGap;
            });
            const objectContentWidth = objectColumnWidths.reduce(function (sum, width) {
                return sum + width;
            }, 0) + objectGaps.reduce(function (sum, gap) {
                return sum + gap;
            }, 0);
            const contentHeight = columnHeights.reduce(function (height, columnHeight) {
                return Math.max(height, columnHeight);
            }, 0);
            const frameContentWidth = columnWidths[0] || 0;
            const requiredUsableWidth = Math.max(
                frameContentWidth * 3,
                objectContentWidth * 1.5
            );
            const stageWidth = Math.max(
                getStageWidth(),
                Math.ceil(requiredUsableWidth + LAYOUT.outerGap * 2)
            );
            const stageHeight = Math.max(LAYOUT.minHeight, contentHeight + LAYOUT.outerGap * 2);
            const usableWidth = stageWidth - LAYOUT.outerGap * 2;
            const frameAreaLeft = LAYOUT.outerGap;
            const frameAreaWidth = usableWidth &#x2F; 3;
            const objectAreaLeft = frameAreaLeft + frameAreaWidth;
            const objectAreaWidth = usableWidth * 2 &#x2F; 3;
            const yStart = Math.max(LAYOUT.outerGap, Math.round((stageHeight - contentHeight) &#x2F; 2));

            updateStageWidth(stageWidth);
            stage.style.height = stageHeight + &quot;px&quot;;

            if (columns[0] &amp;&amp; columns[0].length) {
                let frameY = yStart;
                const frameX = Math.round(
                    frameAreaLeft + Math.max(0, (frameAreaWidth - frameContentWidth) &#x2F; 2)
                );

                columns[0].forEach(function (element) {
                    setNodePosition(element, frameX, frameY);
                    frameY += element.offsetHeight + LAYOUT.boxGap;
                });
            }

            let objectX = Math.round(
                objectAreaLeft + Math.max(0, (objectAreaWidth - objectContentWidth) &#x2F; 2)
            );

            objectColumns.forEach(function (column, index) {
                if (!column || !column.length) {
                    return;
                }

                let y = yStart;
                column.forEach(function (element) {
                    setNodePosition(element, objectX, y);
                    y += element.offsetHeight + LAYOUT.boxGap;
                });

                objectX += objectColumnWidths[index] + (objectGaps[index] || 0);
            });

            avoidAlternativePathCollisions(state, frameElements, objectElements, objectColumns);
        }

        function updateStageHeight(nodeElements) {
            let bottom = isFullscreen()
                ? Math.max(LAYOUT.minHeight, viewport.clientHeight)
                : LAYOUT.minHeight;

            nodeElements.forEach(function (element) {
                const rect = rectInStage(element);
                bottom = Math.max(bottom, rect.bottom + LAYOUT.outerGap);
            });

            stage.style.height = bottom + &quot;px&quot;;
            arrowsSvg.setAttribute(&quot;viewBox&quot;, &quot;0 0 &quot; + stage.offsetWidth + &quot; &quot; + bottom);
        }

        function updateStageWidthToFit(nodeElements) {
            let right = LAYOUT.minWidth;

            nodeElements.forEach(function (element) {
                const rect = rectInStage(element);
                right = Math.max(right, rect.right + LAYOUT.outerGap);
            });

            updateStageWidth(right);
        }

        function sideNormal(side) {
            if (side === &quot;left&quot;) {
                return { x: -1, y: 0 };
            }

            if (side === &quot;right&quot;) {
                return { x: 1, y: 0 };
            }

            if (side === &quot;top&quot;) {
                return { x: 0, y: -1 };
            }

            return { x: 0, y: 1 };
        }

        function chooseConnectionSide(fromRect, toRect) {
            if (toRect.right &lt; fromRect.left) {
                return &quot;left&quot;;
            }

            if (toRect.left &gt; fromRect.right) {
                return &quot;right&quot;;
            }

            if (toRect.bottom &lt; fromRect.top) {
                return &quot;top&quot;;
            }

            if (toRect.top &gt; fromRect.bottom) {
                return &quot;bottom&quot;;
            }

            const dx = toRect.centerX - fromRect.centerX;
            const dy = toRect.centerY - fromRect.centerY;

            if (Math.abs(dx) &gt;= Math.abs(dy)) {
                return dx &gt;= 0 ? &quot;right&quot; : &quot;left&quot;;
            }

            return dy &gt;= 0 ? &quot;bottom&quot; : &quot;top&quot;;
        }

        function chooseReferenceSide(sourceRect, targetRect) {
            return targetRect.centerX &lt; sourceRect.centerX ? &quot;left&quot; : &quot;right&quot;;
        }

        function chooseReferenceSourceSide(reference, sourceRect, targetRect) {
            if (reference.classList.contains(&quot;python-state-shortcode__reference--collection-item&quot;)) {
                return chooseConnectionSide(rectInStage(reference), targetRect);
            }

            return chooseReferenceSide(sourceRect, targetRect);
        }

        function borderPointForSide(rect, side, anchor) {
            const horizontalPadding = Math.min(LAYOUT.cornerGap, rect.width &#x2F; 2);
            const verticalPadding = Math.min(LAYOUT.cornerGap, rect.height &#x2F; 2);

            if (side === &quot;left&quot; || side === &quot;right&quot;) {
                return {
                    x: side === &quot;left&quot; ? rect.left : rect.right,
                    y: clamp(anchor.y, rect.top + verticalPadding, rect.bottom - verticalPadding),
                    side: side
                };
            }

            return {
                x: clamp(anchor.x, rect.left + horizontalPadding, rect.right - horizontalPadding),
                y: side === &quot;top&quot; ? rect.top : rect.bottom,
                side: side
            };
        }

        function outsidePoint(point, amount) {
            const normal = sideNormal(point.side);

            return {
                x: point.x + normal.x * amount,
                y: point.y + normal.y * amount,
                side: point.side
            };
        }

        function pointFromPort(portRect, side) {
            if (side === &quot;bottom&quot;) {
                return {
                    x: portRect.centerX,
                    y: portRect.centerY,
                    side: side
                };
            }

            if (side === &quot;top&quot;) {
                return {
                    x: portRect.centerX,
                    y: portRect.centerY,
                    side: side
                };
            }

            if (side === &quot;left&quot;) {
                return {
                    x: portRect.left,
                    y: portRect.centerY,
                    side: side
                };
            }

            return {
                x: portRect.right,
                y: portRect.centerY,
                side: side
            };
        }

        function createArrowGeometry(start, end, arrowheadLength) {
            const dx = end.x - start.x;
            const dy = end.y - start.y;
            const distance = Math.max(70, Math.sqrt(dx * dx + dy * dy));
            const bend = Math.min(90, distance * 0.32);
            const startNormal = sideNormal(start.side);
            const endNormal = sideNormal(end.side);
            const lineEnd = {
                x: end.x + endNormal.x * arrowheadLength,
                y: end.y + endNormal.y * arrowheadLength,
                side: end.side
            };
            const control1 = {
                x: start.x + startNormal.x * bend,
                y: start.y + startNormal.y * bend
            };
            const control2 = {
                x: end.x + endNormal.x * bend,
                y: end.y + endNormal.y * bend
            };

            return {
                path: [
                    &quot;M&quot;, start.x, start.y,
                    &quot;C&quot;, control1.x, control1.y, control2.x, control2.y, lineEnd.x, lineEnd.y
                ].join(&quot; &quot;),
                control2: control2
            };
        }

        function createArrowheadPoints(end, controlPoint, length, width) {
            const dx = end.x - controlPoint.x;
            const dy = end.y - controlPoint.y;
            const distance = Math.sqrt(dx * dx + dy * dy) || 1;
            const ux = dx &#x2F; distance;
            const uy = dy &#x2F; distance;
            const nx = -uy;
            const ny = ux;
            const baseX = end.x - ux * length;
            const baseY = end.y - uy * length;

            return [
                [end.x, end.y],
                [baseX + nx * width, baseY + ny * width],
                [baseX - nx * width, baseY - ny * width]
            ].map(function (point) {
                return point[0] + &quot;,&quot; + point[1];
            }).join(&quot; &quot;);
        }

        function svgElement(tagName, className) {
            const element = document.createElementNS(&quot;http:&#x2F;&#x2F;www.w3.org&#x2F;2000&#x2F;svg&quot;, tagName);
            element.classList.add(className);
            return element;
        }

        function createEdgePath(className, pathData) {
            const path = svgElement(&quot;path&quot;, className);

            path.setAttribute(&quot;d&quot;, pathData);

            return path;
        }

        function createEdgeArrowhead(className, points) {
            const arrowhead = svgElement(&quot;polygon&quot;, className);

            arrowhead.setAttribute(&quot;points&quot;, points);

            return arrowhead;
        }

        function createEdgeCircle(className, point, radius) {
            const circle = svgElement(&quot;circle&quot;, className);

            circle.setAttribute(&quot;cx&quot;, point.x);
            circle.setAttribute(&quot;cy&quot;, point.y);
            circle.setAttribute(&quot;r&quot;, radius);

            return circle;
        }

        function setEdgeActive(edgeId, active) {
            const edge = arrowsSvg.querySelector(&quot;[data-python-state-edge-id=\&quot;&quot; + edgeId + &quot;\&quot;]&quot;);

            if (edge) {
                edge.classList.toggle(&quot;python-state-shortcode__edge--active&quot;, active);
            }
        }

        function drawArrows(objectElements) {
            arrowsSvg.querySelectorAll(&quot;.python-state-shortcode__edge&quot;).forEach(function (edge) {
                edge.remove();
            });

            root.querySelectorAll(&quot;[data-python-state-target]&quot;).forEach(function (reference, index) {
                const target = reference.dataset.pythonStateTarget;
                const object = objectElements.get(target);

                if (!target || !object) {
                    return;
                }

                const port = reference.querySelector(&quot;.python-state-shortcode__reference-port&quot;);
                const sourceBox = reference.closest(&quot;.python-state-shortcode__box&quot;);
                const sourceRect = rectInStage(sourceBox);
                const objectRect = rectInStage(object);
                const sourceSide = chooseReferenceSourceSide(reference, sourceRect, objectRect);

                reference.dataset.pythonStateReferenceSide = sourceSide;

                const portRect = rectInStage(port || reference);
                const portAnchor = { x: portRect.centerX, y: portRect.centerY };
                const targetSide = chooseConnectionSide(objectRect, portRect);
                const targetBorder = borderPointForSide(objectRect, targetSide, portAnchor);
                const visualPortPoint = rectCenter(portRect);
                const linePortPoint = pointFromPort(portRect, sourceSide);
                const start = outsidePoint(linePortPoint, LAYOUT.arrowGap);
                const end = outsidePoint(targetBorder, LAYOUT.arrowGap);
                const arrowheadLength = 8;
                const arrowGeometry = createArrowGeometry(start, end, arrowheadLength);
                const pathData = arrowGeometry.path;
                const arrowheadPoints = createArrowheadPoints(end, arrowGeometry.control2, arrowheadLength, 4);
                const edge = svgElement(&quot;g&quot;, &quot;python-state-shortcode__edge&quot;);
                const edgeId = &quot;edge-&quot; + index;

                edge.setAttribute(&quot;aria-hidden&quot;, &quot;true&quot;);
                edge.dataset.pythonStateEdgeId = edgeId;
                reference.dataset.pythonStateEdgeId = edgeId;
                reference.onmouseenter = function () {
                    setEdgeActive(edgeId, true);
                };
                reference.onmouseleave = function () {
                    setEdgeActive(edgeId, false);
                };
                edge.addEventListener(&quot;mouseenter&quot;, function () {
                    setEdgeActive(edgeId, true);
                });
                edge.addEventListener(&quot;mouseleave&quot;, function () {
                    setEdgeActive(edgeId, false);
                });
                edge.appendChild(createEdgePath(&quot;python-state-shortcode__edge-hit&quot;, pathData));
                edge.appendChild(createEdgePath(&quot;python-state-shortcode__edge-halo&quot;, pathData));
                edge.appendChild(createEdgePath(&quot;python-state-shortcode__edge-line&quot;, pathData));
                edge.appendChild(createEdgeArrowhead(&quot;python-state-shortcode__edge-arrowhead-halo&quot;, arrowheadPoints));
                edge.appendChild(createEdgeArrowhead(&quot;python-state-shortcode__edge-arrowhead&quot;, arrowheadPoints));
                edge.appendChild(createEdgeCircle(&quot;python-state-shortcode__edge-port-halo&quot;, visualPortPoint, 5.2));
                edge.appendChild(createEdgeCircle(&quot;python-state-shortcode__edge-port&quot;, visualPortPoint, 4.2));
                arrowsSvg.appendChild(edge);
            });
        }

        function isFullscreen() {
            return root.classList.contains(&quot;python-state-shortcode--fullscreen&quot;);
        }

        function isTransparentColor(value) {
            return !value || value === &quot;transparent&quot; || value === &quot;rgba(0, 0, 0, 0)&quot;;
        }

        function getPageBackgroundColor() {
            const candidates = [document.body, document.documentElement, root.parentElement].filter(Boolean);

            for (let index = 0; index &lt; candidates.length; index += 1) {
                const background = window.getComputedStyle(candidates[index]).backgroundColor;

                if (!isTransparentColor(background)) {
                    return background;
                }
            }

            return &quot;transparent&quot;;
        }

        function updateFullpageMetrics() {
            const nav = document.querySelector(&quot;nav&quot;);
            const navRect = nav ? nav.getBoundingClientRect() : null;
            const top = navRect ? Math.max(0, Math.round(navRect.bottom)) : 0;

            root.style.setProperty(&quot;--python-state-fullpage-top&quot;, top + &quot;px&quot;);
            root.style.setProperty(&quot;--python-state-fullpage-bg&quot;, getPageBackgroundColor());
        }

        function setPageScrollLocked(locked) {
            if (locked) {
                savedDocumentOverflow = document.documentElement.style.overflow;
                savedBodyOverflow = document.body.style.overflow;
                document.documentElement.style.overflow = &quot;hidden&quot;;
                document.body.style.overflow = &quot;hidden&quot;;
                return;
            }

            document.documentElement.style.overflow = savedDocumentOverflow;
            document.body.style.overflow = savedBodyOverflow;
        }

        function refreshGeometry(allNodeElements, objectElements) {
            updateStageHeight(allNodeElements);
            drawArrows(objectElements);
        }

        function setFullscreenMode(enabled, allNodeElements, objectElements) {
            if (enabled === isFullscreen()) {
                return;
            }

            const preTogglePositions = captureNodeViewportPositions(allNodeElements);

            if (enabled) {
                updateFullpageMetrics();
            }

            root.classList.toggle(&quot;python-state-shortcode--fullscreen&quot;, enabled);
            fullscreenButton.setAttribute(&quot;aria-pressed&quot;, enabled ? &quot;true&quot; : &quot;false&quot;);
            fullscreenButton.textContent = enabled ? &quot;Exit&quot; : &quot;Full page&quot;;
            setPageScrollLocked(enabled);

            window.requestAnimationFrame(function () {
                if (enabled) {
                    applyNodeViewportPositions(preTogglePositions);
                    updateStageWidthToFit(allNodeElements);
                } else {
                    resetStageWidthToViewport();
                    applyNodeViewportPositions(preTogglePositions);
                    allNodeElements.forEach(fitNodeIntoCompactStage);
                }

                refreshGeometry(allNodeElements, objectElements);
            });
        }

        function makeDraggable(element, objectElements) {
            let offsetX = 0;
            let offsetY = 0;
            let dragging = false;

            element.addEventListener(&quot;pointerdown&quot;, function (event) {
                dragging = true;
                element.classList.add(&quot;python-state-shortcode__box--dragging&quot;);
                element.setPointerCapture(event.pointerId);

                const rect = element.getBoundingClientRect();
                offsetX = event.clientX - rect.left;
                offsetY = event.clientY - rect.top;
            });

            element.addEventListener(&quot;pointermove&quot;, function (event) {
                if (!dragging) {
                    return;
                }

                const stageRect = stage.getBoundingClientRect();
                const maxX = Math.max(0, stage.clientWidth - element.offsetWidth);
                const maxY = Math.max(0, stage.clientHeight - element.offsetHeight);
                const x = clamp(event.clientX - stageRect.left - offsetX, 0, maxX);
                const y = clamp(event.clientY - stageRect.top - offsetY, 0, maxY);

                setNodePosition(element, x, y);
                drawArrows(objectElements);
            });

            function stopDragging() {
                dragging = false;
                element.classList.remove(&quot;python-state-shortcode__box--dragging&quot;);
            }

            element.addEventListener(&quot;pointerup&quot;, stopDragging);
            element.addEventListener(&quot;pointercancel&quot;, stopDragging);
        }

        function render(state) {
            const frameElements = state.frames.map(function (frame) {
                const frameElement = renderFrame(frame);
                nodes.appendChild(frameElement);
                return frameElement;
            });
            const objectElements = new Map();

            state.objects.forEach(function (object) {
                const objectElement = renderObject(object);
                objectElements.set(object.id, objectElement);
                nodes.appendChild(objectElement);
            });

            const allNodeElements = frameElements.concat(Array.from(objectElements.values()));

            positionDefault(state, frameElements, objectElements);
            updateStageHeight(allNodeElements);
            drawArrows(objectElements);

            allNodeElements.forEach(function (element) {
                makeDraggable(element, objectElements);
            });

            fullscreenButton.addEventListener(&quot;click&quot;, function () {
                setFullscreenMode(!isFullscreen(), allNodeElements, objectElements);
            });

            document.addEventListener(&quot;keydown&quot;, function (event) {
                if (event.key === &quot;Escape&quot; &amp;&amp; isFullscreen()) {
                    setFullscreenMode(false, allNodeElements, objectElements);
                }
            });

            window.addEventListener(&quot;resize&quot;, function () {
                if (isFullscreen()) {
                    updateFullpageMetrics();
                    updateStageWidthToFit(allNodeElements);
                } else {
                    resetStageWidthToViewport();
                }

                allNodeElements.forEach(clampNodePosition);
                refreshGeometry(allNodeElements, objectElements);
            });
        }

        const parsed = parseState();

        if (parsed.error) {
            renderError(parsed.error);
        } else {
            render(parsed.state);
        }
    }());
    &lt;&#x2F;script&gt;
&lt;&#x2F;div&gt;
&lt;p&gt;И после этого с помощью функции print выведите в консоль все объекты которые вы сделали.&lt;&#x2F;p&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>Python, FastAPI, 5.</title>
        <published>2026-05-28T00:00:00+00:00</published>
        <updated>2026-05-28T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Unknown
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://mymaterials.ru/lessons/python-fastapi-5/"/>
        <id>https://mymaterials.ru/lessons/python-fastapi-5/</id>
        
        <content type="html" xml:base="https://mymaterials.ru/lessons/python-fastapi-5/">&lt;h2 id=&quot;poleznye-materialy-na-russkom&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#poleznye-materialy-na-russkom&quot; aria-label=&quot;Anchor link for: poleznye-materialy-na-russkom&quot;&gt;Полезные материалы (на русском)&lt;&#x2F;a&gt;&lt;&#x2F;h2&gt;
&lt;ol&gt;
&lt;li&gt;Официальная документация React, &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;ru.react.dev&#x2F;learn&#x2F;synchronizing-with-effects&quot;&gt;статья про Эффекты (useEffect)&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;&#x2F;ol&gt;
&lt;h2 id=&quot;kod-s-zaniatiia&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#kod-s-zaniatiia&quot; aria-label=&quot;Anchor link for: kod-s-zaniatiia&quot;&gt;Код с занятия&lt;&#x2F;a&gt;&lt;&#x2F;h2&gt;
&lt;pre class=&quot;giallo z-code&quot;&gt;&lt;code data-lang=&quot;jsx&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-keyword&quot;&gt;import&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-other&quot;&gt; React&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span&gt; {&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-other&quot;&gt; useState&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-other&quot;&gt; useEffect&lt;&#x2F;span&gt;&lt;span&gt; }&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; from&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt; &amp;#39;&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;react&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;&amp;#39;&lt;&#x2F;span&gt;&lt;span&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-keyword&quot;&gt;import&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt; &amp;#39;&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;.&#x2F;App.css&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;&amp;#39;&lt;&#x2F;span&gt;&lt;span&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-storage z-type&quot;&gt;function&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt; App&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;span&gt; {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-storage z-type&quot;&gt;  let&lt;&#x2F;span&gt;&lt;span&gt; [&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-other&quot;&gt;notes&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-other&quot;&gt; setNotes&lt;&#x2F;span&gt;&lt;span&gt;]&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; =&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt; useState&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span&gt;[&lt;&#x2F;span&gt;&lt;span&gt;]&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;span&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-storage z-type&quot;&gt;  let&lt;&#x2F;span&gt;&lt;span&gt; [&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-other&quot;&gt;text&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-other&quot;&gt; setText&lt;&#x2F;span&gt;&lt;span&gt;]&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; =&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt; useState&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;span&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-storage&quot;&gt;  async&lt;&#x2F;span&gt;&lt;span class=&quot;z-storage z-type&quot;&gt; function&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt; addNote&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable&quot;&gt;e&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;span&gt; {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-variable z-other&quot;&gt;    e&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;preventDefault&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;span&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-keyword&quot;&gt;    if&lt;&#x2F;span&gt;&lt;span&gt; (&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;!&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-other&quot;&gt;text&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;trim&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;span&gt; {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-keyword&quot;&gt;      return&lt;&#x2F;span&gt;&lt;span&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    }&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-keyword&quot;&gt;    await&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt; fetch&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;      &amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;http:&#x2F;&#x2F;127.0.0.1:8000&#x2F;notes&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;      {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;        method&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt; &amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;POST&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;        body&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-other z-constant&quot;&gt; JSON&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;stringify&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span&gt;{&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt; &amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;text&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-other&quot;&gt; text&lt;&#x2F;span&gt;&lt;span&gt; }&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;        headers&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;span&gt; {&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt; &amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;Content-Type&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt; &amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;application&#x2F;json&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt; }&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;      }&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    )&lt;&#x2F;span&gt;&lt;span&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-keyword&quot;&gt;    await&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt; syncNotes&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;span&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;    setText&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;span&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  }&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-storage&quot;&gt;  async&lt;&#x2F;span&gt;&lt;span class=&quot;z-storage z-type&quot;&gt; function&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt; deleteNote&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable&quot;&gt;note_id&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;span&gt; {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-keyword&quot;&gt;    await&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt; fetch&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;      &amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;http:&#x2F;&#x2F;127.0.0.1:8000&#x2F;notes&#x2F;&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; +&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-other&quot;&gt; note_id&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;      {&lt;&#x2F;span&gt;&lt;span&gt; method&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt; &amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;DELETE&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt; }&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    )&lt;&#x2F;span&gt;&lt;span&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-keyword&quot;&gt;    await&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt; syncNotes&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;span&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  }&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-storage&quot;&gt;  async&lt;&#x2F;span&gt;&lt;span class=&quot;z-storage z-type&quot;&gt; function&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt; syncNotes&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;span&gt; {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-storage z-type&quot;&gt;    let&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-other&quot;&gt; response&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; =&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; await&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt; fetch&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;      &amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;http:&#x2F;&#x2F;127.0.0.1:8000&#x2F;notes&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;      {&lt;&#x2F;span&gt;&lt;span&gt; method&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt; &amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;GET&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt; }&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    )&lt;&#x2F;span&gt;&lt;span&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-storage z-type&quot;&gt;    let&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-other&quot;&gt; data&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; =&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; await&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-other&quot;&gt; response&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;json&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;span&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;    setNotes&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-other&quot;&gt;data&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;span&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  }&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;  useEffect&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;span class=&quot;z-storage z-type&quot;&gt; =&amp;gt;&lt;&#x2F;span&gt;&lt;span&gt; {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;    syncNotes&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;span&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  }&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span&gt; [&lt;&#x2F;span&gt;&lt;span&gt;]&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;span&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-keyword&quot;&gt;  return&lt;&#x2F;span&gt;&lt;span&gt; (&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    &amp;lt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-tag&quot;&gt;div&lt;&#x2F;span&gt;&lt;span&gt;&amp;gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;      &amp;lt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-tag&quot;&gt;ul&lt;&#x2F;span&gt;&lt;span&gt;&amp;gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;        {&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-other&quot;&gt;notes&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;map&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable&quot;&gt;note&lt;&#x2F;span&gt;&lt;span class=&quot;z-storage z-type&quot;&gt; =&amp;gt;&lt;&#x2F;span&gt;&lt;span&gt; {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-keyword&quot;&gt;          return&lt;&#x2F;span&gt;&lt;span&gt; (&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;            &amp;lt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-tag&quot;&gt;li&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity&quot;&gt; key&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;=&lt;&#x2F;span&gt;&lt;span&gt;{&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-other&quot;&gt;note&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-other&quot;&gt;id&lt;&#x2F;span&gt;&lt;span&gt;}&lt;&#x2F;span&gt;&lt;span&gt;&amp;gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;              {&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-other&quot;&gt;note&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-other&quot;&gt;text&lt;&#x2F;span&gt;&lt;span&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;              &amp;lt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-tag&quot;&gt;button&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity&quot;&gt; onClick&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;=&lt;&#x2F;span&gt;&lt;span&gt;{&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;span class=&quot;z-storage z-type&quot;&gt; =&amp;gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt; deleteNote&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-other&quot;&gt;note&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-other&quot;&gt;id&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;span&gt;}&lt;&#x2F;span&gt;&lt;span&gt;&amp;gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;                Delete&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;              &amp;lt;&#x2F;&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-tag&quot;&gt;button&lt;&#x2F;span&gt;&lt;span&gt;&amp;gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;            &amp;lt;&#x2F;&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-tag&quot;&gt;li&lt;&#x2F;span&gt;&lt;span&gt;&amp;gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;          )&lt;&#x2F;span&gt;&lt;span&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;        }&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;span&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;      &amp;lt;&#x2F;&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-tag&quot;&gt;ul&lt;&#x2F;span&gt;&lt;span&gt;&amp;gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;      &amp;lt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-tag&quot;&gt;form&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity&quot;&gt; onSubmit&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;=&lt;&#x2F;span&gt;&lt;span&gt;{&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-other&quot;&gt;addNote&lt;&#x2F;span&gt;&lt;span&gt;}&lt;&#x2F;span&gt;&lt;span&gt;&amp;gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;        &amp;lt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-tag&quot;&gt;input&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-entity&quot;&gt;          value&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;=&lt;&#x2F;span&gt;&lt;span&gt;{&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-other&quot;&gt;text&lt;&#x2F;span&gt;&lt;span&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-entity&quot;&gt;          onChange&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;=&lt;&#x2F;span&gt;&lt;span&gt;{&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable&quot;&gt;e&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;span class=&quot;z-storage z-type&quot;&gt; =&amp;gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt; setText&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-other&quot;&gt;e&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-other&quot;&gt;target&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-other&quot;&gt;value&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;span&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-entity&quot;&gt;          placeholder&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;=&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;Введите текст...&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;        &#x2F;&amp;gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;        &amp;lt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-tag&quot;&gt;button&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity&quot;&gt; type&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;=&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;submit&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;&amp;gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;          Add Note&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;        &amp;lt;&#x2F;&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-tag&quot;&gt;button&lt;&#x2F;span&gt;&lt;span&gt;&amp;gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;      &amp;lt;&#x2F;&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-tag&quot;&gt;form&lt;&#x2F;span&gt;&lt;span&gt;&amp;gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    &amp;lt;&#x2F;&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-tag&quot;&gt;div&lt;&#x2F;span&gt;&lt;span&gt;&amp;gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  )&lt;&#x2F;span&gt;&lt;span&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-keyword&quot;&gt;export&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; default&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-other&quot;&gt; App&lt;&#x2F;span&gt;&lt;span&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;&lt;pre class=&quot;giallo z-code&quot;&gt;&lt;code data-lang=&quot;python&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-keyword&quot;&gt;from&lt;&#x2F;span&gt;&lt;span&gt; uvicorn&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; import&lt;&#x2F;span&gt;&lt;span&gt; run&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-keyword&quot;&gt;from&lt;&#x2F;span&gt;&lt;span&gt; fastapi&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; import&lt;&#x2F;span&gt;&lt;span&gt; FastAPI&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span&gt; HTTPException&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span&gt; Response&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-keyword&quot;&gt;from&lt;&#x2F;span&gt;&lt;span&gt; fastapi&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span&gt;middleware&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span&gt;cors&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; import&lt;&#x2F;span&gt;&lt;span&gt; CORSMiddleware&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-keyword&quot;&gt;from&lt;&#x2F;span&gt;&lt;span&gt; pydantic&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; import&lt;&#x2F;span&gt;&lt;span&gt; BaseModel&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;app&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; =&lt;&#x2F;span&gt;&lt;span&gt; FastAPI&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-comment&quot;&gt;#&lt;&#x2F;span&gt;&lt;span class=&quot;z-comment&quot;&gt; Разрешаем frontend-приложению отправлять запросы на backend.  Потом скажу про это подробнее.&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;app&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span&gt;add_middleware&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    CORSMiddleware&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-variable&quot;&gt;    allow_origins&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;=&lt;&#x2F;span&gt;&lt;span&gt;[&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;        &amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;http:&#x2F;&#x2F;localhost:5173&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;        &amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;http:&#x2F;&#x2F;127.0.0.1:5173&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    ]&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-variable&quot;&gt;    allow_methods&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;=&lt;&#x2F;span&gt;&lt;span&gt;[&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;*&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;]&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-variable&quot;&gt;    allow_headers&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;=&lt;&#x2F;span&gt;&lt;span&gt;[&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;*&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;]&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-constant&quot;&gt;NOTES&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; =&lt;&#x2F;span&gt;&lt;span&gt; [&lt;&#x2F;span&gt;&lt;span&gt;]&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-constant&quot;&gt;NEXT_NOTE_ID&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; =&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt; 1&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-storage z-type&quot;&gt;class&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt; CreateNote&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity&quot;&gt;BaseModel&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    text&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;span class=&quot;z-support&quot;&gt; str&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;@&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;app&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;.&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;get&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;&#x2F;notes&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-storage z-type&quot;&gt;def&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt; get_notes&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-keyword&quot;&gt;    return&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt; NOTES&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;@&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;app&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;.&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;post&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;&#x2F;notes&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-storage z-type&quot;&gt;def&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt; create_note&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-parameter z-function&quot;&gt;note&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;span&gt; CreateNote&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-storage&quot;&gt;    global&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt; NEXT_NOTE_ID&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    new_note&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; =&lt;&#x2F;span&gt;&lt;span&gt; {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;        &amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;id&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt; NEXT_NOTE_ID&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;        &amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;text&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;span&gt; note&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span&gt;text&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    }&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-constant&quot;&gt;    NOTES&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span&gt;append&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span&gt;new_note&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-constant&quot;&gt;    NEXT_NOTE_ID&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; +=&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt; 1&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-keyword&quot;&gt;    return&lt;&#x2F;span&gt;&lt;span&gt; new_note&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;@&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;app&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;.&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;delete&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;&#x2F;notes&#x2F;&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt;{note_id}&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-storage z-type&quot;&gt;def&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt; delete_note&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-parameter z-function&quot;&gt;note_id&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;span class=&quot;z-support&quot;&gt; int&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-keyword&quot;&gt;    for&lt;&#x2F;span&gt;&lt;span&gt; note&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; in&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt; NOTES&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-keyword&quot;&gt;        if&lt;&#x2F;span&gt;&lt;span&gt; note&lt;&#x2F;span&gt;&lt;span&gt;[&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;id&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;]&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; ==&lt;&#x2F;span&gt;&lt;span&gt; note_id&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-constant&quot;&gt;            NOTES&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span&gt;remove&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span&gt;note&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-keyword&quot;&gt;            return&lt;&#x2F;span&gt;&lt;span&gt; Response&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable&quot;&gt;status_code&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;=&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt;204&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-keyword&quot;&gt;    raise&lt;&#x2F;span&gt;&lt;span&gt; HTTPException&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable&quot;&gt;status_code&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;=&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt;404&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable&quot;&gt; detail&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;=&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;Note not found&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;run&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span&gt;app&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;&lt;h2 id=&quot;domashnee-zadanie&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#domashnee-zadanie&quot; aria-label=&quot;Anchor link for: domashnee-zadanie&quot;&gt;Домашнее задание&lt;&#x2F;a&gt;&lt;&#x2F;h2&gt;
&lt;p&gt;Два варианта на выбор:&lt;&#x2F;p&gt;
&lt;ol&gt;
&lt;li&gt;В своём проекте реализуйте от начала до конца один или несколько задуманных эндпоинтов. Нужно определить, в каком месте фронтенд должен отправлять HTTP-запрос через fetch, написать этот запрос, сделать соответствующий эндпоинт на бэкенде и проверить, что всё работает. Важно, чтобы у результата был проверяемый эффект: например, что-то создаётся, удаляется или меняется, либо можно создать что-то вручную через запрос на бэкенд, и увидеть результат на фронтенде.&lt;&#x2F;li&gt;
&lt;li&gt;Запустите дома проект, который мы делали на занятии, и убедитесь, что всё работает. Добавьте к заметкам заголовок: (1) на бэкенде у заметки должно появиться поле title, (2) на фронтенде должна появиться форма для ввода заголовка, (3) при создании заметки нужно отправлять и title, и text, (4) в списке заметок нужно отображать и заголовок, и текст.&lt;&#x2F;li&gt;
&lt;&#x2F;ol&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>Python, FastAPI, 4.</title>
        <published>2026-05-25T00:00:00+00:00</published>
        <updated>2026-05-25T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Unknown
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://mymaterials.ru/lessons/python-fastapi-4/"/>
        <id>https://mymaterials.ru/lessons/python-fastapi-4/</id>
        
        <content type="html" xml:base="https://mymaterials.ru/lessons/python-fastapi-4/">&lt;h2 id=&quot;poleznye-resursy-na-russkom&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#poleznye-resursy-na-russkom&quot; aria-label=&quot;Anchor link for: poleznye-resursy-na-russkom&quot;&gt;Полезные ресурсы (на русском)&lt;&#x2F;a&gt;&lt;&#x2F;h2&gt;
&lt;ol&gt;
&lt;li&gt;&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;developer.mozilla.org&#x2F;ru&#x2F;docs&#x2F;Web&#x2F;API&#x2F;Window&#x2F;fetch&quot;&gt;Интерфейс функции fetch&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;li&gt;&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;developer.mozilla.org&#x2F;ru&#x2F;docs&#x2F;Web&#x2F;API&#x2F;Response&quot;&gt;Подробно про объект Response (результат выполнения Fetch)&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;&#x2F;ol&gt;
&lt;h2 id=&quot;domashnee-zadanie&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#domashnee-zadanie&quot; aria-label=&quot;Anchor link for: domashnee-zadanie&quot;&gt;Домашнее задание&lt;&#x2F;a&gt;&lt;&#x2F;h2&gt;
&lt;p&gt;Ниже дан код на JS, который обращается к API, размещённому на http:&#x2F;&#x2F;127.0.0.1:8000. Вам нужно реализовать этот API на FastAPI (восстановив его интерфейс и логику из JS-кода).&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo z-code&quot;&gt;&lt;code data-lang=&quot;javascript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-comment&quot;&gt;&#x2F;&#x2F;&lt;&#x2F;span&gt;&lt;span class=&quot;z-comment&quot;&gt; 1. Сложение&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-variable z-other&quot;&gt;response&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; =&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; await&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt; fetch&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;&amp;#39;&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;http:&#x2F;&#x2F;127.0.0.1:8000&#x2F;calculate&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;&amp;#39;&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span&gt; {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  method&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt; &amp;#39;&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;POST&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;&amp;#39;&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  headers&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;span&gt; {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;    &amp;#39;&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;Content-Type&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;&amp;#39;&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt; &amp;#39;&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;application&#x2F;json&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;&amp;#39;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  }&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  body&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt; &amp;#39;&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;{&amp;quot;operation&amp;quot;: &amp;quot;add&amp;quot;, &amp;quot;a&amp;quot;: 10, &amp;quot;b&amp;quot;: 5}&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;&amp;#39;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;}&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-variable z-other&quot;&gt;json_data&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; =&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; await&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-other&quot;&gt; response&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;json&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-variable z-other&quot;&gt;console&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;log&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-other&quot;&gt;json_data&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-comment&quot;&gt; &#x2F;&#x2F;&lt;&#x2F;span&gt;&lt;span class=&quot;z-comment&quot;&gt; { result: 15 }&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-comment&quot;&gt;&#x2F;&#x2F;&lt;&#x2F;span&gt;&lt;span class=&quot;z-comment&quot;&gt; 2. Вычитание&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-variable z-other&quot;&gt;response&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; =&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; await&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt; fetch&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;&amp;#39;&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;http:&#x2F;&#x2F;127.0.0.1:8000&#x2F;calculate&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;&amp;#39;&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span&gt; {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  method&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt; &amp;#39;&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;POST&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;&amp;#39;&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  headers&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;span&gt; {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;    &amp;#39;&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;Content-Type&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;&amp;#39;&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt; &amp;#39;&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;application&#x2F;json&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;&amp;#39;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  }&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  body&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt; &amp;#39;&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;{&amp;quot;operation&amp;quot;: &amp;quot;subtract&amp;quot;, &amp;quot;a&amp;quot;: 10, &amp;quot;b&amp;quot;: 5}&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;&amp;#39;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;}&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-variable z-other&quot;&gt;json_data&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; =&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; await&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-other&quot;&gt; response&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;json&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-variable z-other&quot;&gt;console&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;log&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-other&quot;&gt;json_data&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-comment&quot;&gt; &#x2F;&#x2F;&lt;&#x2F;span&gt;&lt;span class=&quot;z-comment&quot;&gt; { result: 5 }&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-comment&quot;&gt;&#x2F;&#x2F;&lt;&#x2F;span&gt;&lt;span class=&quot;z-comment&quot;&gt; 3. Деление&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-variable z-other&quot;&gt;response&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; =&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; await&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt; fetch&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;&amp;#39;&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;http:&#x2F;&#x2F;127.0.0.1:8000&#x2F;calculate&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;&amp;#39;&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span&gt; {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  method&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt; &amp;#39;&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;POST&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;&amp;#39;&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  headers&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;span&gt; {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;    &amp;#39;&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;Content-Type&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;&amp;#39;&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt; &amp;#39;&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;application&#x2F;json&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;&amp;#39;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  }&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  body&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt; &amp;#39;&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;{&amp;quot;operation&amp;quot;: &amp;quot;divide&amp;quot;, &amp;quot;a&amp;quot;: 10, &amp;quot;b&amp;quot;: 2}&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;&amp;#39;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;}&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-variable z-other&quot;&gt;json_data&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; =&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; await&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-other&quot;&gt; response&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;json&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-variable z-other&quot;&gt;console&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;log&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-other&quot;&gt;json_data&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-comment&quot;&gt; &#x2F;&#x2F;&lt;&#x2F;span&gt;&lt;span class=&quot;z-comment&quot;&gt; { result: 5 }&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Дополнительные задания:&lt;&#x2F;p&gt;
&lt;ol&gt;
&lt;li&gt;Корректно обработайте исключения, в т.ч. некорректную операцию и деление на 0. Обратите внимание на то, чтобы в HTTP-ответе код был не успешный (200), а связанный с произошедшей ошибкой.&lt;&#x2F;li&gt;
&lt;li&gt;Добавьте возможность получить историю операций. Решите при этом, в каком виде лучше хранить одну операцию: только результатом вычисления или объектом, где есть операция, числа и результат.&lt;&#x2F;li&gt;
&lt;&#x2F;ol&gt;
&lt;p&gt;Указания:&lt;&#x2F;p&gt;
&lt;ol&gt;
&lt;li&gt;Обращайте внимание на то, какой HTTP-запрос формирует &lt;code&gt;fetch&lt;&#x2F;code&gt;, и как должен выглядеть роут (маршрут) в FastAPI, который бы корректно отлавливал такой HTTP-запрос.&lt;&#x2F;li&gt;
&lt;li&gt;Чтобы логика работы калькулятора была полностью отделена от логики работы с HTTP, можно сделать отдельно функцию calculate, принимающую операцию и два числа, и уже её использовать в роуте.&lt;&#x2F;li&gt;
&lt;li&gt;Чтобы сохранить операции до момента, когда пользователь захочет получить историю, можно использовать глобальную переменную. Например, это может быть список, в который вы добавляете информацию о каждой операции.&lt;&#x2F;li&gt;
&lt;li&gt;Обратите внимание на то, что именно выводится в console.log(json_data). API должен возвращать результат именно в таком виде, не просто числом.&lt;&#x2F;li&gt;
&lt;&#x2F;ol&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>Python, FastAPI, 3.</title>
        <published>2026-05-21T00:00:00+00:00</published>
        <updated>2026-05-21T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Unknown
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://mymaterials.ru/lessons/python-fastapi-3/"/>
        <id>https://mymaterials.ru/lessons/python-fastapi-3/</id>
        
        <content type="html" xml:base="https://mymaterials.ru/lessons/python-fastapi-3/">&lt;h2 id=&quot;poleznye-resursy-na-russkom&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#poleznye-resursy-na-russkom&quot; aria-label=&quot;Anchor link for: poleznye-resursy-na-russkom&quot;&gt;Полезные ресурсы (на русском)&lt;&#x2F;a&gt;&lt;&#x2F;h2&gt;
&lt;ol&gt;
&lt;li&gt;Курсы по Python на Stepik: &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;stepik.org&#x2F;course&#x2F;58852&#x2F;promo&quot;&gt;первый&lt;&#x2F;a&gt; и &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;stepik.org&#x2F;course&#x2F;68343&#x2F;promo&quot;&gt;второй&lt;&#x2F;a&gt;.  Сам я не проходил, но слышал, что там хорошо даётся база.&lt;&#x2F;li&gt;
&lt;&#x2F;ol&gt;
&lt;h2 id=&quot;domashnee-zadanie&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#domashnee-zadanie&quot; aria-label=&quot;Anchor link for: domashnee-zadanie&quot;&gt;Домашнее задание&lt;&#x2F;a&gt;&lt;&#x2F;h2&gt;
&lt;p&gt;Изменить FastAPI код с &lt;a href=&quot;..&#x2F;python-fastapi-2&#x2F;&quot;&gt;прошлого занятия&lt;&#x2F;a&gt; так, чтобы в нём использовались Pydantic модели.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;kod-s-zaniatiia&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#kod-s-zaniatiia&quot; aria-label=&quot;Anchor link for: kod-s-zaniatiia&quot;&gt;Код с занятия&lt;&#x2F;a&gt;&lt;&#x2F;h2&gt;
&lt;pre class=&quot;giallo z-code&quot;&gt;&lt;code data-lang=&quot;python&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-keyword&quot;&gt;from&lt;&#x2F;span&gt;&lt;span&gt; uvicorn&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; import&lt;&#x2F;span&gt;&lt;span&gt; run&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-keyword&quot;&gt;from&lt;&#x2F;span&gt;&lt;span&gt; pydantic&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; import&lt;&#x2F;span&gt;&lt;span&gt; BaseModel&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span&gt; Field&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-keyword&quot;&gt;from&lt;&#x2F;span&gt;&lt;span&gt; fastapi&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; import&lt;&#x2F;span&gt;&lt;span&gt; FastAPI&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span&gt; HTTPException&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;app&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; =&lt;&#x2F;span&gt;&lt;span&gt; FastAPI&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-constant&quot;&gt;CHATS&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;span&gt; dict&lt;&#x2F;span&gt;&lt;span&gt;[&lt;&#x2F;span&gt;&lt;span class=&quot;z-support&quot;&gt;int&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span&gt; list&lt;&#x2F;span&gt;&lt;span&gt;[&lt;&#x2F;span&gt;&lt;span class=&quot;z-support&quot;&gt;dict&lt;&#x2F;span&gt;&lt;span&gt;]&lt;&#x2F;span&gt;&lt;span&gt;]&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; =&lt;&#x2F;span&gt;&lt;span class=&quot;z-support&quot;&gt; dict&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-comment&quot;&gt;#&lt;&#x2F;span&gt;&lt;span class=&quot;z-comment&quot;&gt; Пример данных:&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-comment&quot;&gt;#&lt;&#x2F;span&gt;&lt;span class=&quot;z-comment&quot;&gt; CHATS = {0: [{&amp;quot;text&amp;quot;: &amp;quot;Hello&amp;quot;, &amp;quot;sender_name&amp;quot;: &amp;quot;Mihail&amp;quot;}]}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;@&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;app&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;.&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;post&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;&#x2F;chats&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-storage z-type&quot;&gt;async&lt;&#x2F;span&gt;&lt;span class=&quot;z-storage z-type&quot;&gt; def&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt; create_chat&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    new_chat_id&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; =&lt;&#x2F;span&gt;&lt;span class=&quot;z-support&quot;&gt; len&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt;CHATS&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-constant&quot;&gt;    CHATS&lt;&#x2F;span&gt;&lt;span&gt;[&lt;&#x2F;span&gt;&lt;span&gt;new_chat_id&lt;&#x2F;span&gt;&lt;span&gt;]&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; =&lt;&#x2F;span&gt;&lt;span&gt; [&lt;&#x2F;span&gt;&lt;span&gt;]&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-keyword&quot;&gt;    return&lt;&#x2F;span&gt;&lt;span&gt; {&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;id&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;span&gt; new_chat_id&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt; &amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;messages&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;span&gt; [&lt;&#x2F;span&gt;&lt;span&gt;]&lt;&#x2F;span&gt;&lt;span&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-storage z-type&quot;&gt;class&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt; Message&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity&quot;&gt;BaseModel&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    text&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;span class=&quot;z-support&quot;&gt; str&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; =&lt;&#x2F;span&gt;&lt;span&gt; Field&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable&quot;&gt;min_length&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;=&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt;1&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable&quot;&gt; max_length&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;=&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt;2048&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    sender_name&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;span class=&quot;z-support&quot;&gt; str&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;@&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;app&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;.&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;post&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;&#x2F;chats&#x2F;&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt;{chat_id}&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;&#x2F;messages&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-storage z-type&quot;&gt;async&lt;&#x2F;span&gt;&lt;span class=&quot;z-storage z-type&quot;&gt; def&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt; send_message_into_chat&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-variable z-parameter z-function&quot;&gt;    chat_id&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;span class=&quot;z-support&quot;&gt; int&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-variable z-parameter z-function&quot;&gt;    message&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;span&gt; Message&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-comment&quot;&gt;    #&lt;&#x2F;span&gt;&lt;span class=&quot;z-comment&quot;&gt; обратите внимание, что это fastapi достаёт Message из тела запроса (request body), &lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-comment&quot;&gt;    #&lt;&#x2F;span&gt;&lt;span class=&quot;z-comment&quot;&gt;  не из query параметров, как это было раньше.  это изменение поведения FastAPI связано &lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-comment&quot;&gt;    #&lt;&#x2F;span&gt;&lt;span class=&quot;z-comment&quot;&gt;  именно с тем, что мы указываем Pydantic модель, а не простой тип&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-keyword&quot;&gt;    if&lt;&#x2F;span&gt;&lt;span&gt; chat_id&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; not&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; in&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt; CHATS&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-keyword&quot;&gt;        raise&lt;&#x2F;span&gt;&lt;span&gt; HTTPException&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-variable&quot;&gt;            status_code&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;=&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt;404&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-variable&quot;&gt;            detail&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;=&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;Chat not found.  Please create chat before using it.&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;        )&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-comment&quot;&gt;    #&lt;&#x2F;span&gt;&lt;span class=&quot;z-comment&quot;&gt; преобразуем экземпляр класса Message в обычный словарь&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-constant&quot;&gt;    CHATS&lt;&#x2F;span&gt;&lt;span&gt;[&lt;&#x2F;span&gt;&lt;span&gt;chat_id&lt;&#x2F;span&gt;&lt;span&gt;]&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span&gt;append&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span&gt;{&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;text&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;span&gt; message&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span&gt;text&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt; &amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;sender_name&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;span&gt; message&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span&gt;sender_name&lt;&#x2F;span&gt;&lt;span&gt;}&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-keyword&quot;&gt;    return&lt;&#x2F;span&gt;&lt;span&gt; message&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;@&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;app&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;.&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;get&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;&#x2F;chats&#x2F;&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt;{chat_id}&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;&#x2F;messages&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-storage z-type&quot;&gt;async&lt;&#x2F;span&gt;&lt;span class=&quot;z-storage z-type&quot;&gt; def&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt; get_chat_messages&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-variable z-parameter z-function&quot;&gt;    chat_id&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;span class=&quot;z-support&quot;&gt; int&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-keyword&quot;&gt;    if&lt;&#x2F;span&gt;&lt;span&gt; chat_id&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; not&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; in&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt; CHATS&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-keyword&quot;&gt;        raise&lt;&#x2F;span&gt;&lt;span&gt; HTTPException&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-variable&quot;&gt;            status_code&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;=&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt;404&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-variable&quot;&gt;            detail&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;=&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;Chat not found&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;        )&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    result&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; =&lt;&#x2F;span&gt;&lt;span&gt; [&lt;&#x2F;span&gt;&lt;span&gt;]&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-keyword&quot;&gt;    for&lt;&#x2F;span&gt;&lt;span&gt; i&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; in&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt; CHATS&lt;&#x2F;span&gt;&lt;span&gt;[&lt;&#x2F;span&gt;&lt;span&gt;chat_id&lt;&#x2F;span&gt;&lt;span&gt;]&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-comment&quot;&gt;        #&lt;&#x2F;span&gt;&lt;span class=&quot;z-comment&quot;&gt; преобразуем словари в экземпляры класса Message через валидацию&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;        message&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; =&lt;&#x2F;span&gt;&lt;span&gt; Message&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span&gt;model_validate&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span&gt;i&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;        result&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span&gt;append&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span&gt;message&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-keyword&quot;&gt;    return&lt;&#x2F;span&gt;&lt;span&gt; result&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;run&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span&gt;app&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;&lt;style&gt;
.tbox-container {
    border: 1px solid white; 
    border-radius: 2px;
    overflow: hidden;
    padding: 1em;
    margin: 2em 0;
}

.tbox-header {
    font-size: 1.2rem;
    margin: 0.5em 0 1em 0;
}

&lt;&#x2F;style&gt;
&lt;div id=&quot;note-1&quot; class=&quot;tbox-container&quot;&gt;
&lt;div class=&quot;tbox-header&quot;&gt;
&lt;a class=&quot;tbox-anchor&quot; href=&quot;#note-1&quot; aria-label=&quot;Ссылка на этот блок&quot;&gt;
&lt;span class=&quot;tbox-title&quot;&gt;Примечание&lt;&#x2F;span&gt;
&lt;&#x2F;a&gt;
&lt;&#x2F;div&gt;
&lt;div class=&quot;tbox-body&quot;&gt;
&lt;p&gt;Можно было бы хранить в глобальной переменной CHATS сразу экземпляры класса Message. Тогда не надо было бы приводить Message к словарю, а потом обратно.  Однако нам понадобится делать подобные конвертации когда мы начнём использовать базы данных: там нельзя будет сохранить целый экземпляр класса Pydantic модели.&lt;&#x2F;p&gt;
&lt;&#x2F;div&gt;
&lt;&#x2F;div&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>Python, FastAPI, 2.</title>
        <published>2026-05-10T00:00:00+00:00</published>
        <updated>2026-05-10T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Unknown
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://mymaterials.ru/lessons/python-fastapi-2/"/>
        <id>https://mymaterials.ru/lessons/python-fastapi-2/</id>
        
        <content type="html" xml:base="https://mymaterials.ru/lessons/python-fastapi-2/">&lt;h2 id=&quot;poleznye-resursy-na-russkom&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#poleznye-resursy-na-russkom&quot; aria-label=&quot;Anchor link for: poleznye-resursy-na-russkom&quot;&gt;Полезные ресурсы (на русском)&lt;&#x2F;a&gt;&lt;&#x2F;h2&gt;
&lt;ol&gt;
&lt;li&gt;&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;fastapi.tiangolo.com&#x2F;ru&#x2F;learn&#x2F;&quot;&gt;Официальный ресурс по изучению FastAPI&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;li&gt;&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;developer.mozilla.org&#x2F;ru&#x2F;docs&#x2F;Web&#x2F;HTTP&#x2F;Guides&#x2F;Overview&quot;&gt;Хороший (но не самый простой) материал по HTTP&lt;&#x2F;a&gt;.  На том же сайте ещё много смежных материалов.  Некоторые на русском, некоторые только на англ.&lt;&#x2F;li&gt;
&lt;&#x2F;ol&gt;
&lt;h2 id=&quot;primer-http-api-s-kotorym-rabotali&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#primer-http-api-s-kotorym-rabotali&quot; aria-label=&quot;Anchor link for: primer-http-api-s-kotorym-rabotali&quot;&gt;Пример HTTP API с которым работали:&lt;&#x2F;a&gt;&lt;&#x2F;h2&gt;
&lt;h3 id=&quot;endpoint-dlia-polucheniia-vsekh-aitemov&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#endpoint-dlia-polucheniia-vsekh-aitemov&quot; aria-label=&quot;Anchor link for: endpoint-dlia-polucheniia-vsekh-aitemov&quot;&gt;Эндпоинт для получения всех айтемов&lt;&#x2F;a&gt;&lt;&#x2F;h3&gt;
&lt;p&gt;Запрос:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo z-code&quot;&gt;&lt;code data-lang=&quot;http&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-keyword&quot;&gt;GET&lt;&#x2F;span&gt;&lt;span&gt; &#x2F;items&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; HTTP&lt;&#x2F;span&gt;&lt;span&gt;&#x2F;&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt;1.1&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Ответ:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo z-code&quot;&gt;&lt;code data-lang=&quot;http&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-keyword&quot;&gt;HTTP&lt;&#x2F;span&gt;&lt;span&gt;&#x2F;&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt;1.1&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt; 200&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; OK&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-entity z-name z-tag&quot;&gt;Content-Type&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;:&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; application&#x2F;json; charset=utf-8&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt; &lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;[&lt;&#x2F;span&gt;&lt;span&gt;{&lt;&#x2F;span&gt;&lt;span class=&quot;z-support&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-support&quot;&gt;id&lt;&#x2F;span&gt;&lt;span class=&quot;z-support&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt;0&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span class=&quot;z-support&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-support&quot;&gt;name&lt;&#x2F;span&gt;&lt;span class=&quot;z-support&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;Тёплая куртка&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;}&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span&gt;{&lt;&#x2F;span&gt;&lt;span class=&quot;z-support&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-support&quot;&gt;id&lt;&#x2F;span&gt;&lt;span class=&quot;z-support&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt;1&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span class=&quot;z-support&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-support&quot;&gt;name&lt;&#x2F;span&gt;&lt;span class=&quot;z-support&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;Обычные носки&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;}&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span&gt;{&lt;&#x2F;span&gt;&lt;span class=&quot;z-support&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-support&quot;&gt;id&lt;&#x2F;span&gt;&lt;span class=&quot;z-support&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt;2&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span class=&quot;z-support&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-support&quot;&gt;name&lt;&#x2F;span&gt;&lt;span class=&quot;z-support&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;Перчатки&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;}&lt;&#x2F;span&gt;&lt;span&gt;]&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;&lt;h3 id=&quot;endpoint-dlia-polucheniia-odnogo-aitema-po-aidi&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#endpoint-dlia-polucheniia-odnogo-aitema-po-aidi&quot; aria-label=&quot;Anchor link for: endpoint-dlia-polucheniia-odnogo-aitema-po-aidi&quot;&gt;Эндпоинт для получения одного айтема по айди&lt;&#x2F;a&gt;&lt;&#x2F;h3&gt;
&lt;p&gt;Запрос:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo z-code&quot;&gt;&lt;code data-lang=&quot;http&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-keyword&quot;&gt;GET&lt;&#x2F;span&gt;&lt;span&gt; &#x2F;items&#x2F;0&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; HTTP&lt;&#x2F;span&gt;&lt;span&gt;&#x2F;&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt;1.1&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Ответ:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo z-code&quot;&gt;&lt;code data-lang=&quot;http&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-keyword&quot;&gt;HTTP&lt;&#x2F;span&gt;&lt;span&gt;&#x2F;&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt;1.1&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt; 200&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; OK&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-entity z-name z-tag&quot;&gt;Content-Type&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;:&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; application&#x2F;json; charset=utf-8&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt; &lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;{&lt;&#x2F;span&gt;&lt;span class=&quot;z-support&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-support&quot;&gt;id&lt;&#x2F;span&gt;&lt;span class=&quot;z-support&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt;0&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span class=&quot;z-support&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-support&quot;&gt;name&lt;&#x2F;span&gt;&lt;span class=&quot;z-support&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;Тёплая куртка&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;&lt;h2 id=&quot;realizatsiia-bez-ispol-zovaniia-veb-freimvorka&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#realizatsiia-bez-ispol-zovaniia-veb-freimvorka&quot; aria-label=&quot;Anchor link for: realizatsiia-bez-ispol-zovaniia-veb-freimvorka&quot;&gt;Реализация без использования веб-фреймворка&lt;&#x2F;a&gt;&lt;&#x2F;h2&gt;
&lt;pre class=&quot;giallo z-code&quot;&gt;&lt;code data-lang=&quot;python&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-keyword&quot;&gt;from&lt;&#x2F;span&gt;&lt;span&gt; waitress&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; import&lt;&#x2F;span&gt;&lt;span&gt; serve&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt; &lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-keyword&quot;&gt;import&lt;&#x2F;span&gt;&lt;span&gt; json&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt; &lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt; &lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-constant&quot;&gt;ITEMS&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; =&lt;&#x2F;span&gt;&lt;span&gt; [&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;        &amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;id&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt; 0&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;        &amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;name&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt; &amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;Тёплая куртка&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    }&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;        &amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;id&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt; 1&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;        &amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;name&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt; &amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;Обычные носки&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    }&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;        &amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;id&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt; 2&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;        &amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;name&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt; &amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;Перчатки&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    }&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;]&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-storage z-type&quot;&gt;def&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt; app&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-parameter z-function&quot;&gt;environ&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-parameter z-function&quot;&gt; start_response&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-comment&quot;&gt;    #&lt;&#x2F;span&gt;&lt;span class=&quot;z-comment&quot;&gt; Метод запроса: GET, POST, PUT, DELETE и т.д.&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    method&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; =&lt;&#x2F;span&gt;&lt;span&gt; environ&lt;&#x2F;span&gt;&lt;span&gt;[&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;REQUEST_METHOD&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;]&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt; &lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-comment&quot;&gt;    #&lt;&#x2F;span&gt;&lt;span class=&quot;z-comment&quot;&gt; Путь запроса: &#x2F;items, &#x2F;items&#x2F;1 и т.д.&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    path&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; =&lt;&#x2F;span&gt;&lt;span&gt; environ&lt;&#x2F;span&gt;&lt;span&gt;[&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;PATH_INFO&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;]&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt; &lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-comment&quot;&gt;    #&lt;&#x2F;span&gt;&lt;span class=&quot;z-comment&quot;&gt; тут будут лежать данные, которые мы хотим вернуть в зависимости от запроса&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    result_data&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; =&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt; None&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt; &lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-comment&quot;&gt;    #&lt;&#x2F;span&gt;&lt;span class=&quot;z-comment&quot;&gt; Получение всех айтемов&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-keyword&quot;&gt;    if&lt;&#x2F;span&gt;&lt;span&gt; method&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; ==&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt; &amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;GET&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; and&lt;&#x2F;span&gt;&lt;span&gt; path&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; ==&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt; &amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;&#x2F;items&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;        result_data&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; =&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt; ITEMS&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt; &lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-comment&quot;&gt;    #&lt;&#x2F;span&gt;&lt;span class=&quot;z-comment&quot;&gt; Получение одного по айди&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-keyword&quot;&gt;    elif&lt;&#x2F;span&gt;&lt;span&gt; method&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; ==&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt; &amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;GET&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; and&lt;&#x2F;span&gt;&lt;span&gt; path&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span&gt;startswith&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;&#x2F;items&#x2F;&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;        without_prefix&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; =&lt;&#x2F;span&gt;&lt;span&gt; path&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span&gt;removeprefix&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;&#x2F;items&#x2F;&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt; &lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-keyword&quot;&gt;        try&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;            item_id&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; =&lt;&#x2F;span&gt;&lt;span class=&quot;z-support&quot;&gt; int&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span&gt;without_prefix&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-keyword&quot;&gt;        except&lt;&#x2F;span&gt;&lt;span class=&quot;z-support&quot;&gt; ValueError&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-comment&quot;&gt;            #&lt;&#x2F;span&gt;&lt;span class=&quot;z-comment&quot;&gt; FastAPI в похожей ситуации вернёт более длинный текст ошибки, но логика работы такая же&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;            headers&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; =&lt;&#x2F;span&gt;&lt;span&gt; [&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;Content-Type&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt; &amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;application&#x2F;json; charset=utf-8&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;span&gt;]&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;            start_response&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;422 Unprocessable Entity&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span&gt; headers&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-keyword&quot;&gt;            return&lt;&#x2F;span&gt;&lt;span&gt; [&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;&amp;#39;&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;{&amp;quot;detail&amp;quot;:&amp;quot;item_id must be an integer&amp;quot;}&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;&amp;#39;&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span&gt;encode&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;utf-8&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;span&gt;]&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-keyword&quot;&gt;        for&lt;&#x2F;span&gt;&lt;span&gt; item&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; in&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt; ITEMS&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-keyword&quot;&gt;            if&lt;&#x2F;span&gt;&lt;span&gt; item_id&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; ==&lt;&#x2F;span&gt;&lt;span&gt; item&lt;&#x2F;span&gt;&lt;span&gt;[&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;id&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;]&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;                result_data&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; =&lt;&#x2F;span&gt;&lt;span&gt; item&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-keyword&quot;&gt;                break&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-keyword&quot;&gt;        else&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-comment&quot;&gt;  #&lt;&#x2F;span&gt;&lt;span class=&quot;z-comment&quot;&gt; если цикл закончился без break: всё обошли и ничего не нашли&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;            headers&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; =&lt;&#x2F;span&gt;&lt;span&gt; [&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;Content-Type&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt; &amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;application&#x2F;json; charset=utf-8&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;span&gt;]&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;            start_response&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;404 Not Found&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span&gt; headers&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-keyword&quot;&gt;            return&lt;&#x2F;span&gt;&lt;span&gt; [&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;&amp;#39;&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;{&amp;quot;detail&amp;quot;:&amp;quot;Item not found&amp;quot;}&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;&amp;#39;&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span&gt;encode&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;utf-8&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;span&gt;]&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt; &lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-comment&quot;&gt;    #&lt;&#x2F;span&gt;&lt;span class=&quot;z-comment&quot;&gt; ... если никакой из маршрутов не подошёл&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-keyword&quot;&gt;    if&lt;&#x2F;span&gt;&lt;span&gt; result_data&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; is&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt; None&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;        headers&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; =&lt;&#x2F;span&gt;&lt;span&gt; [&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;Content-Type&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt; &amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;application&#x2F;json; charset=utf-8&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;span&gt;]&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;        start_response&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;404 Not Found&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span&gt; headers&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-keyword&quot;&gt;        return&lt;&#x2F;span&gt;&lt;span&gt; [&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;&amp;#39;&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;{&amp;quot;detail&amp;quot;:&amp;quot;Not found&amp;quot;}&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;&amp;#39;&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span&gt;encode&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;utf-8&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;span&gt;]&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt; &lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-comment&quot;&gt;    #&lt;&#x2F;span&gt;&lt;span class=&quot;z-comment&quot;&gt; ... иначе формируем успешный ответ&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    headers&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; =&lt;&#x2F;span&gt;&lt;span&gt; [&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;Content-Type&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt; &amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;application&#x2F;json; charset=utf-8&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;span&gt;]&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    start_response&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;200 OK&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span&gt; headers&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-keyword&quot;&gt;    return&lt;&#x2F;span&gt;&lt;span&gt; [&lt;&#x2F;span&gt;&lt;span&gt;json&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span&gt;dumps&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span&gt;result_data&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable&quot;&gt; ensure_ascii&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;=&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt;False&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span&gt;encode&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;utf-8&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;span&gt;]&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt; &lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt; &lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;serve&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span&gt;app&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;span&gt; &lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;&lt;h2 id=&quot;realizatsiia-s-ispol-zovaniem-fastapi&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#realizatsiia-s-ispol-zovaniem-fastapi&quot; aria-label=&quot;Anchor link for: realizatsiia-s-ispol-zovaniem-fastapi&quot;&gt;Реализация с использованием FastAPI&lt;&#x2F;a&gt;&lt;&#x2F;h2&gt;
&lt;p&gt;Поведение, формирующее из HTTP запроса HTTP ответ по сути такое же, как в примере выше&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo z-code&quot;&gt;&lt;code data-lang=&quot;python&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-keyword&quot;&gt;from&lt;&#x2F;span&gt;&lt;span&gt; uvicorn&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; import&lt;&#x2F;span&gt;&lt;span&gt; run&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-keyword&quot;&gt;from&lt;&#x2F;span&gt;&lt;span&gt; fastapi&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; import&lt;&#x2F;span&gt;&lt;span&gt; FastAPI&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span&gt; HTTPException&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-constant&quot;&gt;ITEMS&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; =&lt;&#x2F;span&gt;&lt;span&gt; [&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    {&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;id&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt; 0&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt; &amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;name&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt; &amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;Тёплая куртка&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;}&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    {&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;id&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt; 1&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt; &amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;name&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt; &amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;Обычные носки&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;}&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    {&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;id&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt; 2&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt; &amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;name&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt; &amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;Перчатки&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;}&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;]&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt; &lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt; &lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;app&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; =&lt;&#x2F;span&gt;&lt;span&gt; FastAPI&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt; &lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt; &lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;@&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;app&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;.&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;get&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;&#x2F;items&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-storage z-type&quot;&gt;def&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt; get_items&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-keyword&quot;&gt;    return&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt; ITEMS&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt; &lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;@&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;app&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;.&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;get&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;&#x2F;items&#x2F;&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt;{item_id}&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-storage z-type&quot;&gt;def&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt; get_item_by_id&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-parameter z-function&quot;&gt;item_id&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;span class=&quot;z-support&quot;&gt; int&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-keyword&quot;&gt;    for&lt;&#x2F;span&gt;&lt;span&gt; item&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; in&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt; ITEMS&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-keyword&quot;&gt;        if&lt;&#x2F;span&gt;&lt;span&gt; item&lt;&#x2F;span&gt;&lt;span&gt;[&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;id&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;]&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; ==&lt;&#x2F;span&gt;&lt;span&gt; item_id&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-keyword&quot;&gt;            return&lt;&#x2F;span&gt;&lt;span&gt; item&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-comment&quot;&gt;    #&lt;&#x2F;span&gt;&lt;span class=&quot;z-comment&quot;&gt; забыл сказать про это.  так в fastapi формируют HTTP ответ с ошибкой.  на след. занятии разберём.&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-keyword&quot;&gt;    raise&lt;&#x2F;span&gt;&lt;span&gt; HTTPException&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable&quot;&gt;status_code&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;=&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt;404&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable&quot;&gt; detail&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;=&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;Item not found&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt; &lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt; &lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;run&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span&gt;app&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;&lt;h2 id=&quot;kod-s-zaniatiia&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#kod-s-zaniatiia&quot; aria-label=&quot;Anchor link for: kod-s-zaniatiia&quot;&gt;Код с занятия&lt;&#x2F;a&gt;&lt;&#x2F;h2&gt;
&lt;pre class=&quot;giallo z-code&quot;&gt;&lt;code data-lang=&quot;python&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-keyword&quot;&gt;from&lt;&#x2F;span&gt;&lt;span&gt; uvicorn&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; import&lt;&#x2F;span&gt;&lt;span&gt; run&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-keyword&quot;&gt;from&lt;&#x2F;span&gt;&lt;span&gt; fastapi&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; import&lt;&#x2F;span&gt;&lt;span&gt; FastAPI&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span&gt; HTTPException&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;app&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; =&lt;&#x2F;span&gt;&lt;span&gt; FastAPI&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-constant&quot;&gt;ITEMS&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; =&lt;&#x2F;span&gt;&lt;span&gt; [&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    {&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;id&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt; 0&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt; &amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;name&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt; &amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;Тёплая куртка&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;}&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    {&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;id&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt; 1&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt; &amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;name&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt; &amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;Обычные носки&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;}&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    {&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;id&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt; 2&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt; &amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;name&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt; &amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;Перчатки&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt; &amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;verbose_name&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt; &amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;Резиновые перчатки&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;}&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;]&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;@&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;app&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;.&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;get&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;&#x2F;items&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-storage z-type&quot;&gt;async&lt;&#x2F;span&gt;&lt;span class=&quot;z-storage z-type&quot;&gt; def&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt; get_all_items&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-keyword&quot;&gt;    return&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt; ITEMS&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;@&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;app&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;.&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;get&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;&#x2F;items&#x2F;&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt;{item_id}&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-storage z-type&quot;&gt;async&lt;&#x2F;span&gt;&lt;span class=&quot;z-storage z-type&quot;&gt; def&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt; get_item_by_id&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-variable z-parameter z-function&quot;&gt;        item_id&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;span class=&quot;z-support&quot;&gt; int&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-variable z-parameter z-function&quot;&gt;        verbose_name&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;span class=&quot;z-support&quot;&gt; bool&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-keyword&quot;&gt;    for&lt;&#x2F;span&gt;&lt;span&gt; item&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; in&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt; ITEMS&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-keyword&quot;&gt;        if&lt;&#x2F;span&gt;&lt;span&gt; item_id&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; ==&lt;&#x2F;span&gt;&lt;span&gt; item&lt;&#x2F;span&gt;&lt;span&gt;[&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;id&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;]&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-keyword&quot;&gt;            if&lt;&#x2F;span&gt;&lt;span&gt; verbose_name&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; and&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt; &amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;verbose_name&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; in&lt;&#x2F;span&gt;&lt;span&gt; item&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-keyword&quot;&gt;                return&lt;&#x2F;span&gt;&lt;span&gt; {&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;id&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;span&gt; item&lt;&#x2F;span&gt;&lt;span&gt;[&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;id&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;]&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt; &amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;name&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;span&gt; item&lt;&#x2F;span&gt;&lt;span&gt;[&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;verbose_name&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;]&lt;&#x2F;span&gt;&lt;span&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-keyword&quot;&gt;            return&lt;&#x2F;span&gt;&lt;span&gt; {&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;id&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;span&gt; item&lt;&#x2F;span&gt;&lt;span&gt;[&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;id&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;]&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt; &amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;name&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;span&gt; item&lt;&#x2F;span&gt;&lt;span&gt;[&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;name&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;]&lt;&#x2F;span&gt;&lt;span&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-comment&quot;&gt;    #&lt;&#x2F;span&gt;&lt;span class=&quot;z-comment&quot;&gt; забыл сказать про это.  так в fastapi формируют HTTP ответ с ошибкой.  на след. занятии разберём.&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-keyword&quot;&gt;    raise&lt;&#x2F;span&gt;&lt;span&gt; HTTPException&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable&quot;&gt;status_code&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;=&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt;404&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable&quot;&gt; detail&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;=&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;Item not found&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;@&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;app&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;.&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;post&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;&#x2F;items&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-storage z-type&quot;&gt;async&lt;&#x2F;span&gt;&lt;span class=&quot;z-storage z-type&quot;&gt; def&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt; create_new_item&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-variable z-parameter z-function&quot;&gt;        name&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;span class=&quot;z-support&quot;&gt; str&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-variable z-parameter z-function&quot;&gt;        verbose_name&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;span class=&quot;z-support&quot;&gt; str&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;    &amp;quot;&amp;quot;&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-string&quot;&gt;    Создаёт новый айтем&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;    &amp;quot;&amp;quot;&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    new_item&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; =&lt;&#x2F;span&gt;&lt;span&gt; {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;        &amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;id&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;span class=&quot;z-support&quot;&gt; len&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt;ITEMS&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;        &amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;name&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;span&gt; name&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;        &amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;verbose_name&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;span&gt; verbose_name&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    }&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-constant&quot;&gt;    ITEMS&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span&gt;append&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span&gt;new_item&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-keyword&quot;&gt;    return&lt;&#x2F;span&gt;&lt;span&gt; new_item&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;run&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span&gt;app&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;</content>
        
    </entry>
    <entry xml:lang="en">
        <title>Как победить на хакатоне. Я побеждал, и я [не] знаю.</title>
        <published>2026-05-01T00:00:00+00:00</published>
        <updated>2026-05-01T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Unknown
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://mymaterials.ru/articles/how-to-win-a-hackathon/"/>
        <id>https://mymaterials.ru/articles/how-to-win-a-hackathon/</id>
        
        <content type="html" xml:base="https://mymaterials.ru/articles/how-to-win-a-hackathon/">&lt;p&gt;Я тут подумал, что мне есть что сказать на эту тему. Опыт поражений и побед в хакатонах сформировал у меня несколько идей, сформулированных ниже. Думаю, они будут особенно полезны тем, кто учавствует не первый раз и всерьёз хочет победить. Но эти идеи применимы и за пределами хакатонов.&lt;&#x2F;p&gt;
&lt;p&gt;Решение о том, кто займёт призовые места по кейсу компании, принимают её представители. Обычно они уже находятся в контексте задачи и имеют представление о том, каким должен быть победитель. Логично тогда делать так: на протяжении всего хакатона вы подгоняете свой проект под их ожидания, и к концу проект должен максимально с этими ожиданиями совпадать. Далее во время презентации вы показываете суть того, что сделали.&lt;&#x2F;p&gt;
&lt;p&gt;Ну, мне такая модель казалась логичной, и я ей следовал. Да и в целом она работает. Вы можете найти огромное количество статей, которые дают советы в рамках примерно той же модели, и эти советы стоят внимания: больше общайтесь с экспертами, заранее готовьте презентацию, иногда спите, и так далее. Но я из своего опыта заметил, что есть эффекты, которые искажают эту модель. И эти искажения могут стоить вам призового места.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;effekt-1-razroznennost-predstavitelei&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#effekt-1-razroznennost-predstavitelei&quot; aria-label=&quot;Anchor link for: effekt-1-razroznennost-predstavitelei&quot;&gt;Эффект 1. Разрозненность представителей.&lt;&#x2F;a&gt;&lt;&#x2F;h2&gt;
&lt;p&gt;У компании часто несколько представителей на кейс. Построение продукта на основе переговоров с одним экспертом может привести к тому, что результат не будет подходить под видение остальных. Важно учитывать, что у нескольких экспертов нет общего представления о лучшем решении и наборе критериев. Да, они обычно заранее обсуждают критерии между собой, но в итоге вы всё равно общаетесь с конкретным человеком, додумывающим на ходу вещи, с которыми остальные могут не согласиться.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;effekt-2-iskazhenie-kriteriev&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#effekt-2-iskazhenie-kriteriev&quot; aria-label=&quot;Anchor link for: effekt-2-iskazhenie-kriteriev&quot;&gt;Эффект 2. Искажение критериев.&lt;&#x2F;a&gt;&lt;&#x2F;h2&gt;
&lt;p&gt;Эксперт часто не знает, чего хочет. &lt;em&gt;Даже если говорит, что знает.&lt;&#x2F;em&gt; У него обычно есть ясное представление о каком-то аспекте решения: к примеру, каким должен быть UX. Он понимает, как должно быть, почему именно так, и на что нужно смотреть при оценке. И тут всё в порядке. Проблема возникает, когда он начинают говорить о том, чего не понимает.&lt;&#x2F;p&gt;
&lt;p&gt;Вот вы спросили: а каким должен быть код? Вам ответили: код должен быть чистым. Окей, требование от экспертов получено. Вы радуетесь, что прекрасно понимаете, что такое чистый код, и способны в полной мере выполнить это требование. Вы тратите половину рабочего времени на архитектуру и получаете полностью готовый к продакшену продукт. И... эксперты не обращают на это внимания.&lt;&#x2F;p&gt;
&lt;p&gt;Оказывается, тот, кто вам это сказал, хорошо разбирается в UI&#x2F;UX, но имеет смутное представление об архитектуре ПО. И на самом деле он так сказал потому, что слышал, что чистый код — это хорошо. Или в его понимании чистота неразрывно связана с тем, будет ли приложение глючить или нет.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;effekt-3-tekhnicheskie-ogranicheniia-protsedury-otsenki&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#effekt-3-tekhnicheskie-ogranicheniia-protsedury-otsenki&quot; aria-label=&quot;Anchor link for: effekt-3-tekhnicheskie-ogranicheniia-protsedury-otsenki&quot;&gt;Эффект 3. Технические ограничения процедуры оценки.&lt;&#x2F;a&gt;&lt;&#x2F;h2&gt;
&lt;p&gt;Процедура оценки имеет свои ограничения. Эксперты не могут уделить каждому решению одинаковое количество времени, иначе этого времени не хватило бы, чтобы вникнуть во все решения. Обычно есть шорт-лист интересных проектов, который используется и во время работы над кейсами, и во время принятия решения. Но даже оценка проектов из шорт-листа может сильно искажаться процедурой принятия решений: решение зависит от факторов, которые эксперты &lt;em&gt;не считают важными&lt;&#x2F;em&gt;. В ходе принятия решения эксперты подвержены различным когнитивным искажениям, что демонстрируется в &lt;a href=&quot;https:&#x2F;&#x2F;mymaterials.ru&#x2F;articles&#x2F;how-to-win-a-hackathon&#x2F;#example-1&quot;&gt;примере&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
&lt;style&gt;
.tbox-container {
    border: 1px solid white; 
    border-radius: 2px;
    overflow: hidden;
    padding: 1em;
    margin: 2em 0;
}

.tbox-header {
    font-size: 1.2rem;
    margin: 0.5em 0 1em 0;
}

&lt;&#x2F;style&gt;
&lt;div id=&quot;example-1&quot; class=&quot;tbox-container&quot;&gt;
&lt;div class=&quot;tbox-header&quot;&gt;
&lt;a class=&quot;tbox-anchor&quot; href=&quot;#example-1&quot; aria-label=&quot;Ссылка на этот блок&quot;&gt;
&lt;span class=&quot;tbox-title&quot;&gt;Пример: платформа CI&#x2F;CD&lt;&#x2F;span&gt;
&lt;&#x2F;a&gt;
&lt;&#x2F;div&gt;
&lt;div class=&quot;tbox-body&quot;&gt;
&lt;p&gt;Представим такой кейс: нужно сделать платформу для автоматической доставки приложений на сервера. В поле зрения экспертов сейчас находится решение конкретной команды. В нём есть возможность установки начального ПО на сервер через веб-интерфейс, через указание пользователя и пароля для подключения по SSH. Эта фича имеет место быть, но она на самом деле достаточно сомнительна: не то чтобы это реально удобнее ручной установки, и при том создаёт на пустом месте дополнительные риски информационной безопасности. Но эта фича показалась экспертам достаточно уместной. Как бы почему нет, пусть будет.&lt;&#x2F;p&gt;
&lt;p&gt;Переходят к следующему проекту. Там тоже эта фича есть. Следующий. А тут этой фичи нет, зато есть другая фича: возможность настроить уведомления о событиях в пайплайне на почту. При оценке проекта бросается в глаза, что нет той фичи, что была у всех предыдущих. Но предыдущие уже были обсуждены, о них сложилось определённое мнение. Будет ли оно изменено из-за того, что тут есть фича, которой не было у них? Вполне просто себе представить, что нет: порядок проектов повлиял на то, какое о них сложилось мнение. И это всё при том, что первая фича не имеет особого смысла, а вторая реально полезна.&lt;&#x2F;p&gt;
&lt;&#x2F;div&gt;
&lt;&#x2F;div&gt;
&lt;h2 id=&quot;effekt-4-raspredelionnost-protsessa-otsenki&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#effekt-4-raspredelionnost-protsessa-otsenki&quot; aria-label=&quot;Anchor link for: effekt-4-raspredelionnost-protsessa-otsenki&quot;&gt;Эффект 4. Распределённость процесса оценки.&lt;&#x2F;a&gt;&lt;&#x2F;h2&gt;
&lt;p&gt;Как уже стало видно, процесс оценки с точки зрения экспертов сложен даже чисто технически, особенно если они пытаются подойти к оценке объективно. И тут важно понимать, что он распределён на всё время проведения хакатона: питч-сессии и другое общение с экспертами влияют на итоговую оценку. Через взаимодействие во время работы формируется представление о вас, от которого зависит, какое именно место вы будете занимать в процессе принятия финального решения. Шорт-лист обычно сформирован ещё задолго до презентации.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;effekt-5-vliianie-sredy&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#effekt-5-vliianie-sredy&quot; aria-label=&quot;Anchor link for: effekt-5-vliianie-sredy&quot;&gt;Эффект 5. Влияние среды.&lt;&#x2F;a&gt;&lt;&#x2F;h2&gt;
&lt;p&gt;На оценку могут влиять детали, не относящиеся к сути кейса. Например, площадка проведения может просить представителей компаний учитывать участие в сторонних активностях. Я один раз столкнулся с тем, что в этом признались представители. При этом они признали, что не могут публично раскрывать этот слой критериев.&lt;&#x2F;p&gt;
&lt;p&gt;Эти эффекты, как видно, могут ломать описанную схему. И вот вы вроде бы реально сделали крутой продукт. Вам кажется, что эксперты тоже видят хороший результат именно так: они ведь сами говорили, что это действительно важно. Вы максимально эффективно выстроили работу над решением. Но побеждает другая команда.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;chto-s-nimi-delat&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#chto-s-nimi-delat&quot; aria-label=&quot;Anchor link for: chto-s-nimi-delat&quot;&gt;Что с ними делать?&lt;&#x2F;a&gt;&lt;&#x2F;h2&gt;
&lt;p&gt;У меня нет готового решения, позволяющего контролировать каждый из этих эффектов. Но есть некоторые логичные гипотезы:&lt;&#x2F;p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Нужно учитывать иерархию внутри группы представителей и, если возможно, узнать, как именно происходит принятие решений. Исходя из этого, стоит думать о том, как продвинуть своё решение внутри этой процедуры.&lt;&#x2F;p&gt;
&lt;&#x2F;li&gt;
&lt;li&gt;
&lt;p&gt;Нужно учитывать каждого представителя: как для прояснения их видения, так и для донесения до них своих идей.&lt;&#x2F;p&gt;
&lt;&#x2F;li&gt;
&lt;li&gt;
&lt;p&gt;Нужно учитывать, что тот, с кем вы общаетесь имеет отличный от вашего опыт, и одни и те же термины или формулировки могут для него значить одно, а для вас другое. Следует уделять внимание тому, как думает эксперт, кроме того, что он говорит.&lt;&#x2F;p&gt;
&lt;&#x2F;li&gt;
&lt;li&gt;
&lt;p&gt;Есть смысл выбирать кейс на основе того, насколько вероятны эти эффекты. Например, если представители экспертны в том же, в чём и вы, вам, скорее всего, будет проще найти с ними общий язык, в частности снизив эффект значимости аспектов результата.&lt;&#x2F;p&gt;
&lt;&#x2F;li&gt;
&lt;li&gt;
&lt;p&gt;Нужно влиять на решение с самого начала до самого конца. Если ваше решение в шорт-листе и его активно сравнивают с другими, даже мелкие детали могут иметь большее значение. Но здесь важно учитывать и остальные эффекты: например, важность детали должна быть понятна экспертам.&lt;&#x2F;p&gt;
&lt;&#x2F;li&gt;
&lt;li&gt;
&lt;p&gt;Нужно заранее выяснить особенности компании и&#x2F;или площадки. Возможно, вы сможете из опыта других команд узнать какие-то неочевидные моменты, вроде того, что надо участвовать в ивентах площадки, или что одна из компаний, как правило, не очень ответственно подходит к хакатону, из-за чего описанные эффекты проявляются крайне значительно.&lt;&#x2F;p&gt;
&lt;&#x2F;li&gt;
&lt;&#x2F;ol&gt;
&lt;p&gt;Однако... даже если вы сумели всё это учесть, не факт, что вы победите. Это лишь то, что, по моему опыту, часто влияло на оценку и при этом поддаётся контролю. &lt;strong&gt;Есть и такие эффекты, которые почти невозможно контролировать.&lt;&#x2F;strong&gt; Пример из моего опыта: иногда из шорт-листа решений выбирает высокопоставленный представитель компании, вроде гендиректора, к общению с которым у вас нет доступа во время работы над кейсом. Так что угадать, что важно в решении именно для него, иногда может быть просто невозможно.&lt;&#x2F;p&gt;
&lt;p&gt;Но не надо думать, что раз всё так сложно, у вас ничего не получится. Если вы честно будете стараться, мне представляется вполне возможным достичь стабильно высокого шанса на победу. Не надо забывать, что конкурируют с вами команды, тоже состоящие из неидеальных людей. Да и опыт попыток понять, как работает эта сложная социальная среда, интересен сам по себе.&lt;&#x2F;p&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>Я знаю, какую музыку ты слушаешь, kido@yandex.ru</title>
        <published>2025-11-04T00:00:00+00:00</published>
        <updated>2025-11-04T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Unknown
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://mymaterials.ru/articles/yandex-email/"/>
        <id>https://mymaterials.ru/articles/yandex-email/</id>
        
        <content type="html" xml:base="https://mymaterials.ru/articles/yandex-email/">&lt;p&gt;Как поделиться своим плейлистом в Яндекс.Музыке? Например, тем, что у всех есть по умолчанию: понравившиеся треки.&lt;&#x2F;p&gt;
&lt;p&gt;Зайдём в веб-версию. Плейлист. Подробности. Кнопка «поделиться». Нам выдаётся ссылка вида&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo z-code&quot;&gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;https:&#x2F;&#x2F;music.yandex.ru&#x2F;playlists&#x2F;lk.UID&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Где &lt;code&gt;UID&lt;&#x2F;code&gt; — уникальный идентификатор плейлиста. Интересно: эта же ссылка у меня в адресной строке браузера. А что вообще произошло, когда я нажал на «поделиться»? Я опубликовал эту страницу? Никаких кнопок для управления приватностью не вижу. Получается, мои избранные теперь всегда публичны: достаточно знать &lt;code&gt;UID&lt;&#x2F;code&gt;? Или, может, я не нашёл, где этим управлять? &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;yandex.ru&#x2F;support&#x2F;music&#x2F;ru&#x2F;collection&#x2F;playlists&quot;&gt;Смотрю в разделе помощи Яндекса&lt;&#x2F;a&gt;:&lt;&#x2F;p&gt;
&lt;p&gt;Новый плейлист, созданный на смартфоне, по умолчанию является приватным, но после того, как вы поделитесь им с другом, он станет доступен и другим пользователям Яндекс Музыки. Читайте подробнее в нашей статье про &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;yandex.ru&#x2F;support&#x2F;search-results&#x2F;?service=&amp;amp;query=%D0%BF%D1%80%D0%B8%D0%B2%D0%B0%D1%82%D0%BD%D0%BE%D1%81%D1%82%D1%8C&quot;&gt;«Приватность»&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;Больше ничего нет. «Статью про приватность» предлагаю вам оценить самим, перейдя по ссылке. Вероятно, нажав на кнопку «поделиться», я навсегда разрешил доступ по ссылке к лайкнутым мною трекам.&lt;&#x2F;p&gt;
&lt;p&gt;Однако потом я всё-таки нашёл, как управлять приватностью плейлистов: под кнопкой «поделиться» есть тумблер «приватный плейлист». Но есть нюанс: для плейлиста с лайкнутыми треками его нет. Контроль приватности есть только для пользовательских плейлистов. Интересно. Может быть, нажатие «поделиться» на самом деле никакого изменения в доступах не дало: мне просто выдали текущий адрес?&lt;&#x2F;p&gt;
&lt;p&gt;Я поэкспериментировал со второго аккаунта, и действительно: достаточно скопировать ссылку из адресной строки, она и так доступна без авторизации.&lt;&#x2F;p&gt;
&lt;h1 id=&quot;nu-i-chto&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#nu-i-chto&quot; aria-label=&quot;Anchor link for: nu-i-chto&quot;&gt;Ну и что?&lt;&#x2F;a&gt;&lt;&#x2F;h1&gt;
&lt;p&gt;Ну, да, если смотреть по UX, имеем небольшой IDOR. Наверное, я бы не писал пост, если бы не одна важная деталь. Если вы попробуете поделиться плейлистом из мобильного приложения, а не через веб, ссылка для плейлиста будет совсем иного вида:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo z-code&quot;&gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;https:&#x2F;&#x2F;music.yandex.ru&#x2F;users&#x2F;LOGIN&#x2F;playlists&#x2F;3&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Где &lt;code&gt;LOGIN&lt;&#x2F;code&gt; — это имя аккаунта, указанное при регистрации Яндекс ID в поле «Придумайте логин». Это же имя является логином для почтового ящика Яндекс Почты. Идентификатор «3», кажется, у всех является локальным идентификатором для плейлиста с лайкнутыми треками (попросил проверить у нескольких знакомых).
При переходе по такой ссылке мы попадаем на страницу, после загрузки которой браузер сразу перенаправляет пользователя на адрес обычного вида. Это реализовано «в лоб», через указание тега meta в заголовке страницы:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo z-code&quot;&gt;&lt;code data-lang=&quot;html&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;&amp;lt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-tag&quot;&gt;meta&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-entity&quot;&gt; id&lt;&#x2F;span&gt;&lt;span&gt;=&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;__next-page-redirect&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-entity&quot;&gt; http-equiv&lt;&#x2F;span&gt;&lt;span&gt;=&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;refresh&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-entity&quot;&gt; content&lt;&#x2F;span&gt;&lt;span&gt;=&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;0;url=&#x2F;playlists&#x2F;lk.UID&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;&#x2F;&amp;gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Так что, судя по всему, такая ссылка будет эквивалентна полученной в веб-версии сервиса. Поэтому любой кто знает ваш почтовый адрес &#x2F; логин от вашего аккаунта может посмотреть, какие треки вы на нём лайкали. И я не отыскал ни одного способа это предотвратить.&lt;&#x2F;p&gt;
&lt;h1 id=&quot;zakliuchenie&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#zakliuchenie&quot; aria-label=&quot;Anchor link for: zakliuchenie&quot;&gt;Заключение&lt;&#x2F;a&gt;&lt;&#x2F;h1&gt;
&lt;p&gt;Позже я нашёл и другие статьи на эту тему: вот от 2023, вот от 2024. Оказывается, что похожее поведение было и раньше, однако в настройках тогда была возможность ограничить доступ ко всем плейлистам на аккаунте. Сейчас веб-версия на вид безопаснее, но фактически, проблемы с конфиденциальностью ухудшились: у пользователей веба может создаться иллюзия ограничения доступа по UID, а способа ограничить доступ, пусть даже топорного, теперь нет.&lt;&#x2F;p&gt;
&lt;p&gt;Возможно, многих не волнует, могут ли третьи лица смотреть их любимые треки. Осадочек после этого мини-расследования у меня остался не от того, что нет инструментов контроля конфиденциальности, а от того, что текущий UX неявно вводит пользователя в заблуждение относительно конфиденциальности, особенно если этот пользователь не подкован технически: пользователь совершает отдельные действия, направленные на ручное предоставление своих личных данных, и ему ничего не указывает на то, что эти данные могут быть случайно им распространены.&lt;&#x2F;p&gt;
</content>
        
    </entry>
</feed>
