<?xml version="1.0" encoding="utf-8" ?><feed xmlns="http://www.w3.org/2005/Atom" xmlns:tt="http://teletype.in/" xmlns:opensearch="http://a9.com/-/spec/opensearch/1.1/"><title>Павел Логинов</title><subtitle>👨‍💻 Backend Developer — пишу о технологиях и практиках программирования, делюсь опытом и находками.</subtitle><author><name>Павел Логинов</name></author><id>https://teletype.in/atom/loginovpavel</id><link rel="self" type="application/atom+xml" href="https://teletype.in/atom/loginovpavel?offset=0"></link><link rel="alternate" type="text/html" href="https://teletype.in/@loginovpavel?utm_source=teletype&amp;utm_medium=feed_atom&amp;utm_campaign=loginovpavel"></link><link rel="next" type="application/rss+xml" href="https://teletype.in/atom/loginovpavel?offset=10"></link><link rel="search" type="application/opensearchdescription+xml" title="Teletype" href="https://teletype.in/opensearch.xml"></link><updated>2026-04-28T05:52:00.270Z</updated><entry><id>loginovpavel:best-practices-alembic-sqlalchemy</id><link rel="alternate" type="text/html" href="https://teletype.in/@loginovpavel/best-practices-alembic-sqlalchemy?utm_source=teletype&amp;utm_medium=feed_atom&amp;utm_campaign=loginovpavel"></link><title>Хорошие практики Alembic и SQLAlchemy</title><published>2024-10-30T06:41:36.138Z</published><updated>2024-10-30T06:41:36.138Z</updated><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://img3.teletype.in/files/24/84/248468c9-899c-492d-84e3-e35284ae2fd6.png"></media:thumbnail><category term="python" label="Python"></category><summary type="html">&lt;img src=&quot;https://img4.teletype.in/files/b3/93/b393d88a-c2e7-495a-bbc8-f16693366526.jpeg&quot;&gt;В этой статье я кратко рассмотрю несколько практик, которые помогут поддерживать порядок в проекте, упростить поддержку базы данных и избежать распространённых ошибок при работе с Alembic и SQLAlchemy. Эти приёмы не раз спасали меня от проблем.</summary><content type="html">
  &lt;figure id=&quot;bbcq&quot; class=&quot;m_column&quot;&gt;
    &lt;img src=&quot;https://img4.teletype.in/files/b3/93/b393d88a-c2e7-495a-bbc8-f16693366526.jpeg&quot; width=&quot;1792&quot; /&gt;
  &lt;/figure&gt;
  &lt;p id=&quot;Tovj&quot;&gt;В этой статье я кратко рассмотрю несколько практик, которые помогут поддерживать порядок в проекте, упростить поддержку базы данных и избежать распространённых ошибок при работе с Alembic и SQLAlchemy. Эти приёмы не раз спасали меня от проблем. Вот темы, которые мы затронем:&lt;/p&gt;
  &lt;ol id=&quot;q6P3&quot;&gt;
    &lt;li id=&quot;gQ7m&quot;&gt;Соглашение об именовании (Naming Convention)&lt;/li&gt;
    &lt;li id=&quot;5zbS&quot;&gt;Сортировка миграций по дате&lt;/li&gt;
    &lt;li id=&quot;m0g2&quot;&gt;Комментарии таблиц, колонок и миграций&lt;/li&gt;
    &lt;li id=&quot;o1TB&quot;&gt;Работа с данными в миграциях без моделей&lt;/li&gt;
    &lt;li id=&quot;YtV8&quot;&gt;Тестирование миграций (Stairway Test)&lt;/li&gt;
    &lt;li id=&quot;tLmG&quot;&gt;Отдельный сервис для проведения миграций&lt;/li&gt;
    &lt;li id=&quot;hr41&quot;&gt;Использование миксинов для моделей&lt;/li&gt;
  &lt;/ol&gt;
  &lt;hr /&gt;
  &lt;h2 id=&quot;1-naming-convention-&quot;&gt;1. Соглашение об именовании (Naming Convention)&lt;/h2&gt;
  &lt;p id=&quot;6j3k&quot;&gt;SQLAlchemy позволяет задать &lt;a href=&quot;https://alembic.sqlalchemy.org/en/latest/naming.html&quot; target=&quot;_blank&quot;&gt;соглашение об именовании&lt;/a&gt;, которое автоматически применится ко всем таблицам и ограничениям при генерации миграций. Это избавляет от необходимости вручную задавать имена индексов, внешних ключей и других ограничений, что упрощает навигацию и делает структуру базы данных предсказуемой и унифицированной.&lt;/p&gt;
  &lt;p id=&quot;Z9qc&quot;&gt;У меня нет рецепта как добавить конвенцию в существующий проект, кроме как перегенерировать миграции. Для нового проекта достаточно добавить соглашение у базового класса, чтобы Alembic автоматически прописывал имена в нужном формате. Пример такого формата ниже, который хорошо подходит большинству случаев:&lt;/p&gt;
  &lt;pre id=&quot;RWue&quot; data-lang=&quot;python&quot;&gt;from sqlalchemy import MetaData
from sqlalchemy.orm import DeclarativeBase

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

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

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

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

def upgrade():
    # Определяем таблицу user_account для работы с данными
    user_account = table(
        &amp;#x27;user_account&amp;#x27;,
        column(&amp;#x27;id&amp;#x27;),
        column(&amp;#x27;username&amp;#x27;, String),
        column(&amp;#x27;email&amp;#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&amp;quot;{user.username}@example.com&amp;quot;)
        )

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

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

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

  app:
    ...
    depends_on:
      app_migrations:
        condition: service_completed_successfully&lt;/pre&gt;
  &lt;p id=&quot;BtFV&quot;&gt;Использование условного &lt;code&gt;depends_on&lt;/code&gt; гарантирует, что миграции начнутся только после того, как база данных будет полностью готова к работе, а приложение запустится после завершения миграций.&lt;/p&gt;
  &lt;hr /&gt;
  &lt;h2 id=&quot;7-&quot;&gt;7. Миксины для моделей&lt;/h2&gt;
  &lt;p id=&quot;N6eI&quot;&gt;Да, это очевидный пункт, но всё же напомню, чтобы никто не забыл. Использование миксинов — удобный способ избежать дублирования кода. Миксины — это классы с часто используемыми полями и методами, которые можно подключить к любым моделям, где они нужны. Например, часто нам нужны поля &lt;code&gt;created_at&lt;/code&gt; и &lt;code&gt;updated_at&lt;/code&gt; для отслеживания времени создания и обновления записей. Также бывает полезно использовать &lt;code&gt;id&lt;/code&gt; на основе UUID, чтобы унифицировать первичные ключи. Всё это можно вынести в миксины.&lt;/p&gt;
  &lt;pre id=&quot;i1o3&quot; data-lang=&quot;python&quot;&gt;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=&amp;quot;Время создания записи&amp;quot;
    )
    updated_at = Column(
        DateTime,
        onupdate=func.now(),
        nullable=True,
        comment=&amp;quot;Время последнего обновления записи&amp;quot;
    )

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

</content></entry><entry><id>loginovpavel:wait-for-service</id><link rel="alternate" type="text/html" href="https://teletype.in/@loginovpavel/wait-for-service?utm_source=teletype&amp;utm_medium=feed_atom&amp;utm_campaign=loginovpavel"></link><title>Ожидание готовности сервисов в Docker Compose: wait-for-it vs Healthcheck</title><published>2024-10-12T08:49:25.446Z</published><updated>2024-10-12T08:49:25.446Z</updated><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://img4.teletype.in/files/f2/8d/f28dea12-1e76-4a89-a37d-5b6e08b41041.png"></media:thumbnail><category term="docker" label="Docker"></category><summary type="html">&lt;img src=&quot;https://img4.teletype.in/files/f1/0a/f10ad648-a59f-432b-8b20-99c2b6853649.jpeg&quot;&gt;При разработке приложений с помощью Docker Compose часто возникает ситуация, когда один сервис должен дождаться готовности другого перед началом своей работы. Например, веб-приложению может понадобиться дождаться запуска базы данных или другого зависимого сервиса. В этой статье мы рассмотрим два популярных способа решения этой задачи:</summary><content type="html">
  &lt;figure id=&quot;oKSO&quot; class=&quot;m_column&quot;&gt;
    &lt;img src=&quot;https://img4.teletype.in/files/f1/0a/f10ad648-a59f-432b-8b20-99c2b6853649.jpeg&quot; width=&quot;1434&quot; /&gt;
  &lt;/figure&gt;
  &lt;p id=&quot;wI2b&quot;&gt;При разработке приложений с помощью Docker Compose часто возникает ситуация, когда один сервис должен дождаться готовности другого перед началом своей работы. Например, веб-приложению может понадобиться дождаться запуска базы данных или другого зависимого сервиса. В этой статье мы рассмотрим два популярных способа решения этой задачи:&lt;/p&gt;
  &lt;ol id=&quot;CuIv&quot;&gt;
    &lt;li id=&quot;jKOD&quot;&gt;Скрипт &lt;code&gt;wait-for-it.sh&lt;/code&gt;&lt;/li&gt;
    &lt;li id=&quot;Dscy&quot;&gt;Использование &lt;code&gt;depends_on&lt;/code&gt; и &lt;code&gt;healthchecks&lt;/code&gt; в Docker Compose&lt;/li&gt;
  &lt;/ol&gt;
  &lt;p id=&quot;jB5Y&quot;&gt;Для демонстрации мы создадим простое приложение на Python, но для понимания статьи знание Python не требуется — вы сможете просто скопировать код.&lt;/p&gt;
  &lt;h2 id=&quot;-&quot;&gt;Настройка рабочего окружения&lt;/h2&gt;
  &lt;p id=&quot;aJIz&quot;&gt;Для начала создадим рабочую директорию:&lt;/p&gt;
  &lt;pre id=&quot;XzWJ&quot; data-lang=&quot;bash&quot;&gt;mkdir waitforit
cd waitforit&lt;/pre&gt;
  &lt;p id=&quot;l2jF&quot;&gt;Теперь создадим тестовое веб-приложение на базе &lt;code&gt;aiohttp&lt;/code&gt;, которое будет включать эндпоинт &lt;code&gt;/healthz&lt;/code&gt; для проверки состояния. Также добавим возможность задержки перед запуском через переменную окружения &lt;code&gt;SLEEP_BEFORE_START&lt;/code&gt;. Эта переменная позволит имитировать задержку старта сервиса. Чтобы сымитировать задержку старта сервиса, будем передавать в эту переменную количество секунд на которое надо задержаться перед запуском приложения.&lt;/p&gt;
  &lt;p id=&quot;Sq43&quot;&gt;&lt;strong&gt;Файл:&lt;/strong&gt; &lt;code&gt;app.py&lt;/code&gt;&lt;/p&gt;
  &lt;pre id=&quot;LP9T&quot; data-lang=&quot;python&quot;&gt;import os
import time
from aiohttp import web

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

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

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

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

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

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

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

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

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

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

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

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

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

</content></entry><entry><id>loginovpavel:python-typing</id><link rel="alternate" type="text/html" href="https://teletype.in/@loginovpavel/python-typing?utm_source=teletype&amp;utm_medium=feed_atom&amp;utm_campaign=loginovpavel"></link><title>Типизация в Python: Сильно, Динамично, Неявно</title><published>2023-11-07T20:06:04.194Z</published><updated>2023-11-07T20:06:04.194Z</updated><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://img3.teletype.in/files/eb/b4/ebb4bc43-b6c2-4f26-88fc-5c255854b373.png"></media:thumbnail><category term="python" label="Python"></category><summary type="html">&lt;img src=&quot;https://img3.teletype.in/files/25/17/25170f7b-5ecc-4c78-aff3-df01a571bb04.png&quot;&gt;Все знают, что в Python типы данных делятся на изменяемые и неизменяемые, а как устроена сама типизация в Python? Чтобы ответить на этот вопрос, надо рассмотреть какими характеристиками обладает система типов Python.</summary><content type="html">
  &lt;figure id=&quot;yoTX&quot; class=&quot;m_column&quot;&gt;
    &lt;img src=&quot;https://img3.teletype.in/files/25/17/25170f7b-5ecc-4c78-aff3-df01a571bb04.png&quot; width=&quot;1793&quot; /&gt;
  &lt;/figure&gt;
  &lt;p id=&quot;vifl&quot;&gt;Все знают, что в Python типы данных делятся на изменяемые и неизменяемые, а как устроена сама типизация в Python? Чтобы ответить на этот вопрос, надо рассмотреть какими характеристиками обладает система типов Python.&lt;/p&gt;
  &lt;p id=&quot;6guK&quot;&gt;Обычно, когда описывают систему типов языка программирования, отвечают на 3 вопроса:&lt;/p&gt;
  &lt;ol id=&quot;ZKJ1&quot;&gt;
    &lt;li id=&quot;Qkg5&quot;&gt;Выполняет ли язык неявные автоматические преобразования типов?&lt;/li&gt;
    &lt;li id=&quot;Hp61&quot;&gt;На каком этапе выясняется тип переменной?&lt;/li&gt;
    &lt;li id=&quot;nd14&quot;&gt;Надо ли явно указывать тип переменных?&lt;/li&gt;
  &lt;/ol&gt;
  &lt;p id=&quot;w9Is&quot;&gt;Отвечая на эти вопросы, можно узнать имеет ли язык &lt;a href=&quot;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&quot; target=&quot;_blank&quot;&gt;сильную или слабую типизацию&lt;/a&gt;, &lt;a href=&quot;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&quot; target=&quot;_blank&quot;&gt;статическую&lt;/a&gt; или &lt;a href=&quot;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&quot; target=&quot;_blank&quot;&gt;динамическую&lt;/a&gt;, &lt;a href=&quot;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&quot; target=&quot;_blank&quot;&gt;явную или неявную&lt;/a&gt;.&lt;/p&gt;
  &lt;p id=&quot;4wao&quot;&gt;Python имеет &lt;strong&gt;сильную динамическую неявную&lt;/strong&gt; типизацию и давайте подробнее рассмотрим почему.&lt;/p&gt;
  &lt;p id=&quot;li2t&quot;&gt;&lt;/p&gt;
  &lt;h2 id=&quot;1-&quot;&gt;1. Сильная типизация&lt;/h2&gt;
  &lt;p id=&quot;WIil&quot;&gt;Когда мы говорим о &amp;quot;сильной типизации&amp;quot; в контексте программирования, мы имеем в виду строгий подход языка к обработке переменных разных типов. В языке программирования Python, который отличается сильной типизацией, разные типы данных не смешиваются автоматически. Так, выражение &lt;code&gt;&amp;quot;some string&amp;quot; - 3&lt;/code&gt; вызовет ошибку, потому что язык не позволяет неявно преобразовывать строку в число для выполнения математической операции. При ошибках связанных с типами Python генерирует исключение &lt;strong&gt;TypeError&lt;/strong&gt;.&lt;/p&gt;
  &lt;p id=&quot;sypA&quot;&gt;Аналогично, попытка сложить список &lt;code&gt;[2, 1, 0]&lt;/code&gt; и множество &lt;code&gt;set([2, 23, 2])&lt;/code&gt; также приведет к ошибке, поскольку Python не будет искать способы автоматически преобразовать одну структуру данных в другую для выполнения операции. В противопоставление можно привести в пример язык JavaScript, который позволяет без проблем складывать строки с числами: &lt;code&gt;3 + &amp;#x27;1&amp;#x27; // Получится строка: &amp;#x27;31&amp;#x27;&lt;/code&gt;.&lt;/p&gt;
  &lt;p id=&quot;OFuE&quot;&gt;Несмотря на сильную типизацию, Python допускает некоторые операции между различными типами данных, но это объясняется явной реализацией, а не автоматическим преобразованием:&lt;/p&gt;
  &lt;pre id=&quot;x7FZ&quot; data-lang=&quot;python&quot;&gt;# Повторение последовательностей:
# вы можете &amp;quot;умножить&amp;quot; строку или список на число,
# и это даст повторяющуюся последовательность.

print(&amp;quot;word&amp;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,
# и здесь операция сложения не вызывает сомнений.

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


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

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

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


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

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

</content></entry><entry><id>loginovpavel:docker-installation</id><link rel="alternate" type="text/html" href="https://teletype.in/@loginovpavel/docker-installation?utm_source=teletype&amp;utm_medium=feed_atom&amp;utm_campaign=loginovpavel"></link><title>Быстрая Установка Docker и Compose на Ubuntu</title><published>2023-11-04T19:58:22.294Z</published><updated>2023-11-04T19:58:22.294Z</updated><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://img3.teletype.in/files/ad/7e/ad7e65cb-bf48-4b13-ad27-975582e3bf0a.png"></media:thumbnail><category term="guide" label="Guide"></category><summary type="html">&lt;img src=&quot;https://img2.teletype.in/files/59/34/59347b16-782f-41f9-bfa3-a0481498b37a.jpeg&quot;&gt;Быстрая установка Docker и Docker Compose на Ubuntu используя простой скрипт.</summary><content type="html">
  &lt;figure id=&quot;QtH0&quot; class=&quot;m_column&quot;&gt;
    &lt;img src=&quot;https://img2.teletype.in/files/59/34/59347b16-782f-41f9-bfa3-a0481498b37a.jpeg&quot; width=&quot;1920&quot; /&gt;
  &lt;/figure&gt;
  &lt;p id=&quot;ienN&quot;&gt;Docker — это невероятно мощный инструмент, но иногда его настройка на Ubuntu иногда может быть сложной задачей. В этом посте мы рассмотрим шаги &lt;strong&gt;установки Docker (и Docker Compose) на Ubuntu&lt;/strong&gt; с использованием одного скрипта.&lt;/p&gt;
  &lt;p id=&quot;ggjp&quot;&gt;Прежде чем мы начнем процесс установки, убедитесь, что вы используете Ubuntu. Шаги, предоставленные в этом руководстве, написаны для Ubuntu, но могут быть применимы к другим дистрибутивам с некоторыми изменениями.&lt;/p&gt;
  &lt;p id=&quot;PV05&quot;&gt;&lt;strong&gt;TL;DR:&lt;/strong&gt;&lt;/p&gt;
  &lt;pre id=&quot;PVDq&quot; data-lang=&quot;bash&quot;&gt;wget https://gist.githubusercontent.com/welel/f80c96482e3b539487b9fa08bfcab86d/raw/90bc2330924d225aef7dc3178f5926bda7daff04/install_docker.sh

sudo chmod +x install_docker.sh

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

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

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

$ ssh root@62.76.228.108

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

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

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

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

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

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

</content></entry><entry><id>loginovpavel:speech-to-text-telegram</id><link rel="alternate" type="text/html" href="https://teletype.in/@loginovpavel/speech-to-text-telegram?utm_source=teletype&amp;utm_medium=feed_atom&amp;utm_campaign=loginovpavel"></link><title>Speech-to-text: Транскрибация Telegram Voice в текст используя OpenAI API</title><published>2023-03-22T11:14:23.482Z</published><updated>2023-07-19T18:04:22.424Z</updated><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://img1.teletype.in/files/04/dd/04dd85ad-d0ec-4783-b280-4340c8643c5c.png"></media:thumbnail><category term="open-ai" label="Open AI"></category><summary type="html">&lt;img src=&quot;https://img2.teletype.in/files/5d/96/5d96b500-866d-42a7-b73f-d1c835c00276.jpeg&quot;&gt;В статье рассмотрим как получить текст голосового сообщения в Telegram боте, используя API OpenAI.</summary><content type="html">
  &lt;p id=&quot;6UPd&quot;&gt;В статье рассмотрим как получить текст голосового сообщения в Telegram боте, используя API OpenAI.&lt;/p&gt;
  &lt;figure id=&quot;LeFn&quot; class=&quot;m_column&quot;&gt;
    &lt;img src=&quot;https://img2.teletype.in/files/5d/96/5d96b500-866d-42a7-b73f-d1c835c00276.jpeg&quot; width=&quot;800&quot; /&gt;
    &lt;figcaption&gt;voice-to-text&lt;/figcaption&gt;
  &lt;/figure&gt;
  &lt;p id=&quot;YnB5&quot;&gt;&lt;strong&gt;Требования&lt;/strong&gt;. У вас должна быть Linux система с установленным Python 3.10+. Если вы в России, потребуется VPN. В качестве Telegram фреймворка используется aiogram 3. Для успешного прохождения туториала необходимы некоторые знания aiogram 3.&lt;/p&gt;
  &lt;hr /&gt;
  &lt;p id=&quot;4NZ1&quot;&gt;&lt;strong&gt;Транскрибация&lt;/strong&gt; — это перевод аудио в текст. В нашем случае: перевод голосового сообщения (voice) в текст.&lt;/p&gt;
  &lt;h2 id=&quot;w10P&quot;&gt;OpenAI API&lt;/h2&gt;
  &lt;p id=&quot;3ofR&quot;&gt;&lt;strong&gt;OpenAI API&lt;/strong&gt; — это программный интерфейс приложения, который предоставляет доступ к технологиям искусственного интеллекта, разработанным и поддерживаемым OpenAI. Для решения нашей задачи нам понадобится модель &lt;strong&gt;Whisper&lt;/strong&gt; от OpenAI.&lt;/p&gt;
  &lt;p id=&quot;VBTG&quot;&gt;Прежде чем продолжать, вам следует убедиться, что у вас есть доступ к OpenAI API. В данной статье описан подробный алгоритм регистрации и получения токена для использования OpenAI API: &lt;a href=&quot;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&quot; target=&quot;_blank&quot;&gt;https://teletype.in/@loginovpavel/get-openai-token&lt;/a&gt;. Как только у вас удастся получить доступ, продолжайте.&lt;/p&gt;
  &lt;p id=&quot;YxMe&quot;&gt;&lt;a href=&quot;https://openai.com/research/whisper&quot; target=&quot;_blank&quot;&gt;Whisper Model&lt;/a&gt; — модель, способная транскрибировать аудио файлы в текст.&lt;/p&gt;
  &lt;p id=&quot;dLj1&quot;&gt;Возможности модели:&lt;/p&gt;
  &lt;ul id=&quot;NS5f&quot;&gt;
    &lt;li id=&quot;qU2g&quot;&gt;Транскрибирование аудио файла на любом языке.&lt;/li&gt;
    &lt;li id=&quot;5CDE&quot;&gt;Транскрибирование аудио файла и перевод текста на английский.&lt;/li&gt;
  &lt;/ul&gt;
  &lt;p id=&quot;LJSL&quot;&gt;Для использования модели необходимо отправить байтовое представление аудио файла размером не более 25 мегабайтов в одном из доступных форматов на endpoint API. Мы будем использовать Python библиотеку &lt;strong&gt;openai&lt;/strong&gt; для выполнения запросов к API в программе.&lt;/p&gt;
  &lt;p id=&quot;34KV&quot;&gt;Пример из документации траскрибирующий аудио файл в текст:&lt;/p&gt;
  &lt;pre id=&quot;y4rZ&quot; data-lang=&quot;python&quot;&gt;# Листинг [1]
# Транскрибация аудио файла в текст

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

import openai

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

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

import asyncio

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

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


router: Router = Router()

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


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


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

import openai

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

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

import asyncio
import io

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

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

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

@router.message(F.content_type == &amp;quot;voice&amp;quot;)
async def process_voice_message(message: Message, bot: Bot):
    &amp;quot;&amp;quot;&amp;quot;Принимает голосовое сообщение, транскрибирует его в текст.&amp;quot;&amp;quot;&amp;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)&lt;/pre&gt;
  &lt;p id=&quot;5cMY&quot;&gt;Наш обработчик &lt;code&gt;process_voice_message&lt;/code&gt; принимает все голосовые сообщения пользователя, как указано в декораторе. Сперва идет вызов нашей функции &lt;code&gt;save_voice_as_mp3&lt;/code&gt; из листинга №7 для скачивания голосового сообщения в директорию &lt;strong&gt;voice_files&lt;/strong&gt; в формате &lt;code&gt;.mp3&lt;/code&gt;, она возвращает путь к скаченному файлу. Далее вызывается функция &lt;code&gt;audio_to_text&lt;/code&gt; из листинга №5 для выполнения запроса к OpenAI API на транскрибацию. Функция возвращает текст аудио файла. Если модель разобрала речь, то возвращается не пустая строка, которую мы распечатываем в чат Telegram бота.&lt;/p&gt;
  &lt;p id=&quot;LByY&quot;&gt;Полный код файла &lt;strong&gt;bot.py&lt;/strong&gt;:&lt;/p&gt;
  &lt;pre id=&quot;dXa1&quot; data-lang=&quot;python&quot;&gt;# Листинг [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 = &amp;quot;sk-BhC44H9LVVtSE74BlbkGGtPs0OTGDx21tjPVu7bzl&amp;quot;
BOT_TOKEN = &amp;quot;5842123168:AAHyhAIFWeh4-8jYvo72eZeq18-9P2wQmaY&amp;quot;

router: Router = Router()


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


async def save_voice_as_mp3(bot: Bot, voice: Voice) -&amp;gt; str:
    &amp;quot;&amp;quot;&amp;quot;Скачивает голосовое сообщение и сохраняет в формате mp3.&amp;quot;&amp;quot;&amp;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&amp;quot;voice_files/voice-{voice.file_unique_id}.mp3&amp;quot;
    AudioSegment.from_file(voice_ogg, format=&amp;quot;ogg&amp;quot;).export(
	    voice_mp3_path, format=&amp;quot;mp3&amp;quot;
	)
    return voice_mp3_path


@router.message(F.content_type == &amp;quot;voice&amp;quot;)
async def process_voice_message(message: Message, bot: Bot):
    &amp;quot;&amp;quot;&amp;quot;Принимает все голосовые сообщения и транскрибирует их в текст.&amp;quot;&amp;quot;&amp;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__ == &amp;quot;__main__&amp;quot;:
    asyncio.run(main())&lt;/pre&gt;
  &lt;p id=&quot;7UtV&quot;&gt;&lt;strong&gt;Результат:&lt;/strong&gt;&lt;/p&gt;
  &lt;figure id=&quot;fHiU&quot; class=&quot;m_original&quot;&gt;
    &lt;img src=&quot;https://img4.teletype.in/files/7d/c2/7dc2588a-0bac-4500-942f-1a26a4102fba.png&quot; width=&quot;481&quot; /&gt;
    &lt;figcaption&gt;Работа бота&lt;/figcaption&gt;
  &lt;/figure&gt;
  &lt;p id=&quot;uis5&quot;&gt;В статье мы рассмотрели эффективный способ получения текстовых версий голосовых сообщений в Telegram боте, используя API OpenAI. Использование OpenAI API значительно упрощает процесс транскрибации голосовых сообщений и делает его более точным и надежным. Всем удачи в изучении!&lt;/p&gt;

</content></entry><entry><id>loginovpavel:get-openai-token</id><link rel="alternate" type="text/html" href="https://teletype.in/@loginovpavel/get-openai-token?utm_source=teletype&amp;utm_medium=feed_atom&amp;utm_campaign=loginovpavel"></link><title>Получение доступа к OpenAI API</title><published>2023-03-20T18:59:18.585Z</published><updated>2023-07-19T18:06:16.366Z</updated><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://img1.teletype.in/files/04/35/04352375-13af-4681-b9cb-1c6e738a12f9.png"></media:thumbnail><category term="open-ai" label="Open AI"></category><summary type="html">&lt;img src=&quot;https://img1.teletype.in/files/cb/9c/cb9cefc3-dbbd-4e87-b13f-8ab06b0eadde.jpeg&quot;&gt;В статье изложена инструкция получения токена OpenAI API и обхода блокировки OpenAI сайта из России.</summary><content type="html">
  &lt;h3 id=&quot;PwiC&quot;&gt;&lt;strong&gt;Получение доступа к OpenAI API&lt;/strong&gt;&lt;/h3&gt;
  &lt;p id=&quot;pXgP&quot;&gt;В статье изложена инструкция получения токена OpenAI API и обхода блокировки OpenAI сайта из России.&lt;/p&gt;
  &lt;figure id=&quot;zetp&quot; class=&quot;m_column&quot;&gt;
    &lt;img src=&quot;https://img1.teletype.in/files/cb/9c/cb9cefc3-dbbd-4e87-b13f-8ab06b0eadde.jpeg&quot; width=&quot;800&quot; /&gt;
  &lt;/figure&gt;
  &lt;p id=&quot;uRTn&quot;&gt;&lt;strong&gt;OpenAI API&lt;/strong&gt; — это программный интерфейс приложения, который предоставляет доступ к технологиям искусственного интеллекта, разработанным и поддерживаемым исследовательской лабораторией OpenAI. API позволяет разработчикам интегрировать функциональность OpenAI в свои собственные приложения и использовать различные модели машинного обучения, такие как обработка естественного языка, генерация текста и т.д.&lt;/p&gt;
  &lt;p id=&quot;0Q78&quot;&gt;Чтобы использовать API, вам необходимо получить токен на сайте &lt;a href=&quot;https://platform.openai.com/&quot; target=&quot;_blank&quot;&gt;OpenAI&lt;/a&gt;. Для России сайт заблокирован. Для преодоления блокировки необходимо иметь VPN и заграничный телефонный номер, чтобы получить СМС для регистрации. В статье на Хабр подробно и точно описано как обойти блокировку при помощи “виртуального” номера телефона. У меня это заняло примерно 10 минут и 20 рублей. Ссылка на статью: &lt;a href=&quot;https://habr.com/ru/post/704600/&quot; target=&quot;_blank&quot;&gt;Как получить доступ к chatGPT в России&lt;/a&gt;.&lt;/p&gt;
  &lt;p id=&quot;70np&quot;&gt;Если вы получили доступ к платформе OpenAI и смогли зайти на страницу ChatGPT, тогда мы можем продолжать.&lt;/p&gt;
  &lt;p id=&quot;e99s&quot;&gt;Сперва перейдем на страницу входа в аккаунт OpenAI: &lt;a href=&quot;https://platform.openai.com/signup&quot; target=&quot;_blank&quot;&gt;https://platform.openai.com/signup&lt;/a&gt;&lt;/p&gt;
  &lt;p id=&quot;Jnqb&quot;&gt;После входа вас должно направить на страницу: &lt;a href=&quot;https://platform.openai.com/overview&quot; target=&quot;_blank&quot;&gt;https://platform.openai.com/overview&lt;/a&gt;&lt;/p&gt;
  &lt;p id=&quot;6JiV&quot;&gt;В меню находим &lt;strong&gt;View API keys&lt;/strong&gt; и переходим туда:&lt;/p&gt;
  &lt;figure id=&quot;I8AX&quot; class=&quot;m_column&quot;&gt;
    &lt;img src=&quot;https://img2.teletype.in/files/91/f9/91f983db-7a68-4d44-accf-075777d0c79e.png&quot; width=&quot;700&quot; /&gt;
    &lt;figcaption&gt;Раскрывающееся меню при нажатии на Personal&lt;/figcaption&gt;
  &lt;/figure&gt;
  &lt;p id=&quot;PoU9&quot;&gt;Тут у вас отображается список имеющихся токенов и есть кнопка создания нового &lt;strong&gt;Create new secret key&lt;/strong&gt;, нажимаем на неё:&lt;/p&gt;
  &lt;figure id=&quot;GPbB&quot; class=&quot;m_column&quot;&gt;
    &lt;img src=&quot;https://img3.teletype.in/files/2c/ce/2cce4df1-af40-4673-8d80-4fd827e73b4c.png&quot; width=&quot;700&quot; /&gt;
    &lt;figcaption&gt;Страница с API токенами&lt;/figcaption&gt;
  &lt;/figure&gt;
  &lt;p id=&quot;h3PY&quot;&gt;После нажатия на кнопку у вас сгенерируется новый токен. Вам покажут его только один раз, поэтому необходимо сразу же его сохранить в доступном для вас и надёжном месте.&lt;/p&gt;
  &lt;figure id=&quot;BmfC&quot; class=&quot;m_column&quot;&gt;
    &lt;img src=&quot;https://img3.teletype.in/files/e0/11/e01115fb-6aea-438d-b143-2bd45caa4e74.png&quot; width=&quot;601&quot; /&gt;
    &lt;figcaption&gt;Новый сгенерированный токен&lt;/figcaption&gt;
  &lt;/figure&gt;
  &lt;p id=&quot;3Rsy&quot;&gt;Поздравляю! Теперь у вас есть возможность использовать OpenAI API в своих приложениях. Исследуйте документацию и возможности этого инструмента, они обширны и интересны. Удачи в изучении!&lt;/p&gt;

</content></entry></feed>