<?xml version="1.0" encoding="utf-8" ?><rss version="2.0" xmlns:tt="http://teletype.in/" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:media="http://search.yahoo.com/mrss/"><channel><title>Павел Логинов</title><generator>teletype.in</generator><description><![CDATA[👨‍💻 Backend Developer — пишу о технологиях и практиках программирования, делюсь опытом и находками.]]></description><image><url>https://img2.teletype.in/files/95/1e/951eda2e-cef8-440b-bb96-573dc8f553e5.png</url><title>Павел Логинов</title><link>https://teletype.in/@loginovpavel</link></image><link>https://teletype.in/@loginovpavel?utm_source=teletype&amp;utm_medium=feed_rss&amp;utm_campaign=loginovpavel</link><atom:link rel="self" type="application/rss+xml" href="https://teletype.in/rss/loginovpavel?offset=0"></atom:link><atom:link rel="next" type="application/rss+xml" href="https://teletype.in/rss/loginovpavel?offset=10"></atom:link><atom:link rel="search" type="application/opensearchdescription+xml" title="Teletype" href="https://teletype.in/opensearch.xml"></atom:link><pubDate>Tue, 28 Apr 2026 05:51:59 GMT</pubDate><lastBuildDate>Tue, 28 Apr 2026 05:51:59 GMT</lastBuildDate><item><guid isPermaLink="true">https://teletype.in/@loginovpavel/best-practices-alembic-sqlalchemy</guid><link>https://teletype.in/@loginovpavel/best-practices-alembic-sqlalchemy?utm_source=teletype&amp;utm_medium=feed_rss&amp;utm_campaign=loginovpavel</link><comments>https://teletype.in/@loginovpavel/best-practices-alembic-sqlalchemy?utm_source=teletype&amp;utm_medium=feed_rss&amp;utm_campaign=loginovpavel#comments</comments><dc:creator>loginovpavel</dc:creator><title>Хорошие практики Alembic и SQLAlchemy</title><pubDate>Wed, 30 Oct 2024 06:41:36 GMT</pubDate><media:content medium="image" url="https://img3.teletype.in/files/24/84/248468c9-899c-492d-84e3-e35284ae2fd6.png"></media:content><category>Python</category><description><![CDATA[<img src="https://img4.teletype.in/files/b3/93/b393d88a-c2e7-495a-bbc8-f16693366526.jpeg"></img>В этой статье я кратко рассмотрю несколько практик, которые помогут поддерживать порядок в проекте, упростить поддержку базы данных и избежать распространённых ошибок при работе с Alembic и SQLAlchemy. Эти приёмы не раз спасали меня от проблем.]]></description><content:encoded><![CDATA[
  <figure id="bbcq" class="m_column">
    <img src="https://img4.teletype.in/files/b3/93/b393d88a-c2e7-495a-bbc8-f16693366526.jpeg" width="1792" />
  </figure>
  <p id="Tovj">В этой статье я кратко рассмотрю несколько практик, которые помогут поддерживать порядок в проекте, упростить поддержку базы данных и избежать распространённых ошибок при работе с Alembic и SQLAlchemy. Эти приёмы не раз спасали меня от проблем. Вот темы, которые мы затронем:</p>
  <ol id="q6P3">
    <li id="gQ7m">Соглашение об именовании (Naming Convention)</li>
    <li id="5zbS">Сортировка миграций по дате</li>
    <li id="m0g2">Комментарии таблиц, колонок и миграций</li>
    <li id="o1TB">Работа с данными в миграциях без моделей</li>
    <li id="YtV8">Тестирование миграций (Stairway Test)</li>
    <li id="tLmG">Отдельный сервис для проведения миграций</li>
    <li id="hr41">Использование миксинов для моделей</li>
  </ol>
  <hr />
  <h2 id="1-naming-convention-">1. Соглашение об именовании (Naming Convention)</h2>
  <p id="6j3k">SQLAlchemy позволяет задать <a href="https://alembic.sqlalchemy.org/en/latest/naming.html" target="_blank">соглашение об именовании</a>, которое автоматически применится ко всем таблицам и ограничениям при генерации миграций. Это избавляет от необходимости вручную задавать имена индексов, внешних ключей и других ограничений, что упрощает навигацию и делает структуру базы данных предсказуемой и унифицированной.</p>
  <p id="Z9qc">У меня нет рецепта как добавить конвенцию в существующий проект, кроме как перегенерировать миграции. Для нового проекта достаточно добавить соглашение у базового класса, чтобы Alembic автоматически прописывал имена в нужном формате. Пример такого формата ниже, который хорошо подходит большинству случаев:</p>
  <pre id="RWue" data-lang="python">from sqlalchemy import MetaData
from sqlalchemy.orm import DeclarativeBase

convention = {
    &#x27;all_column_names&#x27;: lambda constraint, table: &#x27;_&#x27;.join(
        [column.name for column in constraint.columns.values()]
    ),
    &#x27;ix&#x27;: &#x27;ix__%(table_name)s__%(all_column_names)s&#x27;,
    &#x27;uq&#x27;: &#x27;uq__%(table_name)s__%(all_column_names)s&#x27;,
    &#x27;ck&#x27;: &#x27;ck__%(table_name)s__%(constraint_name)s&#x27;,
    &#x27;fk&#x27;: &#x27;fk__%(table_name)s__%(all_column_names)s__%(referred_table_name)s&#x27;,
    &#x27;pk&#x27;: &#x27;pk__%(table_name)s&#x27;,
}

class BaseModel(DeclarativeBase):
    metadata = MetaData(naming_convention=convention)</pre>
  <hr />
  <h2 id="j4L3">2. Сортировка миграций по дате</h2>
  <p id="wvTy">По умолчанию название файла миграции в alembic начинается с тега миграции. При таком именовании, миграции в директории располагаются в случайном порядке. В некоторых случаях такой порядок неудобен. Удобнее хранить их отсортированными в хронологическом порядке.</p>
  <p id="C9m0">Alembic позволяет изменять шаблон названия файла миграции. Настройка располагается в <code>alembic.ini</code> поле <code>file_template</code>. Для сортировки миграций в директории можно воспользоваться двумя удобными вариантами именования миграций.</p>
  <ol id="oIHX">
    <li id="oRTI">На основе даты: <code>file_template = %%(year)d-%%(month).2d-%%(day).2d_%%(rev)s_%%(slug)s </code></li>
    <li id="gLLq">На основе времени (эпоха Unix): <code>file_template = %%(epoch)d_%%(rev)s_%%(slug)s </code></li>
  </ol>
  <p id="a3Wv">Использование дат или Unix времени в именах файлов помогает держать миграции в упорядоченном виде и легко ориентироваться в них. Я предпочитаю использовать эпохи, далее в пункте 3 будет пример имен файлов.</p>
  <hr />
  <h2 id="Bt0E">3. Комментарии для таблиц и миграций</h2>
  <p id="35NC">Следующий совет для тех, кто работает в команде. В целом хорошая практика комментировать атрибуты классов. Когда работаем с моделями SQLAlchemy, вместо комментариев в docstring, лучше воспользоваться комментариями колонок и таблиц. В этом случае комментарии будут в коде и в СУБД. Комментарии в СУБД могут помочь другим пользователям БД (ДБА или аналитикам) понимать назначение полей и таблиц.</p>
  <pre id="MmAm" data-lang="python">class Event(BaseModel):
    __table_args__ = ({&#x27;comment&#x27;: &#x27;System (service) event&#x27;},)

    id: Mapped[uuid.UUID] = mapped_column(
        UUID(as_uuid=True),
        primary_key=True,
        comment=&#x27;Event ID - PK&#x27;,
    )
    service_id: Mapped[int] = mapped_column(
        sa.Integer,
        sa.ForeignKey(
            f&#x27;{IntegrationServiceModel.__tablename__}.id&#x27;,
            ondelete=&#x27;CASCADE&#x27;,
        ),
        nullable=False,
        comment=&#x27;FK to integration service that owns the event&#x27;,
    )
    name: Mapped[str] = mapped_column(
        sa.String(256), nullable=False, comment=&#x27;Event name&#x27;
    )</pre>
  <p id="hpgD">Также полезно комментировать миграции, чтобы легче было их находить в файловой системе. Комментарий добавляется при помощи <code>-m &lt;comment&gt;</code> при генерации миграции. Комментарий будет в докстринге и в названии файла. Согласитесь, при таком именовании найти нужную миграцию намного легче.</p>
  <pre id="iNqf">1728372261_c0a05e0cd317_add_integration_service.py 
1728372272_a1b4c9df789d_add_user.py
1728372283_f32d57aa1234_update_order_status.py  
1728372294_9c8e7ab45e11_create_payment.py
1728372305_bef657cd9342_remove_old_column_from_users.py</pre>
  <hr />
  <h2 id="ANcm">4. Не используйте модели в миграциях</h2>
  <p id="usJC">Модели часто используют для манипуляций с данными, таких как перенос данных из одной таблицы в другую или изменение значений в колонках. Однако использование ORM-моделей в миграциях может привести к проблемам, если модель изменится после создания миграции. В таких случаях миграция, основанная на старой модели, сломается при её выполнении, так как схема БД может больше не соответствовать актуальной модели.</p>
  <p id="hth2">Миграции должны быть статичными и не зависеть от текущего состояния моделей, чтобы обеспечить правильное выполнение независимо от изменений в коде. Далее представлены два способа, как можно избежать использования моделей для манипуляций с данными.</p>
  <p id="roal"><strong>Используйте сырой SQL для манипуляций с данными:</strong></p>
  <pre id="iQPV" data-lang="python">def upgrade():
    op.execute(
        &quot;UPDATE user_account SET email = CONCAT(username, &#x27;@example.com&#x27;) WHERE email IS NULL;&quot;
    )

def downgrade():
    op.execute(
        &quot;UPDATE user_account SET email = NULL WHERE email LIKE &#x27;%@example.com&#x27;;&quot;
    )</pre>
  <p id="OgBl"><strong>Определяйте таблицы непосредственно в миграции:</strong> Если вы хотите использовать SQLAlchemy для манипуляций с данными, можно определить таблицы вручную прямо в миграции. Это обеспечит статичность схемы на момент выполнения миграции и не будет зависеть от изменений в моделях.</p>
  <pre id="st5a" data-lang="python">from sqlalchemy import table, column, String

def upgrade():
    # Определяем таблицу user_account для работы с данными
    user_account = table(
        &#x27;user_account&#x27;,
        column(&#x27;id&#x27;),
        column(&#x27;username&#x27;, String),
        column(&#x27;email&#x27;, String)
    )

    # Получаем соединение с базой данных
    conn = op.get_bind()

    # Выбираем всех пользователей без email
    users = conn.execute(
        user_account.select().where(user_account.c.email == None)
    )

    # Обновляем email для каждого пользователя
    for user in users:
        conn.execute(
            user_account.update().where(user_account.c.id == user.id).values(email=f&quot;{user.username}@example.com&quot;)
        )

def downgrade():
    user_account = table(
        &#x27;user_account&#x27;,
        column(&#x27;id&#x27;),
        column(&#x27;email&#x27;, String)
    )

    conn = op.get_bind()
    # Удаляем email для пользователей, добавленных в upgrade
    conn.execute(
        user_account.update().where(user_account.c.email.like(&#x27;%@example.com&#x27;)).values(email=None)
    )</pre>
  <hr />
  <h2 id="9Sto">5. Тестирование миграций лесенкой (Stairway Test)</h2>
  <p id="lv7P">Stairway test — это методика, при которой проверяются постепенное применение миграций лесенкой <code>upgrade/downgrade</code>. Это проверит миграции на то, что миграции могут создать новую БД с нуля и то, что любую миграцию можно откатить без проблем, т.е. проверяется работоспособность downgrade. Если работаете в команде, добавьте этот тест в CI, спасёт нервы и время.</p>
  <figure id="taaD" class="m_column">
    <img src="https://img1.teletype.in/files/87/d7/87d7bfec-f045-406f-aac2-6609eec5717b.png" width="967" />
    <figcaption>Последовательность выполнения миграций при Stairway тестировании</figcaption>
  </figure>
  <p id="3KHO">Тест внедряется довольно легко и быстро. Оставляю <a href="https://github.com/alvassin/alembic-quickstart" target="_blank">ссылку на репозиторий</a>, где можно найти пример кода. Также там есть другие полезные тесты миграций.</p>
  <hr />
  <h2 id="XXoi">6. Сервис миграций</h2>
  <p id="xOwg">Отдельный сервис для совершения миграций. Это просто один из способов производить миграции. При разработке локально или в окружениях приближенных к разработке этот метод вписывается хорошо. Напоминаю про <a href="https://docs.docker.com/compose/how-tos/startup-order/" target="_blank">условный <code>depends_on</code></a>, который здесь к месту. Берём образ приложения с alembic и запускаем в отдельном контейнере. Добавляем зависимость от БД с условием, что миграции начинаются, только когда БД способен обрабатывать запросы (<code>service_healthy</code>). Также можно добавить условный <code>depends_on</code> (<code>service_completed_successfully</code>) на приложение, чтобы оно запускалось после успешного завершения миграций.</p>
  <pre id="xfzM" data-lang="yaml">  db:
    image: postgres:15
    ...
    healthcheck:
      test: [&quot;CMD-SHELL&quot;, &quot;pg_isready -U ${POSTGRES_USER} -d ${POSTGRES_DB}&quot;]
      interval: 10s
      start_period: 10s

  app_migrations:
    image: &lt;app-image&gt;
    command: [
      &quot;python&quot;,
      &quot;-m&quot;,
      &quot;alembic&quot;,
      &quot;-c&quot;,
      &quot;&lt;path&gt;/alembic.ini&quot;,
      &quot;upgrade&quot;,
      &quot;head&quot;
    ]
    depends_on:
      db:
        condition: service_healthy

  app:
    ...
    depends_on:
      app_migrations:
        condition: service_completed_successfully</pre>
  <p id="BtFV">Использование условного <code>depends_on</code> гарантирует, что миграции начнутся только после того, как база данных будет полностью готова к работе, а приложение запустится после завершения миграций.</p>
  <hr />
  <h2 id="7-">7. Миксины для моделей</h2>
  <p id="N6eI">Да, это очевидный пункт, но всё же напомню, чтобы никто не забыл. Использование миксинов — удобный способ избежать дублирования кода. Миксины — это классы с часто используемыми полями и методами, которые можно подключить к любым моделям, где они нужны. Например, часто нам нужны поля <code>created_at</code> и <code>updated_at</code> для отслеживания времени создания и обновления записей. Также бывает полезно использовать <code>id</code> на основе UUID, чтобы унифицировать первичные ключи. Всё это можно вынести в миксины.</p>
  <pre id="i1o3" data-lang="python">import uuid
from sqlalchemy import Column, DateTime, func
from sqlalchemy.dialects.postgresql import UUID

class TimestampMixin:
    created_at = Column(
        DateTime,
        server_default=func.now(),
        nullable=False,
        comment=&quot;Время создания записи&quot;
    )
    updated_at = Column(
        DateTime,
        onupdate=func.now(),
        nullable=True,
        comment=&quot;Время последнего обновления записи&quot;
    )

class UUIDPrimaryKeyMixin:
    id = Column(
        UUID(as_uuid=True),
        primary_key=True,
        default=uuid.uuid4,
        comment=&quot;Уникальный идентификатор записи&quot;
    )</pre>
  <p id="e5rq">Теперь, когда у нас есть миксины, можем подключить их к любой модели, где требуется и UUID <code>id</code>, и метки времени:</p>
  <pre id="FakI" data-lang="python">class User(UUIDPrimaryKeyMixin, TimestampMixin, BaseModel):
    __tablename__ = &#x27;user&#x27;
    # Другие столбцы...</pre>
  <hr />
  <h2 id="Mdks">Заключение</h2>
  <p id="NHBP">Работа с миграциями может стать настоящей головной болью, но, если соблюдать несколько простых правил, поддерживать порядок в проекте гораздо легче. Соглашения об именовании, сортировка по дате, комментарии и тестирование миграций не раз спасали нас от хаоса и помогали не совершать ошибки. Надеюсь, эта статья оказалась полезной — поделитесь своими практиками работы с миграциями в комментариях!</p>

]]></content:encoded></item><item><guid isPermaLink="true">https://teletype.in/@loginovpavel/wait-for-service</guid><link>https://teletype.in/@loginovpavel/wait-for-service?utm_source=teletype&amp;utm_medium=feed_rss&amp;utm_campaign=loginovpavel</link><comments>https://teletype.in/@loginovpavel/wait-for-service?utm_source=teletype&amp;utm_medium=feed_rss&amp;utm_campaign=loginovpavel#comments</comments><dc:creator>loginovpavel</dc:creator><title>Ожидание готовности сервисов в Docker Compose: wait-for-it vs Healthcheck</title><pubDate>Sat, 12 Oct 2024 08:49:25 GMT</pubDate><media:content medium="image" url="https://img4.teletype.in/files/f2/8d/f28dea12-1e76-4a89-a37d-5b6e08b41041.png"></media:content><category>Docker</category><description><![CDATA[<img src="https://img4.teletype.in/files/f1/0a/f10ad648-a59f-432b-8b20-99c2b6853649.jpeg"></img>При разработке приложений с помощью Docker Compose часто возникает ситуация, когда один сервис должен дождаться готовности другого перед началом своей работы. Например, веб-приложению может понадобиться дождаться запуска базы данных или другого зависимого сервиса. В этой статье мы рассмотрим два популярных способа решения этой задачи:]]></description><content:encoded><![CDATA[
  <figure id="oKSO" class="m_column">
    <img src="https://img4.teletype.in/files/f1/0a/f10ad648-a59f-432b-8b20-99c2b6853649.jpeg" width="1434" />
  </figure>
  <p id="wI2b">При разработке приложений с помощью Docker Compose часто возникает ситуация, когда один сервис должен дождаться готовности другого перед началом своей работы. Например, веб-приложению может понадобиться дождаться запуска базы данных или другого зависимого сервиса. В этой статье мы рассмотрим два популярных способа решения этой задачи:</p>
  <ol id="CuIv">
    <li id="jKOD">Скрипт <code>wait-for-it.sh</code></li>
    <li id="Dscy">Использование <code>depends_on</code> и <code>healthchecks</code> в Docker Compose</li>
  </ol>
  <p id="jB5Y">Для демонстрации мы создадим простое приложение на Python, но для понимания статьи знание Python не требуется — вы сможете просто скопировать код.</p>
  <h2 id="-">Настройка рабочего окружения</h2>
  <p id="aJIz">Для начала создадим рабочую директорию:</p>
  <pre id="XzWJ" data-lang="bash">mkdir waitforit
cd waitforit</pre>
  <p id="l2jF">Теперь создадим тестовое веб-приложение на базе <code>aiohttp</code>, которое будет включать эндпоинт <code>/healthz</code> для проверки состояния. Также добавим возможность задержки перед запуском через переменную окружения <code>SLEEP_BEFORE_START</code>. Эта переменная позволит имитировать задержку старта сервиса. Чтобы сымитировать задержку старта сервиса, будем передавать в эту переменную количество секунд на которое надо задержаться перед запуском приложения.</p>
  <p id="Sq43"><strong>Файл:</strong> <code>app.py</code></p>
  <pre id="LP9T" data-lang="python">import os
import time
from aiohttp import web

async def healthz(request):
    return web.Response(text=&quot;OK&quot;)

if __name__ == &quot;__main__&quot;:
    sleep_time = int(os.getenv(&quot;SLEEP_BEFORE_START&quot;, 0))
    print(f&quot;Sleep {sleep_time} sec before start...&quot;)
    time.sleep(sleep_time)

    app = web.Application()
    app.add_routes([web.get(&quot;/healthz&quot;, healthz)])
    print(&quot;Starting...&quot;)
    web.run_app(app, host=&quot;0.0.0.0&quot;, port=8080)</pre>
  <p id="qMoC">Теперь создадим <code>Dockerfile</code> для нашего приложения.</p>
  <p id="phbr"><strong>Файл:</strong> <code>Dockerfile</code></p>
  <pre id="QpXj" data-lang="dockerfile">FROM python:3.9-slim

WORKDIR /app
RUN pip install aiohttp \
    &amp;&amp; apt-get update \
    &amp;&amp; apt-get install -y curl
COPY app.py .
CMD [&quot;python&quot;, &quot;-u&quot;, &quot;app.py&quot;]</pre>
  <p id="iSIa">Теперь создадим <code>docker-compose.yml</code>, который будет запускать два сервиса:</p>
  <ol id="e6u0">
    <li id="Bc9R"><strong>web</strong> — наше веб-приложение с задержкой старта.</li>
    <li id="Du1q"><strong>dependent</strong> — сервис, который должен ждать, пока <strong>web</strong> станет доступным.</li>
  </ol>
  <p id="Dan3"><strong>Файл:</strong> <code>docker-compose.yml</code></p>
  <pre id="Ck0p" data-lang="yaml">version: &quot;3.8&quot;

services:
  web:
    image: sleep-web-app
    build: .
    environment:
      SLEEP_BEFORE_START: 10

  dependent:
    image: sleep-web-app
    depends_on:
      - web</pre>
  <p id="YOf5">Запустим контейнеры и посмотрим на результат:</p>
  <pre id="zaXY" data-lang="bash">docker compose up --build</pre>
  <p id="vmq5">Результат покажет, что сервис <strong>dependent</strong> начнёт запуск до того, как сервис <strong>web</strong> будет готов:</p>
  <pre id="AD51">web-1        | Sleep 10 sec before start...
dependent-1  | Sleep 0 sec before start...
dependent-1  | Starting...
web-1        | Starting...</pre>
  <p id="3mJv">Наша задача — настроить Docker Compose так, чтобы сервис <strong>dependent</strong> начинал работу <strong>только после того, как сервис web будет полностью готов</strong>. В результате хотим получить такой лог:</p>
  <pre id="YnmE">web-1        | Sleep 10 sec before start...
web-1        | Starting...
dependent-1  | Sleep 0 sec before start...
dependent-1  | Starting...</pre>
  <h2 id="-wait-for-it-sh">Использование wait-for-it.sh</h2>
  <p id="rLHH"><code>wait-for-it.sh</code> — это простой Bash-скрипт, который блокирует выполнение сервиса до тех пор, пока другой сервис не станет доступен, проверяя доступность конкретного порта. Это удобно для проверки готовности баз данных, веб-сервисов или других TCP-сервисов.</p>
  <p id="Tv9S">Скрипт надо запустить до основной команды запуска приложения. Это можно сделать в <code>entrypoint</code> или непосредственно подставить перед командой запуска. В нашем примере воспользуемся вторым способом для простоты.</p>
  <p id="1X4c">В качестве аргументов достаточно передать <code>host:port</code>, доступность которого ожидаем. Тогда скрипт подождёт 15 секунд (значение по умолчанию). Чтобы задать своё время ожидания, можно добавить аргумент <code>--timeout &lt;sec&gt;</code>. Если за это время порт станет доступным, то скрипт остановится и сервис продолжит запуск, но если <code>host:port</code> окажется недоступным по истечении таймаута сервис всё равно начнёт запуск. Чтобы этого избежать и остановить запуск с ошибочным статусом, необходимо передать флаг <code>--strict</code>.</p>
  <p id="OjpQ">Давайте скачаем скрипт с GitHub и попробуем им воспользоваться:</p>
  <pre id="UZ4w" data-lang="bash">curl -o wait-for-it.sh https://raw.githubusercontent.com/vishnubob/wait-for-it/master/wait-for-it.sh
chmod +x wait-for-it.sh</pre>
  <p id="M57P">Добавим к сервису <strong>dependent</strong> волюм, через который прокинем скрипт. И добавим команду запуска приложения с использованием <code>wait-for-it.sh</code>. Скрипт будет ожидать готовность <strong>web</strong> сервиса на порту <code>8080</code>. Основная команда запуска сервиса выполняется после <code>--</code>. Вы можете поиграть с параметрами и различными сценариями запуска. Мы рассмотрим пример, когда мы примерно понимаем сколько надо ждать web сервис (10 секунд) и оставим таймаут по умолчанию (15 секунд).</p>
  <pre id="z2EU" data-lang="yaml">  dependent:
    image: sleep-web-app
    command:
      - ./wait-for-it.sh
      - web:8080
      - --
      - python
      - -u
      - app.py
    volumes:
      - ./wait-for-it.sh:/app/wait-for-it.sh</pre>
  <p id="Ibhr">Теперь запустим наши сервисы:</p>
  <pre id="tH4G" data-lang="bash">docker-compose up</pre>
  <p id="Ww0c">Из лога видно, что сервис <strong>dependent</strong> начинает работу с запуска <code>wait-for-it.sh</code> и ожидания <code>web:8080</code>. После того как <strong>web</strong> сервис запускается, <strong>dependent</strong> останавливает <code>wait-for-it.sh</code>, пишет сколько он прождал и выполняет команду, указанную после <code>--</code> слешей. Мы добились необходимого поведения: зависимый сервис запускается после того, как другой сервис готов принимает соединения на своём порту.</p>
  <pre id="nRGQ">dependent-1  | wait-for-it.sh: waiting 15 seconds for web:8080
web-1        | Sleep 10 sec before start...
web-1        | Starting...
dependent-1  | wait-for-it.sh: web:8080 is available after 11 seconds
dependent-1  | Sleep 0 sec before start...
dependent-1  | Starting...</pre>
  <h4 id="-wait-for-it-sh-">Преимущества <code>wait-for-it.sh</code>:</h4>
  <ul id="ieuH">
    <li id="b6iX">Быстро внедрить: достаточно скачать и добавить запуск перед основной командой.</li>
    <li id="dicL">Гибкость: можно настроить ожидание для любого порта на любом сервисе, даже для внешних сервисов вне композа.</li>
    <li id="9jdF">Простота: нет необходимости делать дополнительные конфигурации.</li>
  </ul>
  <h4 id="-">Недостатки:</h4>
  <ul id="SRai">
    <li id="2cU9"><code>wait-for-it.sh</code> проверяет только доступность порта, но не проверяет полную готовность сервиса (например, не проверяет завершение инициализации схемы базы данных). Часто бывает, что порт уже открыт, а сервис не готов обрабатывать входящие запросы. Эту проблему поможет решить следующий способ.</li>
  </ul>
  <h2 id="-depends_on-healthcheck-">Использование <code>depends_on</code> и <code>healthcheck</code></h2>
  <p id="LLzn">Еще один способ заставить один сервис ожидать готовности другого — это использование условной опции <code>depends_on</code> и механизма <code>healthcheck</code> в Docker Compose.</p>
  <p id="eVdm">В этом подходе сервис должен иметь <code>healthcheck</code>, который проверяет готовность сервиса обрабатывать запросы, а зависимый сервис иметь <code>depends_on</code> с условием на <code>healthcheck</code> первого. Простыми словами, зависимый сервис запускается, когда первый сервис здоров (готов обрабатывать запросы).</p>
  <p id="a2tz">Добавим <code>healthcheck</code> для сервиса <strong>web</strong>, чтобы проверять его готовность через эндпоинт <code>/healthz</code>. Тем самым, мы будем знать, что у сервиса не просто открыт порт, но и то, что сервис готов обрабатывать запросы. Подробнее про <code>healthcheck</code> <a href="https://www.warp.dev/terminus/docker-compose-health-check" target="_blank">можно прочитать здесь</a>. В <code>test</code> задаётся команда, которая проверяет здоровье сервиса. Команда могла бы проверять просто открытость порта, тогда это был бы эквивалент <code>wait-for-it.sh</code>. Но суть <code>healthcheck</code> не просто проверить порт, а готовность сервиса обработать запросы.</p>
  <pre id="vxIL" data-lang="yaml">  web:
    image: sleep-web-app
    build: .
    environment:
      SLEEP_BEFORE_START: 10
    healthcheck:
      test: [&quot;CMD&quot;, &quot;curl&quot;, &quot;http://web:8080/healthz&quot;]
      interval: 10s # Как часто будет проверяться статус
      retries: 5 # Сколько раз проверить, прежде чем считать недоступным
      start_period: 10s # Через сколько после запуска начинать проверки
      timeout: 10s # Таймаут на каждый запуск test</pre>
  <p id="dNJd">К зависимому сервису добавим <code>depends_on</code> с условием. Сервис <strong>dependent</strong> зависит от <strong>web</strong>, но будет запускаться только после того, как сервис <strong>web</strong> успешно пройдет проверку состояния. Больше про условный <code>depends_on</code> <a href="https://docs.docker.com/reference/compose-file/services/#depends_on" target="_blank">можно прочитать здесь</a>. Надо упомянуть, что не все версии композа поддерживают условный <code>depends_on</code>.</p>
  <p id="PyAr">Финальный композ:</p>
  <pre id="8a1l" data-lang="yaml">version: &quot;3.8&quot;

services:
  web:
    image: sleep-web-app
    build: .
    environment:
      SLEEP_BEFORE_START: 10
    healthcheck:
      test: [&quot;CMD&quot;, &quot;curl&quot;, &quot;http://web:8080/healthz&quot;]
      interval: 10s
      retries: 5
      start_period: 10s
      timeout: 10s

  dependent:
    image: sleep-web-app
    depends_on:
      web:
        condition: service_healthy</pre>
  <p id="quzr">Теперь снова запускаем наши сервисы:</p>
  <pre id="ejo4" data-lang="bash">docker-compose up</pre>
  <p id="fgT4">На этот раз сервис <strong>dependent</strong> начнет выполнение только после того, как проверка состояния сервиса <strong>web</strong> через <code>/healthz</code> пройдет успешно.</p>
  <pre id="dChT">web-1        | Sleep 10 sec before start...
web-1        | Starting...
dependent-1  | Sleep 0 sec before start...
dependent-1  | Starting...</pre>
  <p id="HbHZ">Популярные сервисы имеют готовые решения для <code>healthcheck</code>:</p>
  <pre id="CHvi" data-lang="yaml">postgresql: [&quot;CMD&quot;, &quot;pg_isready&quot;, &quot;-U&quot;, &quot;postgres&quot;]
kafka: [&quot;CMD-SHELL&quot;, &quot;kafka-broker-api-versions.sh --bootstrap-server localhost:9092&quot;]
redis: [&quot;CMD&quot;, &quot;redis-cli&quot;, &quot;ping&quot;]
mysql: [&quot;CMD&quot;, &quot;mysqladmin&quot;, &quot;ping&quot;, &quot;-h&quot;, &quot;localhost&quot;]
mongodb: [&quot;CMD&quot;, &quot;mongo&quot;, &quot;--eval&quot;, &quot;db.adminCommand(&#x27;ping&#x27;)&quot;]
elasticsearch: [&quot;CMD-SHELL&quot;, &quot;curl -f http://localhost:9200/_cluster/health || exit 1&quot;]</pre>
  <h4 id="-depends_on-healthcheck-">Преимущества <code>depends_on</code> + <code>healthcheck</code>:</h4>
  <ul id="ZvAL">
    <li id="6GlP">Проверки состояния дают более полное представление о готовности сервиса по сравнению с простой проверкой порта.</li>
    <li id="bub8">Это встроенное решение Docker Compose, что упрощает его использование без дополнительных скриптов.</li>
    <li id="Sm28">Легче настраивать цепочки зависимостей.</li>
  </ul>
  <h4 id="-">Недостатки:</h4>
  <ul id="xIjQ">
    <li id="lbvC">Более объемная конфигурация, которая может быть менее гибкой для сервисов, не основанных на TCP.</li>
    <li id="cq82">Не все образы поддерживают проверки состояния по умолчанию, возможно, потребуется писать свои скрипты.</li>
    <li id="D7qA">Сложность с проверкой готовности внешних сервисов, вне композа.</li>
  </ul>
  <h2 id="-">Какой метод выбрать?</h2>
  <p id="whq1">Выбор между <code>wait-for-it.sh</code> и <code>depends_on</code> с <code>healthcheck</code> зависит от вашего конкретного случая:</p>
  <ul id="WUGu">
    <li id="MaE7"><strong>Используйте <code>wait-for-it.sh</code></strong>, если вам нужно быстрое и простое решение для ожидания доступности порта, особенно в тех средах, где вы имеете больший контроль над зависимостями.</li>
    <li id="KkcU"><strong>Используйте <code>depends_on</code> + <code>healthcheck</code></strong>, если вам нужно более надежное, встроенное решение Docker Compose, которое гарантирует полную готовность сервиса, а не только его доступность.</li>
  </ul>
  <h2 id="-">Заключение</h2>
  <p id="I0MC">Управление готовностью сервисов в Docker Compose — важный аспект создания надежных многосервисных приложений. Независимо от того, выберете ли вы простоту <code>wait-for-it.sh</code> или встроенные возможности <code>depends_on</code> с проверками состояния, оба метода помогают обеспечить, что ваши сервисы ожидают полной готовности зависимостей перед запуском. Правильный выбор стратегии позволит избежать потенциальных проблем и сделать запуск сервисов более плавным в ваших Docker-приложениях.</p>

]]></content:encoded></item><item><guid isPermaLink="true">https://teletype.in/@loginovpavel/django-pagination</guid><link>https://teletype.in/@loginovpavel/django-pagination?utm_source=teletype&amp;utm_medium=feed_rss&amp;utm_campaign=loginovpavel</link><comments>https://teletype.in/@loginovpavel/django-pagination?utm_source=teletype&amp;utm_medium=feed_rss&amp;utm_campaign=loginovpavel#comments</comments><dc:creator>loginovpavel</dc:creator><title>Удобная Пагинация в Django</title><pubDate>Sun, 31 Dec 2023 18:12:30 GMT</pubDate><media:content medium="image" url="https://img2.teletype.in/files/55/aa/55aaf3e3-f42f-4fe7-8d75-437f7e21df99.png"></media:content><category>Django</category><description><![CDATA[<img src="https://img2.teletype.in/files/19/48/19480da8-45c7-4fca-95ff-e32f87af6526.png"></img>Рассмотрим один из удачных примеров постраничной пагинации в Django, используя класс Paginator. Такая пагинация удобна для пользователя и обычно подходит для большинства случаев.]]></description><content:encoded><![CDATA[
  <p id="BquR">Пагинация — это распространенная практика веб-разработки, используемая для разделения больших наборов данных на меньшие и более управляемые части, отображаемые на нескольких страницах.</p>
  <p id="xo72">Рассмотрим один из удачных примеров постраничной пагинации в Django, используя класс <code><strong>Paginator</strong></code>. Такая пагинация удобна для пользователя и обычно подходит для большинства случаев.</p>
  <figure id="wmzL" class="m_column">
    <img src="https://img1.teletype.in/files/0b/2d/0b2db6a0-dbb3-4193-87df-5a854c8f84d5.jpeg" width="859" />
    <figcaption>Интерфейс пагинатора</figcaption>
  </figure>
  <p id="orEl">Этот вид пагинации позволяет:</p>
  <ul id="D10V">
    <li id="b87C">перемещаться на следующую и предыдущую страницы с помощью кнопок <strong>Prev</strong> и <strong>Next</strong>;</li>
    <li id="3V0p">перемещаться на первую и последнюю страницы с любой страницы;</li>
    <li id="FGuX">перемещаться через одну страницу.</li>
  </ul>
  <h2 id="-django">Пагинация в Django</h2>
  <p id="362E">Вы можете ознакомиться с подробной информацией о пагинации в Django по следующим ссылкам:</p>
  <ul id="XrrT">
    <li id="eJR4"><a href="https://docs.djangoproject.com/en/5.0/topics/pagination/" target="_blank">Documentation: Pagination</a></li>
    <li id="69b1"><a href="https://realpython.com/django-pagination/" target="_blank">Pagination for a User-Friendly Django App</a></li>
  </ul>
  <p id="Dtwg">Кратко напомню, что пагинация в Django осуществляется с помощью классов <code>Paginator</code> и <code>Page</code><strong>.</strong> <code>Paginator</code> разбивает большой набор данных на страницы и принимает в качестве параметров набор объектов (<code>list</code>, <code>tuple</code> или <code>QuerySet</code>) и размер страницы. <code>Page</code> представляет текущую страницу в пагинации и содержит объекты страницы и информацию о соседних страницах.</p>
  <p id="s19H">Пагинатор можно создавать вручную или автоматически с использованием Class-Based Views. В шаблон передается объект страницы (<code>page_obj</code>), который содержит объекты, информацию о страницах и сам пагинатор.</p>
  <p id="3xm7"><strong>Используемые атрибуты страницы в рассматриваемой пагинации:</strong></p>
  <ul id="fOy7">
    <li id="ccTe"><code>page_obj.has_previous</code>: проверяет, есть ли предыдущая страница.</li>
    <li id="RvAE"><code>page_obj.has_next</code>: проверяет, есть ли следующая страница.</li>
    <li id="4vCS"><code>page_obj.previous_page_number</code>: возвращает номер предыдущей страницы.</li>
    <li id="Nkk4"><code>page_obj.next_page_number</code>: возвращает номер следующей страницы.</li>
    <li id="u2cZ"><code>page_obj.number</code>: возвращает номер текущей страницы.</li>
    <li id="HzAA"><code>page_obj.paginator</code>: возвращает экземпляр класса <code>Paginator</code>.</li>
    <li id="8SSR"><code>page_obj.paginator.num_pages</code>: возвращает общее количество страниц.</li>
    <li id="CEEf"><code>page_obj.paginator.page_range</code>: возвращает диапазон страниц.</li>
  </ul>
  <p id="Hfyr">Также в рассматриваемой пагинации используется фильтр шаблонов <strong>add</strong>, который позволяет прибавлять значения к переменным прямо в шаблоне.</p>
  <h2 id="user-fiendly-">User-Fiendly пагианция</h2>
  <p id="nZsf">На скриншоте показано как будет выглядеть пагинатор на различных страницах. Пагинатор всегда отображает крайние страницы и две соседние страницы, если они существуют. Также отображаются кнопки <strong>Prev</strong> и <strong>Next</strong>, если текущая страница не является крайней.</p>
  <figure id="AAjr" class="m_column">
    <img src="https://img2.teletype.in/files/19/48/19480da8-45c7-4fca-95ff-e32f87af6526.png" width="1080" />
    <figcaption>Пагинатор на разных страницах</figcaption>
  </figure>
  <p id="AtWW">В следующем листинге представлена реализация этой пагинации. Просто вставьте этот код в свой проект, и скорее всего, все должно работать. Далее мы рассмотрим этот листинг подробнее.</p>
  <pre id="aLO6" data-lang="html">{% if page_obj.has_previous %}
  &lt;li&gt;&lt;a href=&quot;?page={{ page_obj.previous_page_number }}&quot;&gt;(Prev)&lt;/a&gt;&lt;/li&gt;

  {% if page_obj.number &gt; 3 %}
      &lt;li&gt;&lt;a href=&quot;?page=1&quot;&gt;(1)&lt;/a&gt;&lt;/li&gt;
    {% if page_obj.number &gt; 4 %}
      &lt;li&gt;&lt;a &gt;...&lt;/a&gt;&lt;/li&gt;
    {% endif %}
  {% endif %}
{% endif %}

{% for num in page_obj.paginator.page_range %}
  {% if page_obj.number == num %}
      &lt;li&gt;&lt;a href=&quot;?page={{ num }}&quot;&gt;({{ num }})&lt;/a&gt;&lt;/li&gt;
  {% elif num &gt; page_obj.number|add:&#x27;-3&#x27; and num &lt; page_obj.number|add:&#x27;3&#x27; %}
      &lt;li&gt;&lt;a href=&quot;?page={{ num }}&quot;&gt;({{ num }})&lt;/a&gt;&lt;/li&gt;
  {% endif %}
{% endfor %}

{% if page_obj.has_next %}
  {% if page_obj.number &lt; page_obj.paginator.num_pages|add:&#x27;-3&#x27; %}
    &lt;li&gt;&lt;a &gt;...&lt;/a&gt;&lt;/li&gt;
    &lt;li&gt;&lt;a href=&quot;?page={{ page_obj.paginator.num_pages }}&quot;&gt;
      ({{ page_obj.paginator.num_pages }})
    &lt;/a&gt;&lt;/li&gt;
  {% elif page_obj.number &lt; page_obj.paginator.num_pages|add:&#x27;-2&#x27; %}
    &lt;li&gt;&lt;a href=&quot;?page={{ page_obj.paginator.num_pages }}&quot;&gt;
      ({{ page_obj.paginator.num_pages }})
    &lt;/a&gt;&lt;/li&gt;
  {% endif %}

  &lt;li&gt;&lt;a href=&quot;?page={{ page_obj.next_page_number }}&quot;&gt;(Next)&lt;/a&gt;&lt;/li&gt;
{% endif %}
</pre>
  <p id="Qfmk">Этот фрагмент кода является шаблоном Django для создания элемента управления пагинацией. Давайте разберем, что делает каждая часть этого кода:</p>
  <p id="X6Pz"><strong>1. Ссылка на Предыдущую Страницу</strong>:</p>
  <pre id="tHEm" data-lang="html">{% if page_obj.has_previous %}
  &lt;li&gt;&lt;a href=&quot;?page={{ page_obj.previous_page_number }}&quot;&gt;(Prev)&lt;/a&gt;&lt;/li&gt;
</pre>
  <p id="Wwic">Если <code>page_obj.has_previous</code> равно <code>True</code>, он показывает кнопку <strong>Prev</strong> на предыдущую страницу. Переменная <code>page_obj.previous_page_number</code> содержит номер предыдущей страницы.</p>
  <p id="0X9Z"><strong>2. Ссылки на Первые Страницы и Многоточие</strong>:</p>
  <pre id="OkaS" data-lang="html">{% if page_obj.number &gt; 3 %}
    &lt;li&gt;&lt;a href=&quot;?page=1&quot;&gt;(1)&lt;/a&gt;&lt;/li&gt;
  {% if page_obj.number &gt; 4 %}
    &lt;li&gt;&lt;a &gt;...&lt;/a&gt;&lt;/li&gt;
  {% endif %}
{% endif %}
</pre>
  <p id="nC0D">Эти строки кода обрабатывают отображение ссылки на первую страницу и многоточие (<code>...</code>), если текущий номер страницы больше 3 (или 4 для многоточия).</p>
  <p id="SFYR"><strong>3. Текущая Страница и Ссылки на Соседние Страницы</strong>:</p>
  <pre id="xWxP" data-lang="html">{% for num in page_obj.paginator.page_range %}
  {% if page_obj.number == num %}
      &lt;li&gt;&lt;a href=&quot;?page={{ num }}&quot;&gt;({{ num }})&lt;/a&gt;&lt;/li&gt;
  {% elif num &gt; page_obj.number|add:&#x27;-3&#x27; and num &lt; page_obj.number|add:&#x27;3&#x27; %}
      &lt;li&gt;&lt;a href=&quot;?page={{ num }}&quot;&gt;({{ num }})&lt;/a&gt;&lt;/li&gt;
  {% endif %}
{% endfor %}
</pre>
  <p id="DV12">Этот цикл проходит через диапазон страниц в пагинаторе. Он проверяет два условия:</p>
  <ul id="2t5i">
    <li id="ZVk1">Если номер страницы (<code>num</code>) является текущей страницей (<code>page_obj.number</code>), он создает для нее ссылку. Обычно это оформляется по-разному, чтобы указать на текущую страницу.</li>
    <li id="HE4n">Если номер страницы находится в пределах трех страниц до или после текущей страницы, он также создает ссылки на эти страницы. Таким образом, пользователь может переходить к ближайшим двум страницам, не просматривая весь диапазон.</li>
  </ul>
  <p id="2o5w"><strong>4. Многоточие и Ссылка на Последнюю Страницу</strong>:</p>
  <pre id="6Ged" data-lang="html">{% if page_obj.has_next %}
{% if page_obj.number &lt; page_obj.paginator.num_pages|add:&#x27;-3&#x27; %}
&lt;li&gt;&lt;a &gt;...&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;?page={{ page_obj.paginator.num_pages }}&quot;&gt;
  ({{ page_obj.paginator.num_pages }})
&lt;/a&gt;&lt;/li&gt;
{% elif page_obj.number &lt; page_obj.paginator.num_pages|add:&#x27;-2&#x27; %}
&lt;li&gt;&lt;a href=&quot;?page={{ page_obj.paginator.num_pages }}&quot;&gt;
  ({{ page_obj.paginator.num_pages }})
&lt;/a&gt;&lt;/li&gt;
{% endif %}
</pre>
  <p id="mMYJ">Эта часть похожа на второй раздел, но для конца диапазона пагинации. Она отображает многоточие и ссылку на последнюю страницу, если текущая страница находится на нескольких страницах от последней.</p>
  <p id="KNfN"><strong>5. Ссылка на Следующую Страницу</strong>:</p>
  <pre id="BEIw" data-lang="html">{% if page_obj.has_next %}
...  
&lt;li&gt;&lt;a href=&quot;?page={{ page_obj.next_page_number }}&quot;&gt;(Next)&lt;/a&gt;&lt;/li&gt;
{% endif %}
</pre>
  <p id="kI1d">Аналогично первой части, только кнопка для перехода на следующую страницу.</p>
  <hr />
  <h2 id="-"><strong>Заключение</strong></h2>
  <p id="37sW">Мы рассмотрели пример простой и интуитивно понятной постраничной пагинации в Django, которая позволяет пользователям легко перемещаться по страницам и получать нужную информацию.</p>
  <p id="w5JH">Реализация пагинации может быть адаптирована под ваши потребности, и вы можете внести изменения в код, чтобы соответствовать требованиям вашего проекта. Удачи в использовании пагинации в ваших Django приложениях!</p>

]]></content:encoded></item><item><guid isPermaLink="true">https://teletype.in/@loginovpavel/python-typing</guid><link>https://teletype.in/@loginovpavel/python-typing?utm_source=teletype&amp;utm_medium=feed_rss&amp;utm_campaign=loginovpavel</link><comments>https://teletype.in/@loginovpavel/python-typing?utm_source=teletype&amp;utm_medium=feed_rss&amp;utm_campaign=loginovpavel#comments</comments><dc:creator>loginovpavel</dc:creator><title>Типизация в Python: Сильно, Динамично, Неявно</title><pubDate>Tue, 07 Nov 2023 20:06:04 GMT</pubDate><media:content medium="image" url="https://img3.teletype.in/files/eb/b4/ebb4bc43-b6c2-4f26-88fc-5c255854b373.png"></media:content><category>Python</category><description><![CDATA[<img src="https://img3.teletype.in/files/25/17/25170f7b-5ecc-4c78-aff3-df01a571bb04.png"></img>Все знают, что в Python типы данных делятся на изменяемые и неизменяемые, а как устроена сама типизация в Python? Чтобы ответить на этот вопрос, надо рассмотреть какими характеристиками обладает система типов Python.]]></description><content:encoded><![CDATA[
  <figure id="yoTX" class="m_column">
    <img src="https://img3.teletype.in/files/25/17/25170f7b-5ecc-4c78-aff3-df01a571bb04.png" width="1793" />
  </figure>
  <p id="vifl">Все знают, что в Python типы данных делятся на изменяемые и неизменяемые, а как устроена сама типизация в Python? Чтобы ответить на этот вопрос, надо рассмотреть какими характеристиками обладает система типов Python.</p>
  <p id="6guK">Обычно, когда описывают систему типов языка программирования, отвечают на 3 вопроса:</p>
  <ol id="ZKJ1">
    <li id="Qkg5">Выполняет ли язык неявные автоматические преобразования типов?</li>
    <li id="Hp61">На каком этапе выясняется тип переменной?</li>
    <li id="nd14">Надо ли явно указывать тип переменных?</li>
  </ol>
  <p id="w9Is">Отвечая на эти вопросы, можно узнать имеет ли язык <a href="https://ru.wikipedia.org/wiki/%D0%A1%D0%B8%D0%BB%D1%8C%D0%BD%D0%B0%D1%8F_%D0%B8_%D1%81%D0%BB%D0%B0%D0%B1%D0%B0%D1%8F_%D1%82%D0%B8%D0%BF%D0%B8%D0%B7%D0%B0%D1%86%D0%B8%D1%8F" target="_blank">сильную или слабую типизацию</a>, <a href="https://ru.wikipedia.org/wiki/%D0%A1%D1%82%D0%B0%D1%82%D0%B8%D1%87%D0%B5%D1%81%D0%BA%D0%B0%D1%8F_%D1%82%D0%B8%D0%BF%D0%B8%D0%B7%D0%B0%D1%86%D0%B8%D1%8F" target="_blank">статическую</a> или <a href="https://ru.wikipedia.org/wiki/%D0%94%D0%B8%D0%BD%D0%B0%D0%BC%D0%B8%D1%87%D0%B5%D1%81%D0%BA%D0%B0%D1%8F_%D1%82%D0%B8%D0%BF%D0%B8%D0%B7%D0%B0%D1%86%D0%B8%D1%8F" target="_blank">динамическую</a>, <a href="https://ru.wikipedia.org/wiki/%D0%AF%D0%B2%D0%BD%D0%BE%D0%B5_%D0%BD%D0%B0%D0%B7%D0%BD%D0%B0%D1%87%D0%B5%D0%BD%D0%B8%D0%B5_%D1%82%D0%B8%D0%BF%D0%BE%D0%B2" target="_blank">явную или неявную</a>.</p>
  <p id="4wao">Python имеет <strong>сильную динамическую неявную</strong> типизацию и давайте подробнее рассмотрим почему.</p>
  <p id="li2t"></p>
  <h2 id="1-">1. Сильная типизация</h2>
  <p id="WIil">Когда мы говорим о &quot;сильной типизации&quot; в контексте программирования, мы имеем в виду строгий подход языка к обработке переменных разных типов. В языке программирования Python, который отличается сильной типизацией, разные типы данных не смешиваются автоматически. Так, выражение <code>&quot;some string&quot; - 3</code> вызовет ошибку, потому что язык не позволяет неявно преобразовывать строку в число для выполнения математической операции. При ошибках связанных с типами Python генерирует исключение <strong>TypeError</strong>.</p>
  <p id="sypA">Аналогично, попытка сложить список <code>[2, 1, 0]</code> и множество <code>set([2, 23, 2])</code> также приведет к ошибке, поскольку Python не будет искать способы автоматически преобразовать одну структуру данных в другую для выполнения операции. В противопоставление можно привести в пример язык JavaScript, который позволяет без проблем складывать строки с числами: <code>3 + &#x27;1&#x27; // Получится строка: &#x27;31&#x27;</code>.</p>
  <p id="OFuE">Несмотря на сильную типизацию, Python допускает некоторые операции между различными типами данных, но это объясняется явной реализацией, а не автоматическим преобразованием:</p>
  <pre id="x7FZ" data-lang="python"># Повторение последовательностей:
# вы можете &quot;умножить&quot; строку или список на число,
# и это даст повторяющуюся последовательность.

print(&quot;word&quot; * 3)  # wordwordword
print([1, 2] * 3)  # [1, 2, 1, 2, 1, 2]

# float и int
print(5 + 0.1)  # 5.1

# bool и число
print(2.2 + True)  # 3.2

# Замечание: тип bool наследуется от int,
# и здесь операция сложения не вызывает сомнений.

# и другие...</pre>
  <p id="8Irj">Эти операции возможны благодаря внутренним механизмам языка, предоставляющим четкую реализацию для таких случаев. Например, вы в своей программе можете явно задать поведение вашего типа (класса) при сложении с другим объектом, используя магические методы. Но если вы этого не сделаете, то получите ошибку при попытке сложить с чем-то.</p>
  <p id="BlV5">Для наглядности сделаем так, чтобы строка работала как в JavaScript: приводила складываемый объект к строке и конкатенировала результат.</p>
  <pre id="69GN" data-lang="python">class JavaScriptStr(str):
    def __add__(self, value):  # Явная реализация сложения
        return super().__add__(str(value))


number = JavaScriptStr(&quot;3&quot;)
print(number + 1)  # &quot;31&quot;</pre>
  <p id="2-"></p>
  <h2 id="zNr9">2. Динамическая типизация</h2>
  <p id="5Aw6">Динамическая типизация Python означает, что типы данных переменных определяются во время выполнения программы, а не на этапе компиляции, как в языках со статической типизацией. Это облегчает написание гибкого кода и позволяет, например, создавать функции, которые работают с различными типами данных.</p>
  <p id="ExKA">Рассмотрим функцию <code>find</code>, которая ищет элемент <code>required_element</code> в последовательности <code>sequence</code>. В языке <strong>C</strong> для реализации этой же логики пришлось бы написать несколько версий функции для разных типов данных, тогда как в Python достаточно одной:</p>
  <pre id="1ROs" data-lang="python">def find(required_element, sequence):
    &quot;&quot;&quot;Осуществляет поиск элемента в последовательности.&quot;&quot;&quot;
    for index, element in enumerate(sequence):
        if element == required_element:
            return index
    return -1

    
print(find(2, [1, 2, 3]))  # Выведет: 1
print(find(&quot;c&quot;, (&quot;a&quot;, &quot;b&quot;, &quot;c&quot;, &quot;d&quot;)))  # Выведет: 2

print(find(1, 1))  # Возникнет исключение TypeError</pre>
  <p id="RjSJ">Негативная сторона динамической типизации в том, что она может порождать неожиданные ошибки во время выполнения программы. Например, если мы в <code>sequence</code> передадим не итерируемый объект (по которому нельзя пройтись с помощью <strong>for</strong>), то получим ошибку. Но узнаем о ней, только когда выполнение программы дойдёт до конкретной строчки с <strong>for</strong> обходом по этому объекту.</p>
  <p id="OX4Q">Также динамическая типизация позволяет перезаписывать в одну переменную данные разных типов.</p>
  <pre id="UJPB" data-lang="python">number = 1  # int
number = &quot;One&quot;  # str</pre>
  <p id="3-"></p>
  <h2 id="JJcK">3. Неявная типизация</h2>
  <p id="pcUk">В языках с неявной типизацией не требуется явно указывать тип переменной — интерпретатор сам определяет тип на основе присвоенного значения. Это упрощает код и делает его более читаемым:</p>
  <pre id="npuh" data-lang="python">var = 5  # int
var = &quot;some string&quot;  # теперь var - это str</pre>
  <p id="-python"></p>
  <h2 id="5htL">Аннотация Типов в Python</h2>
  <p id="zHfq">Начиная с версии 3.5, в Python появилась поддержка аннотации типов, что позволяет программистам явно указывать типы переменных и возвращаемых функцией значений. Это не влияет на работу интерпретатора, но помогает в программировании и поддержке кода:</p>
  <pre id="xOC9" data-lang="python">def plus(x: int, y: int) -&gt; int:
    return x + y


print(plus(5, 10))  # Выведет: 15

# Функция выполнит конкатенацию строк.
# Это показывает, что аннотация не равно явная типизация.
print(plus(&quot;Hello &quot;, &quot;world!&quot;))  # Выведет: Hello world!</pre>
  <p id="7jmN">Указание типов через аннотации делает код более понятным для других разработчиков, а также позволяет находить ошибки до выполнения программы, используя дополнительные инструменты статического анализа. Но язык по-прежнему сохраняет гибкость, присущую динамической типизации</p>
  <p id="-"></p>
  <h2 id="dWF1">Заключение</h2>
  <p id="1zjv">Типизация в Python сочетает в себе строгость и гибкость: с одной стороны, сильная типизация защищает от ошибок, связанных с несоответствием типов, с другой — динамическая типизация позволяет писать более универсальный и легко читаемый код. Аннотации типов приносят ясность в структуру программы, делая ее более предсказуемой и безопасной.</p>

]]></content:encoded></item><item><guid isPermaLink="true">https://teletype.in/@loginovpavel/docker-installation</guid><link>https://teletype.in/@loginovpavel/docker-installation?utm_source=teletype&amp;utm_medium=feed_rss&amp;utm_campaign=loginovpavel</link><comments>https://teletype.in/@loginovpavel/docker-installation?utm_source=teletype&amp;utm_medium=feed_rss&amp;utm_campaign=loginovpavel#comments</comments><dc:creator>loginovpavel</dc:creator><title>Быстрая Установка Docker и Compose на Ubuntu</title><pubDate>Sat, 04 Nov 2023 19:58:22 GMT</pubDate><media:content medium="image" url="https://img3.teletype.in/files/ad/7e/ad7e65cb-bf48-4b13-ad27-975582e3bf0a.png"></media:content><category>Guide</category><description><![CDATA[<img src="https://img2.teletype.in/files/59/34/59347b16-782f-41f9-bfa3-a0481498b37a.jpeg"></img>Быстрая установка Docker и Docker Compose на Ubuntu используя простой скрипт.]]></description><content:encoded><![CDATA[
  <figure id="QtH0" class="m_column">
    <img src="https://img2.teletype.in/files/59/34/59347b16-782f-41f9-bfa3-a0481498b37a.jpeg" width="1920" />
  </figure>
  <p id="ienN">Docker — это невероятно мощный инструмент, но иногда его настройка на Ubuntu иногда может быть сложной задачей. В этом посте мы рассмотрим шаги <strong>установки Docker (и Docker Compose) на Ubuntu</strong> с использованием одного скрипта.</p>
  <p id="ggjp">Прежде чем мы начнем процесс установки, убедитесь, что вы используете Ubuntu. Шаги, предоставленные в этом руководстве, написаны для Ubuntu, но могут быть применимы к другим дистрибутивам с некоторыми изменениями.</p>
  <p id="PV05"><strong>TL;DR:</strong></p>
  <pre id="PVDq" data-lang="bash">wget https://gist.githubusercontent.com/welel/f80c96482e3b539487b9fa08bfcab86d/raw/90bc2330924d225aef7dc3178f5926bda7daff04/install_docker.sh

sudo chmod +x install_docker.sh

sudo ./install_docker.sh
</pre>
  <p id="q5Wr"><strong>Вот как установить Docker с помощью скрипта в Ubuntu:</strong></p>
  <hr />
  <h3 id="-1-">Шаг 1: Загрузите установочный скрипт</h3>
  <p id="wnH7">Сначала вам нужно загрузить скрипт, который автоматизирует процесс установки. Откройте терминал и введите следующую команду:</p>
  <pre id="7Hvn" data-lang="bash">wget https://gist.githubusercontent.com/welel/f80c96482e3b539487b9fa08bfcab86d/raw/90bc2330924d225aef7dc3178f5926bda7daff04/install_docker.sh</pre>
  <p id="JmCs">Эта команда загружает необходимый скрипт из Gist и сохраняет его в текущей директории.</p>
  <p id="A6WK">Скрипт доступен на Gist. Gist включает в себя сам скрипт и подробное объяснение его использования. Вы можете просмотреть и скачать скрипт, а также найти инструкции о том, как им пользоваться, по следующей ссылке:</p>
  <p id="815I"><a href="https://gist.github.com/welel/f80c96482e3b539487b9fa08bfcab86d" target="_blank">Установить Docker на Ubuntu - Скрипт установки и руководство</a></p>
  <p id="KPmC">Этот скрипт делает процесс установки понятным и простым, позволяя вам быстро запустить Docker на вашей системе Ubuntu с минимальными усилиями.</p>
  <h3 id="-2-">Шаг 2: Предоставьте скрипту разрешения на выполнение</h3>
  <p id="vt7O">Прежде чем запустить скрипт, вам нужно дать ему разрешение на выполнение. Измените разрешения с помощью команды <code>chmod</code>:</p>
  <pre id="T2or" data-lang="bash">sudo chmod +x install_docker.sh</pre>
  <h3 id="-3-">Шаг 3: Выполните скрипт</h3>
  <p id="bfEg">Теперь пришло время запустить скрипт. Вы сделаете это с повышенными привилегиями, используя <code>sudo</code>:</p>
  <pre id="mMgF" data-lang="bash">sudo ./install_docker.sh</pre>
  <p id="lWdP">После выполнения скрипта следите за выводом в терминале. Если все пройдет как запланировано, вы должны увидеть сообщение <strong>Docker successfully installed.</strong> Это подтверждение означает, что Docker был успешно установлен на вашей системе, и вы готовы использовать его для ваших нужд в контейнеризации.</p>
  <hr />
  <h3 id="-">После установки</h3>
  <p id="koEv">Чтобы убедиться, что все работает корректно, вы можете выполнить:</p>
  <pre id="h0ML" data-lang="bash">docker --version

docker compose version</pre>
  <p id="T0ZF">Эта команда должна вывести версию Docker, которую вы только что установили, и версию Docker Compose.</p>
  <figure id="0Guc" class="m_original" data-caption-align="center">
    <img src="https://img2.teletype.in/files/dd/02/dd02e16f-9574-4ea5-865f-f5ab7ee8be1a.png" width="500" />
    <figcaption>Вывод команд</figcaption>
  </figure>
  <p id="IgR9">Кроме того, хорошей практикой будет добавить вашего пользователя в группу <code>docker</code>, чтобы вы могли выполнять команды Docker без использования <code>sudo</code>:</p>
  <pre id="Qbvi" data-lang="bash">sudo usermod -aG docker ${USER}</pre>
  <p id="pZlj">Не забудьте выйти из системы и войти обратно, чтобы это изменение вступило в силу.</p>
  <hr />
  <p id="mukp">С помощью вышеуказанного скрипта установка Docker на Ubuntu становится очень быстрой. <strong>Счастливого &quot;докеринга&quot;!</strong></p>

]]></content:encoded></item><item><guid isPermaLink="true">https://teletype.in/@loginovpavel/rent-vps</guid><link>https://teletype.in/@loginovpavel/rent-vps?utm_source=teletype&amp;utm_medium=feed_rss&amp;utm_campaign=loginovpavel</link><comments>https://teletype.in/@loginovpavel/rent-vps?utm_source=teletype&amp;utm_medium=feed_rss&amp;utm_campaign=loginovpavel#comments</comments><dc:creator>loginovpavel</dc:creator><title>Как Арендовать Дешевый Виртуальный Сервер за 130 рублей</title><pubDate>Sat, 04 Nov 2023 18:24:00 GMT</pubDate><media:content medium="image" url="https://img4.teletype.in/files/35/ac/35ac3332-0b71-4afa-b837-fcb69e0d71e1.png"></media:content><category>Guide</category><description><![CDATA[<img src="https://img2.teletype.in/files/df/f3/dff3757c-b64a-4607-b272-56b280d33fb3.jpeg"></img>Пошагово рассмотрим как можно за несколько минут арендовать дешевый виртуальный сервер за 130 рублей. Рассмотрим процесс аренды и входа на сервер на платформе RU VDS.]]></description><content:encoded><![CDATA[
  <p id="2ulV">Пошагово рассмотрим как можно за несколько минут арендовать дешевый виртуальный сервер за <strong>130 рублей</strong>. Рассмотрим процесс аренды и входа на сервер на платформе <a href="https://ruvds.com/" target="_blank">RU VDS</a>. Этот пост для людей, которые никогда не арендовали сервер и думают, что это делается сложно и дорого. На самом деле ничего сложного нет, просто следуйте инструкции. <strong>Цены актуальны на 2023 год.</strong></p>
  <figure id="t6pK" class="m_column">
    <img src="https://img2.teletype.in/files/df/f3/dff3757c-b64a-4607-b272-56b280d33fb3.jpeg" width="810" />
  </figure>
  <p id="o2C3">Дешевый виртуальный сервер с небольшими ресурсами хорошо подходит для ряда целей, особенно если вы не ожидаете большой нагрузки или если требования к вычислительным мощностям и хранению данных невелики. Вот несколько примеров задач, для которых может подойти такой сервер: <strong>хостинг веб-сайтов, легкие веб-приложения, размещение ботов</strong> и так далее.</p>
  <p id="7fzN">За 130 рублей получится арендовать машину со следующей конфигурацией: <strong>1 ядро x 2.2 ГГц, 500 Мб RAM, 10 Гб HDD</strong>, операционная система на выбор. Такой конфигурации вполне хватит для поднятия учебных веб-приложений, ботов и даже реальных проектов с небольшим количеством пользователей.</p>
  <hr />
  <h3 id="1-">1. Регистрация</h3>
  <p id="rpCI">Перейдите на сайт <a href="https://ruvds.com/" target="_blank">RU VDS</a> и выполните регистрацию.</p>
  <hr />
  <h3 id="2-">2. Переход на форму создания сервера</h3>
  <p id="tQXd">Нажмите на выпадающее меню <strong>Выбрать VPS</strong>, затем на <strong>VPS Старт</strong>. Выберите желаемую конфигурацию. Я выберу самую дешевую за 130 рублей. Нажмите <strong>Заказать</strong>.</p>
  <figure id="qaQe" class="m_column" data-caption-align="center">
    <img src="https://img1.teletype.in/files/4c/6c/4c6c63fa-7f01-4498-a996-8ad68716e16d.png" width="1919" />
    <figcaption>Переход на выбор тарифа</figcaption>
  </figure>
  <figure id="duBY" class="m_column" data-caption-align="center">
    <img src="https://img1.teletype.in/files/8a/11/8a114a1d-6348-49df-8900-fd15ff338406.png" width="1919" />
    <figcaption>Выбор тарифа</figcaption>
  </figure>
  <hr />
  <h3 id="3-">3. Конфигурация ресурсов сервера</h3>
  <p id="Fu4z">Первым пунктом предлагается <strong>выбрать дата-центр</strong>, где будет размещен ваш сервер. Выбирайте дата-центр поближе к себе или к вашим потенциальным пользователям.</p>
  <figure id="lVGl" class="m_column" data-caption-align="center">
    <img src="https://img2.teletype.in/files/52/8f/528fe468-1879-4011-aae2-9b125e8907b9.png" width="1919" />
    <figcaption>Выбор дата-центра</figcaption>
  </figure>
  <p id="g3a6">Далее есть возможность <strong>поменять ресурсы сервера</strong>, при этом изменится цена.</p>
  <figure id="2QgD" class="m_column" data-caption-align="center">
    <img src="https://img4.teletype.in/files/fb/ff/fbff3cd5-f887-40b3-bcf2-c5e14c622675.png" width="1919" />
    <figcaption>Конфигурация ресурсов</figcaption>
  </figure>
  <p id="csEM">Данный пункт позволяет <strong>выбрать шаблон</strong> для сервера. Шаблон — сервер с предустановленным софтом (например, можно выбрать шаблон для Django проекта). Я предпочитаю работать с контейнеризацией при разворачивании проектов, потому выбираю чистую ОС. Также можно <strong>выбрать желаемую операционную систему</strong> (можно потом поменять) и <strong>срок аренды</strong>.</p>
  <figure id="7Z95" class="m_column" data-caption-align="center">
    <img src="https://img1.teletype.in/files/03/01/030130fc-6842-43a0-8cb9-a29ae23e797a.png" width="1919" />
    <figcaption>Выбор ОС и срока аренды</figcaption>
  </figure>
  <hr />
  <h3 id="4-">4. Оплата</h3>
  <p id="bVJS">После нажатия на кнопку <strong>Оплата</strong>, вы попадете на форму оплаты с большим разнообразием возможностей оплаты. По моему опыту оплата производится мгновенно.</p>
  <figure id="Uqsi" class="m_column" data-caption-align="center">
    <img src="https://img2.teletype.in/files/d1/1c/d11c1dc8-783b-46f3-853d-d42417dcb311.png" width="1919" />
    <figcaption>Страница оплаты</figcaption>
  </figure>
  <hr />
  <h3 id="5-">5. Вход на сервер</h3>
  <p id="WJzx">После оплаты переходите на страницу <strong>Мои серверы</strong>. Здесь отображается ваш сервер, который можно включать, выключать, обнулять, переустанавливать ОС и так далее.</p>
  <figure id="Flx1" class="m_column" data-caption-align="center">
    <img src="https://img1.teletype.in/files/80/ca/80ca3aaa-6f09-4ef7-9277-4e303d7fcfce.png" width="1919" />
    <figcaption>Страница сервера</figcaption>
  </figure>
  <p id="1xqH">Теперь использую любой SSH клиент можно войти на сервер, используя данные для входа: IP, имя пользователя, пароль. Воспользуемся ssh клиентом системы Ubuntu.</p>
  <pre id="ulU3" data-lang="bash"># ssh &lt;user&gt;@&lt;IP&gt;

$ ssh root@62.76.228.108

# Вводим пароль

# Соглашаемся с предупреждением

# Выводим информацию о сервере

$ lsb_release -a
</pre>
  <figure id="lXC2" class="m_column" data-caption-align="center">
    <img src="https://img4.teletype.in/files/34/19/34191077-caf2-48a2-804a-8ab826178c11.png" width="805" />
    <figcaption>Вход на сервер через SSH</figcaption>
  </figure>
  <p id="kK1U">Поздравляю, вы успешно арендовали сервер, на котором теперь можно запустить целый ряд интересных и полезных проектов! Неважно, начинаете ли вы свой путь в мире веб-разработки или уже имеете опыт и просто ищете подходящее место для экспериментов — этот сервер открывает перед вами множество возможностей.</p>
  <p id="3FHH">Далее логичным шагом будет установить <strong>Docker и Docker Compose</strong>, инструкция: <a href="https://teletype.in/@loginovpavel/docker-installation" target="_blank">Быстрая Установка Docker и Compose на Ubuntu</a></p>

]]></content:encoded></item><item><guid isPermaLink="true">https://teletype.in/@loginovpavel/cheap-windows-11</guid><link>https://teletype.in/@loginovpavel/cheap-windows-11?utm_source=teletype&amp;utm_medium=feed_rss&amp;utm_campaign=loginovpavel</link><comments>https://teletype.in/@loginovpavel/cheap-windows-11?utm_source=teletype&amp;utm_medium=feed_rss&amp;utm_campaign=loginovpavel#comments</comments><dc:creator>loginovpavel</dc:creator><title>Windows 11 за 500–1000 рублей</title><pubDate>Fri, 25 Aug 2023 20:33:08 GMT</pubDate><media:content medium="image" url="https://img4.teletype.in/files/f6/8c/f68c7d73-4a26-41ec-ab4a-22a181a30168.png"></media:content><description><![CDATA[<img src="https://img1.teletype.in/files/ca/ca/caca52fa-9a57-416e-bc3d-dfc16068834d.jpeg"></img>Покупка компьютера без операционной системы обходится дешевле. При заказе своего ноутбука, мне предложили купить Windows 11 за 15.000 рублей, что мне показалось забавным. Когда я пришёл забирать ноутбук в магазин, краем уха я услышал, как покупателю предлагают дополнительно к компьютеру купить Windows за похожую цену, и поколебавшись, он согласился. Наверное, это и подвигло меня написать немного об этом. В этом посте мы рассмотрим, как можно приобрести Windows 11 всего за 500/1000 рублей.]]></description><content:encoded><![CDATA[
  <figure id="SPzA" class="m_column">
    <img src="https://img1.teletype.in/files/ca/ca/caca52fa-9a57-416e-bc3d-dfc16068834d.jpeg" width="1200" />
  </figure>
  <p id="uytg">Покупка компьютера без операционной системы обходится дешевле. При заказе своего ноутбука, мне предложили купить Windows 11 за 15.000 рублей, что мне показалось забавным. Когда я пришёл забирать ноутбук в магазин, краем уха я услышал, как покупателю предлагают дополнительно к компьютеру купить Windows за похожую цену, и поколебавшись, он согласился. Наверное, это и подвигло меня написать немного об этом. В этом посте мы рассмотрим, как можно приобрести Windows 11 всего за 500/1000 рублей.</p>
  <p id="fSM7">Несколько слов “почему Windows” (можете пропустить это). Мне, как и многим разработчикам, удобнее работать непосредственно в Linux. Но не все ноутбуки подходят для работы с этой операционной системой. Иногда при установке Linux у ноутбука могут перестать работать некоторые функции или могут появиться проблемы с драйверами. Есть <a href="https://ubuntu.com/certified/laptops" target="_blank">список сертифицированных ноутбуков под Ubuntu</a>, у которых при работе с Linux должно быть минимум проблем. Мой ноутбук не из этой серии, а производитель заявляет, что лучшая ОС для моего девайса — Windows 10/11. Ради хорошего ноутбука пришлось пойти на компромисс и установить Windows. Благо помимо виртуальной машины существуют такие инструменты как <a href="https://learn.microsoft.com/ru-ru/windows/wsl/about" target="_blank">WSL</a> (позволяет запускать Linux-приложения и среду на вашем компьютере под управлением Windows). Для разработчиков и тех, кто предпочитает Linux, WSL открывает новые возможности, делая работу с обеими операционными системами более гармоничной.</p>
  <h3 id="0mQc"><strong>Шаг 1: Установка Windows 11</strong></h3>
  <p id="k9Hf">Первым шагом необходимо получить установочный образ Windows 11, создать загрузочную флешку и установить Windows на компьютер. Официальный сайт Microsoft предлагает <a href="https://www.microsoft.com/software-download/windows11" target="_blank">скачать образ</a>, однако не всегда это бывает доступно. При возникновении ошибки, такой как “<em>We are unable to complete your request at this time. Some users, entities and locations are banned from using this service.</em>”, VPN может помочь обойти ограничения. Если VPN не помогает, то скачайте образ с другого источника. Важно выбирать проверенный источник для загрузки образа, который позволяет скачать официальную версию образа, а не переделанные сборки.</p>
  <p id="VGzC">Я скачивал образ Windows 11 Pro с этого сайта: <a href="https://softcomputers.org/download/download-windows-11/download-windows-11/" target="_blank">https://softcomputers.org/download/download-windows-11/download-windows-11/</a>. Есть английская и русская версии и чек-сумма для проверки. Вы можете поискать источники заслуживающие ваше доверие.</p>
  <h3 id="Rxxb"><strong>Шаг 2: Покупка ключа</strong></h3>
  <p id="UTG9">Чтобы сэкономить значительную сумму, я обратился к ресурсам вроде Ozon, Авито, сайты в интернете, где можно найти лицензионные ключи Windows 11 по намного более доступным ценам, чем в официальных магазинах. Однако при таком выборе, всегда необходимо быть осторожным и проверять рейтинг продавца и отзывы покупателей, чтобы избежать мошенничества. Также, даже успешно активированный ключ не признак гарантии того, что в будущем с ним не возникнет проблем. В любом случае это будет дешевле, даже если придётся купить пару ключей.</p>
  <p id="5xoM">Многие отзываются о данном сайте, как о надежном продавце: <a href="https://softmonstr.ru/product/windows-11-pro-%D0%BE%D0%B5%D0%BC/" target="_blank">https://softmonstr.ru/product/windows-11-pro-%D0%BE%D0%B5%D0%BC/</a>, но я нашёл хороший и дешевый вариант на Авито. Продавец имеет положительные отзывы, и очень оперативно проводит сделки. Ключ можно приобрести за 500 рублей — одноразовый (до переустановки ОС) и за 1000 — с привязкой к аккаунту Microsoft. Через 2 минуты после первого сообщения, я уже вводил свой электронный ключ, который подошёл и активировал Windows с привязкой.</p>
  <p id="cRD9"><strong>Заключение:</strong></p>
  <p id="R40z">Как видите, приобретение Windows 11 за более доступную цену возможно, но требует осторожности. Проверьте рейтинг продавца и используйте проверенные источники для загрузки операционной системы. Ключ, связанный с учетной записью Microsoft, обычно более гибкий и позволяет переустанавливать Windows при необходимости. Не забывайте о преимуществах Windows Subsystem for Linux (WSL), который делает Windows привлекательным выбором для разработчиков.</p>
  <p id="neCf">Наглядное видео как установить Windows: <a href="https://www.youtube.com/watch?v=oeMhkn6WAgQ&t=143s" target="_blank">https://www.youtube.com/watch?v=oeMhkn6WAgQ&amp;t=143s</a></p>

]]></content:encoded></item><item><guid isPermaLink="true">https://teletype.in/@loginovpavel/intercept-https-traffic-android</guid><link>https://teletype.in/@loginovpavel/intercept-https-traffic-android?utm_source=teletype&amp;utm_medium=feed_rss&amp;utm_campaign=loginovpavel</link><comments>https://teletype.in/@loginovpavel/intercept-https-traffic-android?utm_source=teletype&amp;utm_medium=feed_rss&amp;utm_campaign=loginovpavel#comments</comments><dc:creator>loginovpavel</dc:creator><title>Перехват HTTP(S) трафика Android приложения</title><pubDate>Sun, 06 Aug 2023 12:27:15 GMT</pubDate><media:content medium="image" url="https://img1.teletype.in/files/0e/31/0e317a0b-ae82-4b9c-9149-d662d0b71439.png"></media:content><category>Компьютерные сети</category><description><![CDATA[<img src="https://img2.teletype.in/files/5b/9e/5b9e2ba3-eda1-4e6c-bf12-234411c744f8.jpeg"></img>Android Studio предоставляет вам возможность проверять сетевой трафик вашего собственного приложения. Могут возникнуть ситуации, когда вы захотите увидеть, какие API вызовы выполняются в других приложениях. Например, при парсинге какого-нибудь сайта, на API точке может стоять защита от парсинга. Бывает, что в мобильной версии сайта (мобильном приложении) используются другие API вызовы, у которых может не быть защиты.]]></description><content:encoded><![CDATA[
  <p id="gUCf">Android Studio предоставляет вам возможность проверять сетевой трафик вашего собственного приложения. Могут возникнуть ситуации, когда вы захотите увидеть, какие API вызовы выполняются в других приложениях. Например, при парсинге какого-нибудь сайта, на API точке может стоять защита от парсинга. Бывает, что в мобильной версии сайта (мобильном приложении) используются другие API вызовы, у которых может не быть защиты.</p>
  <p id="fA7S">В таких случаях может быть удобно иметь специально подготовленное виртуальное устройство Android (AVD), готовое посмотреть, какими HTTP(S) запросами обменивается устройство и сервер.</p>
  <p id="0CdC">В этом руководстве объясняется, как настроить специально подготовленный AVD, который можно использовать для мониторинга сетевого трафика Android приложения, установленного в эмуляторе, даже если оно обменивается данными через защищенное соединение (HTTPS).</p>
  <h2 id="ylOV">Установка mitmproxy</h2>
  <p id="Mdha">Для просмотра сетевого трафика мы будем использовать <a href="https://mitmproxy.org/" target="_blank">mitmproxy</a> (в этом руководстве будет использоваться веб-интерфейс <strong>mitmweb</strong>, который позволяет просматривать трафик в интерфейсе браузера). MITMproxy позволяет просматривать сетевой трафик, добавляя себя в качестве прокси-сервера между приложением и сервером (API).</p>
  <figure id="iZe7" class="m_column">
    <img src="https://img2.teletype.in/files/5b/9e/5b9e2ba3-eda1-4e6c-bf12-234411c744f8.jpeg" width="1000" />
  </figure>
  <p id="7nPe">В зависимости от вашей ОС вы должны следовать этому руководству по установке: <a href="https://docs.mitmproxy.org/stable/overview-installation/" target="_blank">https://docs.mitmproxy.org/stable/overview-installation/</a></p>
  <p id="SCWO">Для установки на Linux, необходимо <a href="https://mitmproxy.org/" target="_blank">скачать архив</a>, затем распаковать его в удобную папку.</p>
  <h2 id="CikH">Установка Android Studio</h2>
  <p id="zdjS">Для создания и запуска виртуального устройства Android будет использоваться <strong>Android Studio</strong>.</p>
  <p id="x6rt">В зависимости от вашей ОС вы должны следовать этому руководству по установке: <a href="https://developer.android.com/studio/install" target="_blank">https://developer.android.com/studio/install</a></p>
  <p id="3ubl">После установки, необходимо добавить в переменную окружения PATH пути к эмулятору и используемым далее инструментам. На Linux можно в конец файла <code>~/.bashrc</code> добавить строчки:</p>
  <pre id="vT9z" data-lang="bash">export PATH=$PATH:$HOME/Android/Sdk/platform-tools
export PATH=$PATH:$HOME/Android/Sdk/emulator</pre>
  <p id="KbVn">После установки и настройки, запускайте Android Studio, выполнив скрипт <code>studio.sh</code> в <code>android-studio/bin/</code> директории.</p>
  <h2 id="Y8fK">Создание AVD</h2>
  <p id="kDEO">1. В окне Android Studio нажмите <strong>More Actions</strong> -&gt; <strong>Virtual Device Manager</strong>.</p>
  <figure id="lnSD" class="m_column">
    <img src="https://img4.teletype.in/files/36/74/36745ad2-bafb-4a61-a7e0-2e76bfb1d2ae.png" width="943" />
  </figure>
  <p id="klBn">2. В окне менеджера устройств нажмите <strong>Create Device</strong>.</p>
  <figure id="Uks0" class="m_column">
    <img src="https://img3.teletype.in/files/65/d4/65d48e32-edd8-4213-b152-a3bc0f6dedd4.png" width="803" />
  </figure>
  <p id="UHc0">3. Я выберу Pixel 3.</p>
  <figure id="oBMh" class="m_column">
    <img src="https://img1.teletype.in/files/06/9b/069ba9a5-ecdd-4f65-b1ad-8e5a5ff5676f.png" width="999" />
  </figure>
  <p id="QtYf">4. Выберите образ системы. По опыту, <strong>API уровня 28</strong> и <strong>ABI x86 (Android 9.0)</strong> работают лучше всего. Более новые уровни API могут помешать вам загрузить пользовательские (CA) сертификаты.</p>
  <figure id="fTLp" class="m_column">
    <img src="https://img3.teletype.in/files/ed/5b/ed5bcd6d-5516-4b29-a5d2-f9702d2ed8ec.png" width="999" />
  </figure>
  <p id="Qq4m">5. После выбора образа жмите <strong>Finish</strong>.</p>
  <figure id="DCco" class="m_column">
    <img src="https://img4.teletype.in/files/bf/5c/bf5cff87-14db-4f2c-954f-a70f36061a55.png" width="802" />
  </figure>
  <p id="TY7m">У вас появилось новое устройство, но пока не запускайте его. Закройте Android Studio.</p>
  <h2 id="PXOZ">Подготовка AVD</h2>
  <p id="Xo4n">1. Откройте свой терминал.</p>
  <p id="5VwL">2. Предполагая, что вы добавили пути Android Studio инструментов в $PATH, если нет, вернитесь на шаг установки Android Studio.</p>
  <p id="23St">3. Выведите список доступных виртуальных устройств командой и скопируйте название созданного нами устройства:</p>
  <pre id="qz0p" data-lang="bash">emulator -list-avds</pre>
  <p id="4CK5">4. По умолчанию (при запуске из AVD Manager) мы не сможем записывать никакие файлы в системные папки. Чтобы запустить эмулятор с доступными для записи системными папками, используйте эту команду:</p>
  <pre id="lPI0" data-lang="bash">emulator -avd Pixel_3_API_28 -writable-system</pre>
  <p id="8QUt">5. Подождите, когда загрузится устройство.</p>
  <h2 id="Z5LI">Настройка mitmproxy</h2>
  <p id="H0QY">1. Перейдите в папку с mitmproxy и запустите <strong>mitmweb</strong>. В домашней директории у вас появится папка <code>.mitmproxy</code>. Переходите в неё.</p>
  <pre id="qKi9" data-lang="bash">cd ~/.mitmproxy/</pre>
  <p id="rXdE">2. Сгенерируйте сертификат:</p>
  <pre id="UYvU" data-lang="bash">hashed_name=&#x60;openssl x509 -inform PEM -subject_hash_old -in mitmproxy-ca-cert.cer | head -1&#x60; &amp;&amp; cp mitmproxy-ca-cert.cer $hashed_name.0</pre>
  <p id="HFuJ">У вас в папке должен появиться файл с расширением <code>.0</code>, например такой <code>c8750f0d.0</code>. В дальнейших шагах подставляйте своё название файла из папки. Дополнительную информацию можно найти здесь: <a href="https://docs.mitmproxy.org/stable/howto-install-system-trusted-ca-android/" target="_blank">https://docs.mitmproxy.org/stable/howto-install-system-trusted-ca-android/</a></p>
  <p id="FKd4">3. Выполните последовательность команд в терминале и подождите перезагрузку AVD:</p>
  <pre id="IEl5" data-lang="bash">adb root
adb remount
adb shell &#x27;mount -o rw,remount /&#x27;
adb push c8750f0d.0 /system/etc/security/cacerts
adb shell &#x27;chmod 664 /system/etc/security/cacerts/c8750f0d.0&#x27;
adb reboot</pre>
  <h2 id="5GCS">Настройка прокси на AVD</h2>
  <p id="bY4v">1. Нажмите на дополнительные настройки:</p>
  <figure id="hFZ7" class="m_retina">
    <img src="https://img3.teletype.in/files/e7/ab/e7abf532-7ae0-400e-8cad-84b2f62c4633.png" width="236" />
  </figure>
  <p id="QUlb">2. В открывшемся окне выберите <strong>Settings</strong> → <strong>Proxy</strong> → <strong>Manual proxy configuration</strong>. В Host name впишите <strong>0.0.0.0</strong>, в Port number <strong>8080</strong>. Нажмите <strong>Apply</strong>.</p>
  <figure id="2kLu" class="m_column">
    <img src="https://img3.teletype.in/files/ae/64/ae648eb5-6f21-4f4a-a174-14cf1c544131.png" width="822" />
  </figure>
  <h2 id="ZVAl">Перехват трафика браузера</h2>
  <p id="GLvy">1. Запустите <strong>mitmweb</strong> и перейдите в браузере по адресу <a href="http://localhost:8081/" target="_blank">http://localhost:8081/</a></p>
  <figure id="tVDz" class="m_column">
    <img src="https://img4.teletype.in/files/f0/e7/f0e700ed-71de-47d3-ba1e-9c9fb169614e.png" width="812" />
  </figure>
  <p id="mYg4">2. Откройте на AVD браузер и перейдите по адресу <strong>mitm.it</strong>, в mitmweb у вас должна открыться вкладка <strong>Flow</strong>, в которой отображается информация о выполненных запросах AVD.</p>
  <figure id="J2EH" class="m_column">
    <img src="https://img3.teletype.in/files/ec/f6/ecf63d3f-3abb-48ac-9555-28a7ffcfdfd6.png" width="1644" />
  </figure>
  <h2 id="PJWV">Установка Android приложения</h2>
  <p id="yNkG">Для примера перехватим трафик приложения Ozon, вы можете попробовать необходимое вам приложение.</p>
  <ol id="zENy">
    <li id="gngE">Скачайте apk файл. <a href="https://www.apkmirror.com/apk/internet-solutions-llc/ozon-5-%d0%bc%d0%bb%d0%bd-%d1%82%d0%be%d0%b2%d0%b0%d1%80%d0%be%d0%b2-%d0%bf%d0%be-%d0%bd%d0%b8%d0%b7%d0%ba%d0%b8%d0%bc-%d1%86%d0%b5%d0%bd%d0%b0%d0%bc/ozon-5-%d0%bc%d0%bb%d0%bd-%d1%82%d0%be%d0%b2%d0%b0%d1%80%d0%be%d0%b2-%d0%bf%d0%be-%d0%bd%d0%b8%d0%b7%d0%ba%d0%b8%d0%bc-%d1%86%d0%b5%d0%bd%d0%b0%d0%bc-16-15-0-release/ozon-%d1%82%d0%be%d0%b2%d0%b0%d1%80%d1%8b-%d0%bf%d1%80%d0%be%d0%b4%d1%83%d0%ba%d1%82%d1%8b-%d0%b1%d0%b8%d0%bb%d0%b5%d1%82%d1%8b-16-15-0-android-apk-download/" target="_blank">Apk файл Ozon</a>. И откройте терминал в директории с файлом.</li>
    <li id="DyFF">Выполните команду установки:</li>
  </ol>
  <pre id="qhh5" data-lang="bash">adb install &lt;название apk файла&gt;</pre>
  <h2 id="ucOC">Перехват трафика приложения</h2>
  <ol id="oZ3k">
    <li id="EZH1">Запустите <strong>mitmweb</strong> и перейдите в браузере по адресу <a href="http://localhost:8081/" target="_blank">http://localhost:8081/</a></li>
    <li id="oYDN">Запустите приложение, которое хотите проинспектировать.</li>
  </ol>
  <p id="0xoz">Как видим, mitmproxy перехватывает все API вызовы. За счёт CA сертификатов, мы можем наблюдать HTTPS трафик.</p>
  <figure id="AEQ6" class="m_custom">
    <img src="https://img3.teletype.in/files/60/76/607601e8-951c-4b34-97eb-62df73515eb5.png" width="1324" />
  </figure>
  <h3 id="VN1d">Источники</h3>
  <ol id="DPZG">
    <li id="8GpU"><a href="https://multigesture.net/articles/inspecting-https-network-traffic-of-any-android-app/" target="_blank">Inspecting (HTTPS) Network Traffic of any Android App</a></li>
    <li id="Jqp5"><a href="https://docs.mitmproxy.org/stable/howto-install-system-trusted-ca-android/" target="_blank">Install System CA Certificate on Android Emulator</a></li>
    <li id="hsQf"><a href="https://www.makeuseof.com/install-apps-via-adb-android/" target="_blank">How to Install Android Apps via ADB</a></li>
  </ol>
  <p id="fNuC"><strong>Подробнее о том как работает mimtproxy:</strong></p>
  <p id="J5u6"><a href="https://selectel.ru/blog/analiz-http-trafika-s-mitmproxy/" target="_blank">Selectel: Анализ HTTP-трафика с mitmproxy</a></p>

]]></content:encoded></item><item><guid isPermaLink="true">https://teletype.in/@loginovpavel/speech-to-text-telegram</guid><link>https://teletype.in/@loginovpavel/speech-to-text-telegram?utm_source=teletype&amp;utm_medium=feed_rss&amp;utm_campaign=loginovpavel</link><comments>https://teletype.in/@loginovpavel/speech-to-text-telegram?utm_source=teletype&amp;utm_medium=feed_rss&amp;utm_campaign=loginovpavel#comments</comments><dc:creator>loginovpavel</dc:creator><title>Speech-to-text: Транскрибация Telegram Voice в текст используя OpenAI API</title><pubDate>Wed, 22 Mar 2023 11:14:23 GMT</pubDate><media:content medium="image" url="https://img1.teletype.in/files/04/dd/04dd85ad-d0ec-4783-b280-4340c8643c5c.png"></media:content><category>Open AI</category><description><![CDATA[<img src="https://img2.teletype.in/files/5d/96/5d96b500-866d-42a7-b73f-d1c835c00276.jpeg"></img>В статье рассмотрим как получить текст голосового сообщения в Telegram боте, используя API OpenAI.]]></description><content:encoded><![CDATA[
  <p id="6UPd">В статье рассмотрим как получить текст голосового сообщения в Telegram боте, используя API OpenAI.</p>
  <figure id="LeFn" class="m_column">
    <img src="https://img2.teletype.in/files/5d/96/5d96b500-866d-42a7-b73f-d1c835c00276.jpeg" width="800" />
    <figcaption>voice-to-text</figcaption>
  </figure>
  <p id="YnB5"><strong>Требования</strong>. У вас должна быть Linux система с установленным Python 3.10+. Если вы в России, потребуется VPN. В качестве Telegram фреймворка используется aiogram 3. Для успешного прохождения туториала необходимы некоторые знания aiogram 3.</p>
  <hr />
  <p id="4NZ1"><strong>Транскрибация</strong> — это перевод аудио в текст. В нашем случае: перевод голосового сообщения (voice) в текст.</p>
  <h2 id="w10P">OpenAI API</h2>
  <p id="3ofR"><strong>OpenAI API</strong> — это программный интерфейс приложения, который предоставляет доступ к технологиям искусственного интеллекта, разработанным и поддерживаемым OpenAI. Для решения нашей задачи нам понадобится модель <strong>Whisper</strong> от OpenAI.</p>
  <p id="VBTG">Прежде чем продолжать, вам следует убедиться, что у вас есть доступ к OpenAI API. В данной статье описан подробный алгоритм регистрации и получения токена для использования OpenAI API: <a href="https://medium.com/@pavel.loginov.dev/%D0%BF%D0%BE%D0%BB%D1%83%D1%87%D0%B5%D0%BD%D0%B8%D0%B5-%D0%B4%D0%BE%D1%81%D1%82%D1%83%D0%BF%D0%B0-%D0%BA-openai-api-3bf709a31fad" target="_blank">https://teletype.in/@loginovpavel/get-openai-token</a>. Как только у вас удастся получить доступ, продолжайте.</p>
  <p id="YxMe"><a href="https://openai.com/research/whisper" target="_blank">Whisper Model</a> — модель, способная транскрибировать аудио файлы в текст.</p>
  <p id="dLj1">Возможности модели:</p>
  <ul id="NS5f">
    <li id="qU2g">Транскрибирование аудио файла на любом языке.</li>
    <li id="5CDE">Транскрибирование аудио файла и перевод текста на английский.</li>
  </ul>
  <p id="LJSL">Для использования модели необходимо отправить байтовое представление аудио файла размером не более 25 мегабайтов в одном из доступных форматов на endpoint API. Мы будем использовать Python библиотеку <strong>openai</strong> для выполнения запросов к API в программе.</p>
  <p id="34KV">Пример из документации траскрибирующий аудио файл в текст:</p>
  <pre id="y4rZ" data-lang="python"># Листинг [1]
# Транскрибация аудио файла в текст

# Примечание: вам необходимо использовать
# OpenAI Python v0.27.0 для работы с этим кодом

import openai

audio_file = open(&quot;/path/to/file/german.mp3&quot;, &quot;rb&quot;)
transcript = openai.Audio.translate(&quot;whisper-1&quot;, audio_file)</pre>
  <p id="slRe">Сперва открывается файл по пути <code>&quot;/path/to/file/german.mp3&quot;</code> в binary моде. Далее выполняется метод <code>translate</code>, который принимает название модели <code>&quot;whisper-1&quot;</code> и файл <code>audio_file</code>.</p>
  <p id="HZtg">Формат ответа <code>transcript</code>:</p>
  <pre id="e02x" data-lang="python"># Листинг [2]
# Ответ от модели, содержимое transcript

{
  &quot;text&quot;: &quot;Текст аудио файла.&quot;
}</pre>
  <h3 id="3Qex">Создание проекта и установка зависимостей</h3>
  <p id="wZM7">Для начала необходимо установить программу <strong>ffmpeg</strong> для работы с медиа файлами в ОС Linux. Это программа необходима для работоспособности одной из зависимостей проекта, подробнее о ней далее.</p>
  <pre id="x2Sr">$ sudo apt install ffmpeg</pre>
  <p id="5UFp">Создайте папку с проектом и настройте виртуальное окружение. В окружение установите фреймворк <strong>aiogram 3</strong> (для работы с Telegram API), библиотеку <strong>openai</strong> (для работы с OpenAI API) не ниже 0.27 версии и библиотеку <strong>pydub</strong> (для работы с аудио файлами).</p>
  <pre id="ecn9">$ pip install --pre aiogram
$ pip install openai==0.27.0
$ pip install pydub</pre>
  <p id="Kfeg">Для удобства будем работать в одном файле. Создайте новый файл <strong>bot.py</strong> и поместите туда следующий код. Поменяйте токен бота на свой.</p>
  <pre id="gR3u" data-lang="python"># Листинг [3]
# Эхо бот на aiogram 3

import asyncio

from aiogram import Bot, Dispatcher, F, Router
from aiogram.types import Message

# Токен вашего телеграм бота
BOT_TOKEN = &quot;5842123168:AAHyhAIFWeh4-8jYvo72eZeq18-9P2wQmaY&quot;


router: Router = Router()

# Хендлер сообщений с которым будем работать
@router.message(F.content_type == &quot;voice&quot;)
async def process_message(message: Message):
    &quot;&quot;&quot;Принимает все голосовые сообщения и отвечает эхо.&quot;&quot;&quot;
    await message.answer(text=&quot;echo&quot;)


async def main():
    bot: Bot = Bot(token=BOT_TOKEN)
    dp: Dispatcher = Dispatcher()
    dp.include_router(router)
    await dp.start_polling(bot)


if __name__ == &quot;__main__&quot;:
    asyncio.run(main())</pre>
  <p id="4HnZ">У вас должна была получиться примерно следующая структура проекта:</p>
  <pre id="IQOE">project/
├── env/ 	# Виртуальное окружение
└── bot.py  # Код с ботом</pre>
  <p id="Qt4a">Запустите бота и проверьте работоспособность. На любое сообщение бот должен возвращать <strong>echo</strong>.</p>
  <pre id="rQXf">$ python bot.py</pre>
  <p id="nMDh">Если всё работает, двигаемся дальше.</p>
  <h3 id="aphk">Транскрибация аудио в текст</h3>
  <p id="Hnc5">Продолжаем работать в файле <strong>bot.py</strong>, добавляя последующий код в него. Перед тем как начать работать с библиотекой openai, необходимо проинициализировать API токен. Импортируем библиотеку и инициализируем токен, который вы сгенерировали ранее.</p>
  <pre id="56nr" data-lang="python"># Листинг [4]
# Инициализация openai ключа

import openai

openai.api_key = &quot;sk-BhC44H9LVVtSE74BlbkGGtPs0OTGDx21tjPVu7bzl&quot;</pre>
  <p id="tULV">Выше был пример из документации OpenAI с транскрибацией (листинг №1). В нём использовался метод <code>transcribe</code>, класса <code>Audio</code> из модуля <code>openai</code> для создания запроса к OpenAI API. Также в классе <code>Audio</code> имеется метод <code>atranscribe</code> для выполнения этого же запроса, но в асинхронном режиме. Метод принимает два аргумента - название модели (<code>&quot;whisper-1&quot;</code>) и файл для транскрибации.</p>
  <p id="BwZc">Создадим следующую функцию:</p>
  <pre id="zn3F" data-lang="python"># Листинг [5]
# Функция для транскрибации аудио файла

async def audio_to_text(file_path: str) -&gt; str:
    &quot;&quot;&quot;Принимает путь к аудио файлу, возвращает текст файла.&quot;&quot;&quot;
    with open(file_path, &quot;rb&quot;) as audio_file:
        transcript = await openai.Audio.atranscribe(
            &quot;whisper-1&quot;, audio_file
        )
    return transcript[&quot;text&quot;]</pre>
  <p id="rOCw">Функция принимает путь к аудио файлу <code>file_path</code>. Открывает файл на чтение в binary моде <code>audio_file</code>. Выполняет запрос методом <code>atranscribe</code> класса <code>Audio</code>, передавая название модели и сам файл. В листинге №2 указан формат ответа от модели, поэтому для возвращения результирующего текста обращаемся к ключу <code>&quot;text&quot;</code> ответа <code>transcript</code>.</p>
  <p id="gFoA">Далее напишем функцию для сохранения голосового сообщения с сервера Telegram. Для этого подготовим директорию <strong>voice_files</strong>.</p>
  <p id="rqIl">У вас должна была получиться следующая структура проекта:</p>
  <pre id="BoNB">project/
├── env/ 	        # Виртуальное окружение
├── voice_files/    # Хранилище войсов в .mp3 формате
└── bot.py          # Код с ботом</pre>
  <p id="i7w8">Импортируем <code>io</code>, <code>Voice</code>, <code>AudioSegment</code>.</p>
  <pre id="RhlH" data-lang="python"># Листинг [6]
# Необходимые импорты

import asyncio
import io

from aiogram import Bot, Dispatcher, F, Router
from aiogram.types import Message, Voice

import openai
from pydub import AudioSegment</pre>
  <p id="D3Jo">Разберём функцию по шагам:</p>
  <pre id="rKUS" data-lang="python"># Листинг [7]
# Функция сохраняющая голосовое сообщение в mp3

async def save_voice_as_mp3(bot: Bot, voice: Voice) -&gt; str:
    &quot;&quot;&quot;Скачивает голосовое сообщение и сохраняет в формате mp3.&quot;&quot;&quot;
    voice_file_info = await bot.get_file(voice.file_id)
    voice_ogg = io.BytesIO()
    await bot.download_file(voice_file_info.file_path, voice_ogg)
    
    voice_mp3_path = f&quot;voice_files/voice-{voice.file_unique_id}.mp3&quot;
    AudioSegment.from_file(voice_ogg, format=&quot;ogg&quot;).export(
	    voice_mp3_path, format=&quot;mp3&quot;
	)
    return voice_mp3_path</pre>
  <p id="gJOg">Функция принимает экземпляр бота <code>bot</code> для скачивания файлов и экземпляр голосового сообщения <code>voice</code> с необходимой информацией.</p>
  <p id="XAsZ">Голосовые сообщения скачиваются в формате <code>.ogg</code>, а модель Whisper с этим форматом не работает. Поэтому мы будем скачивать файл с сервера в переменную <code>voice_ogg</code>, затем конвертировать его в формат <code>.mp3</code> в папку <strong>voice_files</strong>.</p>
  <p id="9PIO">Метод <code>bot.get_file</code> принимает идентификатор файла голосового сообщения <code>voice</code> и возвращает информацию о файле. Нам нужен путь к файлу.</p>
  <p id="21cb">Создаем байтовую переменную <code>voice_ogg</code> в которую будет скачиваться голосовое сообщение с сервера в формате <code>.ogg</code>. Затем методом <code>bot.download_file</code>, который принимает путь к файлу на сервере и место назначения для сохранения, скачиваем файл. Передаем путь к файлу <code>voice_file_info.file_path</code> и место для сохранения <code>voice_ogg</code>.</p>
  <p id="7rHM">Далее используем класс <code>AudioSegment</code> из библиотеки <code>pydub</code> для преобразования формата файла из <code>.ogg</code> в <code>.mp3</code>. Задаем путь для сохранения файла в переменную <code>voice_mp3_path</code>. Используем метод <code>from_file</code> для открытия файла и сразу же метод <code>export</code> для сохранения конвертированного файла по месту назначения, указывая форматы данных.</p>
  <p id="yHjF">Функция возвращает путь к скаченному файлу.</p>
  <p id="TyPU">Осталось за малым — соединить всё воедино в нашем обработчике. Для этого отредактируем его следующим образом:</p>
  <pre id="to9l" data-lang="python"># Листинг [8]
# Обработчик транскрибирующий голосовые сообщения

@router.message(F.content_type == &quot;voice&quot;)
async def process_voice_message(message: Message, bot: Bot):
    &quot;&quot;&quot;Принимает голосовое сообщение, транскрибирует его в текст.&quot;&quot;&quot;
    voice_path = await save_voice_as_mp3(bot, message.voice)
    transcripted_voice_text = await audio_to_text(voice_path)

    if transcripted_voice_text:
        await message.reply(text=transcripted_voice_text)</pre>
  <p id="5cMY">Наш обработчик <code>process_voice_message</code> принимает все голосовые сообщения пользователя, как указано в декораторе. Сперва идет вызов нашей функции <code>save_voice_as_mp3</code> из листинга №7 для скачивания голосового сообщения в директорию <strong>voice_files</strong> в формате <code>.mp3</code>, она возвращает путь к скаченному файлу. Далее вызывается функция <code>audio_to_text</code> из листинга №5 для выполнения запроса к OpenAI API на транскрибацию. Функция возвращает текст аудио файла. Если модель разобрала речь, то возвращается не пустая строка, которую мы распечатываем в чат Telegram бота.</p>
  <p id="LByY">Полный код файла <strong>bot.py</strong>:</p>
  <pre id="dXa1" data-lang="python"># Листинг [9]
# Телеграм бот транскрибирующий голосовые сообщения

import asyncio
import io

from aiogram import Bot, Dispatcher, F, Router
from aiogram.types import Message, Voice

import openai
from pydub import AudioSegment


openai.api_key = &quot;sk-BhC44H9LVVtSE74BlbkGGtPs0OTGDx21tjPVu7bzl&quot;
BOT_TOKEN = &quot;5842123168:AAHyhAIFWeh4-8jYvo72eZeq18-9P2wQmaY&quot;

router: Router = Router()


async def audio_to_text(file_path: str) -&gt; str:
    &quot;&quot;&quot;Принимает путь к аудио файлу, возвращает текст файла.&quot;&quot;&quot;
    with open(file_path, &quot;rb&quot;) as audio_file:
        transcript = await openai.Audio.atranscribe(
	        &quot;whisper-1&quot;, audio_file
	    )
    return transcript[&quot;text&quot;]


async def save_voice_as_mp3(bot: Bot, voice: Voice) -&gt; str:
    &quot;&quot;&quot;Скачивает голосовое сообщение и сохраняет в формате mp3.&quot;&quot;&quot;
    voice_file_info = await bot.get_file(voice.file_id)
    voice_ogg = io.BytesIO()
    await bot.download_file(voice_file_info.file_path, voice_ogg)
    voice_mp3_path = f&quot;voice_files/voice-{voice.file_unique_id}.mp3&quot;
    AudioSegment.from_file(voice_ogg, format=&quot;ogg&quot;).export(
	    voice_mp3_path, format=&quot;mp3&quot;
	)
    return voice_mp3_path


@router.message(F.content_type == &quot;voice&quot;)
async def process_voice_message(message: Message, bot: Bot):
    &quot;&quot;&quot;Принимает все голосовые сообщения и транскрибирует их в текст.&quot;&quot;&quot;
    voice_path = await save_voice_as_mp3(bot, message.voice)
    transcripted_voice_text = await audio_to_text(voice_path)

    if transcripted_voice_text:
        await message.reply(text=transcripted_voice_text)


async def main():
    bot: Bot = Bot(token=BOT_TOKEN)
    dp: Dispatcher = Dispatcher()
    dp.include_router(router)
    await dp.start_polling(bot)


if __name__ == &quot;__main__&quot;:
    asyncio.run(main())</pre>
  <p id="7UtV"><strong>Результат:</strong></p>
  <figure id="fHiU" class="m_original">
    <img src="https://img4.teletype.in/files/7d/c2/7dc2588a-0bac-4500-942f-1a26a4102fba.png" width="481" />
    <figcaption>Работа бота</figcaption>
  </figure>
  <p id="uis5">В статье мы рассмотрели эффективный способ получения текстовых версий голосовых сообщений в Telegram боте, используя API OpenAI. Использование OpenAI API значительно упрощает процесс транскрибации голосовых сообщений и делает его более точным и надежным. Всем удачи в изучении!</p>

]]></content:encoded></item><item><guid isPermaLink="true">https://teletype.in/@loginovpavel/get-openai-token</guid><link>https://teletype.in/@loginovpavel/get-openai-token?utm_source=teletype&amp;utm_medium=feed_rss&amp;utm_campaign=loginovpavel</link><comments>https://teletype.in/@loginovpavel/get-openai-token?utm_source=teletype&amp;utm_medium=feed_rss&amp;utm_campaign=loginovpavel#comments</comments><dc:creator>loginovpavel</dc:creator><title>Получение доступа к OpenAI API</title><pubDate>Mon, 20 Mar 2023 18:59:18 GMT</pubDate><media:content medium="image" url="https://img1.teletype.in/files/04/35/04352375-13af-4681-b9cb-1c6e738a12f9.png"></media:content><category>Open AI</category><description><![CDATA[<img src="https://img1.teletype.in/files/cb/9c/cb9cefc3-dbbd-4e87-b13f-8ab06b0eadde.jpeg"></img>В статье изложена инструкция получения токена OpenAI API и обхода блокировки OpenAI сайта из России.]]></description><content:encoded><![CDATA[
  <h3 id="PwiC"><strong>Получение доступа к OpenAI API</strong></h3>
  <p id="pXgP">В статье изложена инструкция получения токена OpenAI API и обхода блокировки OpenAI сайта из России.</p>
  <figure id="zetp" class="m_column">
    <img src="https://img1.teletype.in/files/cb/9c/cb9cefc3-dbbd-4e87-b13f-8ab06b0eadde.jpeg" width="800" />
  </figure>
  <p id="uRTn"><strong>OpenAI API</strong> — это программный интерфейс приложения, который предоставляет доступ к технологиям искусственного интеллекта, разработанным и поддерживаемым исследовательской лабораторией OpenAI. API позволяет разработчикам интегрировать функциональность OpenAI в свои собственные приложения и использовать различные модели машинного обучения, такие как обработка естественного языка, генерация текста и т.д.</p>
  <p id="0Q78">Чтобы использовать API, вам необходимо получить токен на сайте <a href="https://platform.openai.com/" target="_blank">OpenAI</a>. Для России сайт заблокирован. Для преодоления блокировки необходимо иметь VPN и заграничный телефонный номер, чтобы получить СМС для регистрации. В статье на Хабр подробно и точно описано как обойти блокировку при помощи “виртуального” номера телефона. У меня это заняло примерно 10 минут и 20 рублей. Ссылка на статью: <a href="https://habr.com/ru/post/704600/" target="_blank">Как получить доступ к chatGPT в России</a>.</p>
  <p id="70np">Если вы получили доступ к платформе OpenAI и смогли зайти на страницу ChatGPT, тогда мы можем продолжать.</p>
  <p id="e99s">Сперва перейдем на страницу входа в аккаунт OpenAI: <a href="https://platform.openai.com/signup" target="_blank">https://platform.openai.com/signup</a></p>
  <p id="Jnqb">После входа вас должно направить на страницу: <a href="https://platform.openai.com/overview" target="_blank">https://platform.openai.com/overview</a></p>
  <p id="6JiV">В меню находим <strong>View API keys</strong> и переходим туда:</p>
  <figure id="I8AX" class="m_column">
    <img src="https://img2.teletype.in/files/91/f9/91f983db-7a68-4d44-accf-075777d0c79e.png" width="700" />
    <figcaption>Раскрывающееся меню при нажатии на Personal</figcaption>
  </figure>
  <p id="PoU9">Тут у вас отображается список имеющихся токенов и есть кнопка создания нового <strong>Create new secret key</strong>, нажимаем на неё:</p>
  <figure id="GPbB" class="m_column">
    <img src="https://img3.teletype.in/files/2c/ce/2cce4df1-af40-4673-8d80-4fd827e73b4c.png" width="700" />
    <figcaption>Страница с API токенами</figcaption>
  </figure>
  <p id="h3PY">После нажатия на кнопку у вас сгенерируется новый токен. Вам покажут его только один раз, поэтому необходимо сразу же его сохранить в доступном для вас и надёжном месте.</p>
  <figure id="BmfC" class="m_column">
    <img src="https://img3.teletype.in/files/e0/11/e01115fb-6aea-438d-b143-2bd45caa4e74.png" width="601" />
    <figcaption>Новый сгенерированный токен</figcaption>
  </figure>
  <p id="3Rsy">Поздравляю! Теперь у вас есть возможность использовать OpenAI API в своих приложениях. Исследуйте документацию и возможности этого инструмента, они обширны и интересны. Удачи в изучении!</p>

]]></content:encoded></item></channel></rss>