<?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>David Dobryakov</title><subtitle>Немного преподаю, немного управляю</subtitle><author><name>David Dobryakov</name></author><id>https://teletype.in/atom/kantegory</id><link rel="self" type="application/atom+xml" href="https://teletype.in/atom/kantegory?offset=0"></link><link rel="alternate" type="text/html" href="https://blog.kantegory.me/?utm_source=teletype&amp;utm_medium=feed_atom&amp;utm_campaign=kantegory"></link><link rel="next" type="application/rss+xml" href="https://teletype.in/atom/kantegory?offset=10"></link><link rel="search" type="application/opensearchdescription+xml" title="Teletype" href="https://teletype.in/opensearch.xml"></link><updated>2026-05-09T09:34:29.852Z</updated><entry><id>kantegory:endpoint-optimization</id><link rel="alternate" type="text/html" href="https://blog.kantegory.me/endpoint-optimization?utm_source=teletype&amp;utm_medium=feed_atom&amp;utm_campaign=kantegory"></link><title>Оптимизация работы эндпоинта</title><published>2026-02-14T11:56:27.012Z</published><updated>2026-02-14T11:56:27.012Z</updated><category term="programming" label="Программирование"></category><summary type="html">Часто так бывает, что новые фичи заводятся итерационным путём и сначала в формате &quot;лишь бы оно работало&quot;. Со временем, когда руки для следующей итерации высвобождаются, появляется желание подобные фичи порефакторить.</summary><content type="html">
  &lt;p id=&quot;Uzvc&quot;&gt;Часто так бывает, что новые фичи заводятся итерационным путём и сначала в формате &amp;quot;лишь бы оно работало&amp;quot;. Со временем, когда руки для следующей итерации высвобождаются, появляется желание подобные фичи порефакторить.&lt;/p&gt;
  &lt;p id=&quot;qgW1&quot;&gt;Одну такую фичу я завёз на продакшн около полутора месяцев назад, а на этой неделе наконец-то дошли руки до оптимизации. &lt;/p&gt;
  &lt;p id=&quot;vtSq&quot;&gt;Проект ещё только находится на пути к выходу из MVP, но уже имеет несколько активных клиентов, данные которых прилетают к нам на сервера 24/7. &lt;/p&gt;
  &lt;p id=&quot;og9n&quot;&gt;&lt;strong&gt;Кратко о сути проблемы:&lt;/strong&gt;&lt;/p&gt;
  &lt;ul id=&quot;h1Sq&quot;&gt;
    &lt;li id=&quot;1jtf&quot;&gt;мне надо было динамически рассчитывать статус текущего состояния некой установки, этих установок может быть несколько;&lt;/li&gt;
    &lt;li id=&quot;4qQk&quot;&gt;в первой итерации статус рассчитывался через python и сохранялся в БД в момент совершения запроса на его получение.&lt;/li&gt;
  &lt;/ul&gt;
  &lt;p id=&quot;f99J&quot;&gt;&lt;strong&gt;Путь улучшения перфоманса выглядел так:&lt;/strong&gt;&lt;/p&gt;
  &lt;ul id=&quot;Sb9k&quot;&gt;
    &lt;li id=&quot;3KCG&quot;&gt;я отрефакторил всё вручную и ускорил работу метода с 30 секунд до 12 секунд;&lt;/li&gt;
    &lt;li id=&quot;TnTG&quot;&gt;я сходил в GPT, он отрицательно ускорил метод с 12 секунд до 20 секунд;&lt;/li&gt;
    &lt;li id=&quot;U6BO&quot;&gt;я начал делать фоновый предрасчёт через celery и сократил тем самым работу метода аж до 68мс;&lt;/li&gt;
    &lt;li id=&quot;Ss5I&quot;&gt;количество данных в БД увеличилось (за 3 дня насоздавалось свыше 40 тысяч записей со статусами) и работа эндпоинта замедлилась до 40 секунд;&lt;/li&gt;
    &lt;li id=&quot;iZW2&quot;&gt;я начал записывать результат предрасчёта в redis и вновь вернул время работы к значению в районе ~100мс.&lt;/li&gt;
  &lt;/ul&gt;
  &lt;p id=&quot;F1ni&quot;&gt;&lt;strong&gt;Как можно было бы решить это иначе?&lt;/strong&gt;&lt;/p&gt;
  &lt;p id=&quot;WEZC&quot;&gt;Вынести предрасчёт на уровень postgres (в другом месте этого же проекта уже есть view, которая динамически рассчитывает некоторые показатели), но поскольку накручивать триггеры в БД я не очень хочу, а сохранять новые статусы для истории и обновлённой инфографики может быть важно, было принято именно то решение, о котором я написал выше.&lt;/p&gt;
  &lt;p id=&quot;XFAW&quot;&gt;&lt;strong&gt;Какой вывод я для себя сделал?&lt;/strong&gt;&lt;/p&gt;
  &lt;p id=&quot;JHgk&quot;&gt;Задним умом я понимаю, что не просчитал основной риск: количество записей может стать довольно большим за короткий срок и это отрицательно скажется на общей производительности эндпоинта.&lt;/p&gt;

</content></entry><entry><id>kantegory:express-autodoc</id><link rel="alternate" type="text/html" href="https://blog.kantegory.me/express-autodoc?utm_source=teletype&amp;utm_medium=feed_atom&amp;utm_campaign=kantegory"></link><title>Настраиваем автодокументирование для express-приложений</title><published>2025-05-24T07:11:36.006Z</published><updated>2025-05-24T07:11:36.006Z</updated><category term="programming" label="Программирование"></category><summary type="html">Долгое время в express было принято использовать решения, которые довольно большую часть работы перекладывают на разработчика. Одним из самых популярных решений и по сей день является swagger-jsdoc.</summary><content type="html">
  &lt;h2 id=&quot;zvov&quot;&gt;Вместо предисловия&lt;/h2&gt;
  &lt;p id=&quot;1phR&quot;&gt;Долгое время в express было принято использовать решения, которые довольно большую часть работы перекладывают на разработчика. Одним из самых популярных решений и по сей день является &lt;a href=&quot;https://www.npmjs.com/package/swagger-jsdoc&quot; target=&quot;_blank&quot;&gt;swagger-jsdoc&lt;/a&gt;.&lt;/p&gt;
  &lt;p id=&quot;OiKm&quot;&gt;Поскольку, оно является и самым проверенным, я продолжал рассказывать о нём студентам из года в год. Но в этом году что-то пошло не так... Один из студентов спросил: &amp;quot;а нет ли чего-то такого же удобного, как в Nest.JS?&amp;quot;. Тут-то всё и началось.&lt;/p&gt;
  &lt;p id=&quot;8TZq&quot;&gt;Я прочитал более десятка статей, пересмотрел несколько роликов в поисках оптимального решения. И вот мы здесь. Я выделил 3 решения для настройки автодокументации и подготовил сравнения и примеры для них.&lt;/p&gt;
  &lt;h2 id=&quot;ji23&quot;&gt;Старый-добрый swagger-jsdoc&lt;/h2&gt;
  &lt;p id=&quot;FlQK&quot;&gt;Крайне простое в использовании решение, которое требует от вас только терпения и времени. По сути, вы описываете все эндпоинты через специальные jsdoc-комментарии, по формату совпадающие с OpenAPI в YAML.&lt;/p&gt;
  &lt;p id=&quot;Grrf&quot;&gt;Библиотека же ходит по вашему коду, собирает все такие комментарии и встраивает их в общий файлик OpenAPI.&lt;/p&gt;
  &lt;p id=&quot;jUxi&quot;&gt;Пример кода:&lt;/p&gt;
  &lt;pre id=&quot;XgfF&quot; data-lang=&quot;typescript&quot;&gt;/**
 * @openapi
 * /v1/testCreate:
 *   post:
 *     produces:
 *       - application/json
 *     parameters:
 *       - name: username
 *         in: formData
 *         required: true
 *         type: string
 *       - name: password
 *         in: formData
 *         required: true
 *         type: string
 *     description: Test create
 *     responses:
 *       200:
 *         description: Returns a created object.
 */
router
  .route(&amp;#x27;/testCreate&amp;#x27;)
  .post(exampleController.post)&lt;/pre&gt;
  &lt;p id=&quot;Vbnm&quot;&gt;Плюсы у такого решения вполне очевидны, оно достаточно простое и универсальное. Подходит, даже если вы не используете typescript на бэкенде (&lt;em&gt;лучше всё-таки использовать...&lt;/em&gt;). Минусы, думаю, вы и сами понимаете.&lt;/p&gt;
  &lt;h3 id=&quot;fR6z&quot;&gt;Плюсы и минусы&lt;/h3&gt;
  &lt;p id=&quot;fog6&quot;&gt;Плюсы:&lt;/p&gt;
  &lt;ul id=&quot;JQ5E&quot;&gt;
    &lt;li id=&quot;DKNq&quot;&gt;простота&lt;/li&gt;
    &lt;li id=&quot;Qx9i&quot;&gt;универсальность&lt;/li&gt;
  &lt;/ul&gt;
  &lt;p id=&quot;H0Pe&quot;&gt;Минусы:&lt;/p&gt;
  &lt;ul id=&quot;tbx2&quot;&gt;
    &lt;li id=&quot;yWpy&quot;&gt;много ручной работы&lt;/li&gt;
  &lt;/ul&gt;
  &lt;h3 id=&quot;TkSH&quot;&gt;Полный код примера&lt;/h3&gt;
  &lt;p id=&quot;kM4k&quot;&gt;Полный код примера доступен на &lt;a href=&quot;https://github.com/kantegory/mentoring/tree/master/16_express_swagger_example&quot; target=&quot;_blank&quot;&gt;github&lt;/a&gt;.&lt;/p&gt;
  &lt;h2 id=&quot;IGwH&quot;&gt;Пишем как на Nest: routing-controllers&lt;/h2&gt;
  &lt;p id=&quot;s8gS&quot;&gt;Если вы начинаете с express, но планируете перейти на Nest позднее, то routing-controllers - ваш выбор. Синтаксически это почти один-в-один совпадает с Nest.&lt;/p&gt;
  &lt;p id=&quot;XeAs&quot;&gt;Пример кода:&lt;/p&gt;
  &lt;pre id=&quot;IH2r&quot; data-lang=&quot;typescript&quot;&gt;@JsonController()
export class ExampleController {
    @OpenAPI({ summary: &amp;#x27;Test create&amp;#x27; })
    @ResponseSchema(TestCreateResponseDto, { statusCode: 200 })
    @Post(&amp;#x27;/testCreate&amp;#x27;)
    post(
        @Body({ type: TestCreateDto }) body: TestCreateDto,
        @Res() response: Response,
    ): void {
        const uuid: string = randomUUID();

        const responseBody = {
            uuid,
            ...body,
        };

        response.status(201).send(responseBody);
    }
}
&lt;/pre&gt;
  &lt;p id=&quot;1v6w&quot;&gt;Как видите, от привычного express тут мало что осталось, но контроллеры становятся куда более структурированными за счёт аннотаций.&lt;/p&gt;
  &lt;p id=&quot;dCOF&quot;&gt;Типы для документации собираются на основе &lt;a href=&quot;https://www.npmjs.com/package/class-validator&quot; target=&quot;_blank&quot;&gt;class-validator&lt;/a&gt;, как и в Nest. Что фактические вынуждает вас к валидации входящих данных.&lt;/p&gt;
  &lt;p id=&quot;5yZv&quot;&gt;Стоит отметить, что само по себе, решение не предоставляет возможностей для генерации документации, это делается за счёт использования библиотеки &lt;a href=&quot;https://www.npmjs.com/package/routing-controllers-openapi&quot; target=&quot;_blank&quot;&gt;routing-controllers-openapi&lt;/a&gt;. У неё есть проблема, из-за которой могут быть проблемы с работой рантайма &lt;a href=&quot;https://www.npmjs.com/package/tsx&quot; target=&quot;_blank&quot;&gt;tsx&lt;/a&gt;. Не могу сказать, что я как-то сильно погружался, но по моим ощущениям, это связано с некорректной обработкой данных из метадаты. К счастью, это исправляется либо за счёт monkey-патча, либо за счёт использования &lt;a href=&quot;https://github.com/kantegory/routing-controllers-openapi&quot; target=&quot;_blank&quot;&gt;моего форка&lt;/a&gt;.&lt;/p&gt;
  &lt;p id=&quot;5BeE&quot;&gt;У меня есть даже свой &lt;a href=&quot;https://github.com/kantegory/express-typeorm-boilerplate/&quot; target=&quot;_blank&quot;&gt;бойлерплейт&lt;/a&gt; на основе routing-controllers. Правда, он не до конца написан, но начало положено...&lt;/p&gt;
  &lt;h3 id=&quot;aGLK&quot;&gt;Плюсы и минусы&lt;/h3&gt;
  &lt;p id=&quot;N76R&quot;&gt;Плюсы:&lt;/p&gt;
  &lt;ul id=&quot;K22p&quot;&gt;
    &lt;li id=&quot;OaKs&quot;&gt;код более структурирован&lt;/li&gt;
    &lt;li id=&quot;k4Yi&quot;&gt;вынужденное использование валидаторов&lt;/li&gt;
    &lt;li id=&quot;SBUI&quot;&gt;синтаксически близко к Nest&lt;/li&gt;
  &lt;/ul&gt;
  &lt;p id=&quot;WRBJ&quot;&gt;Минусы:&lt;/p&gt;
  &lt;ul id=&quot;Fjih&quot;&gt;
    &lt;li id=&quot;h9wJ&quot;&gt;проблема с рантаймом tsx&lt;/li&gt;
    &lt;li id=&quot;mcVU&quot;&gt;остаётся ли express всё ещё таким же лёгким?&lt;/li&gt;
  &lt;/ul&gt;
  &lt;h3 id=&quot;3HlM&quot;&gt;Полный код примера&lt;/h3&gt;
  &lt;p id=&quot;YSk9&quot;&gt;Полный код примера доступен на &lt;a href=&quot;https://github.com/kantegory/mentoring/tree/master/29_express_swagger_routing_controllers_example&quot; target=&quot;_blank&quot;&gt;github&lt;/a&gt;.&lt;/p&gt;
  &lt;h2 id=&quot;JC35&quot;&gt;Ещё немножко декораторов: tsoa&lt;/h2&gt;
  &lt;p id=&quot;6KUD&quot;&gt;Да, снова декораторы. Но &lt;a href=&quot;https://www.npmjs.com/package/tsoa&quot; target=&quot;_blank&quot;&gt;tsoa&lt;/a&gt; - решение куда более комплексное, чем routing-controllers. По крайней мере, на мой взгляд.&lt;/p&gt;
  &lt;p id=&quot;8hCI&quot;&gt;Давайте посмотрим на пример:&lt;/p&gt;
  &lt;pre id=&quot;aZ95&quot; data-lang=&quot;typescript&quot;&gt;@Route()
@Tags(&amp;#x27;Example&amp;#x27;)
export class ExampleController extends Controller {
    @Post(&amp;#x27;/testCreate&amp;#x27;)
    @Response&amp;lt;TestCreateResponseDto&amp;gt;(201, &amp;#x27;Returns a created object.&amp;#x27;)
    public async post(
        @Body() body: TestCreateDto,
    ): Promise&amp;lt;TestCreateResponseDto&amp;gt; {
        const uuid: string = randomUUID();

        return {
            uuid,
            ...body,
        };
    }
}
&lt;/pre&gt;
  &lt;p id=&quot;v8KD&quot;&gt;Это решение предоставляет минимально-необходимый набор инструментов для реализации автодокументируемого кода.&lt;/p&gt;
  &lt;p id=&quot;QRVq&quot;&gt;Для составления схем используются интерфейсы, валидация в них осуществляется за счёт добавления специальных комментариев. Код всё также хорошо структурирован, как и в routing-controllers. Никаких проблем с рантаймами тайпскрипта тут нет.&lt;/p&gt;
  &lt;p id=&quot;QFFr&quot;&gt;Для меня tsoa - самое оптимальное решение. Буду во все новые проекты на Node брать именно эту библиотеку.&lt;/p&gt;
  &lt;h3 id=&quot;2RqP&quot;&gt;Плюсы и минусы&lt;/h3&gt;
  &lt;p id=&quot;3sZK&quot;&gt;Плюсы:&lt;/p&gt;
  &lt;ul id=&quot;DiuL&quot;&gt;
    &lt;li id=&quot;gcPj&quot;&gt;код более структурирован&lt;/li&gt;
    &lt;li id=&quot;dUgH&quot;&gt;встроенная валидация&lt;/li&gt;
    &lt;li id=&quot;cOId&quot;&gt;единое решение для контроллеров и автодокументирования&lt;/li&gt;
    &lt;li id=&quot;ezTr&quot;&gt;синтаксически близко к Nest&lt;/li&gt;
  &lt;/ul&gt;
  &lt;p id=&quot;6hYr&quot;&gt;Минусы:&lt;/p&gt;
  &lt;ul id=&quot;f86G&quot;&gt;
    &lt;li id=&quot;vttL&quot;&gt;остаётся ли express всё ещё таким лёгким?&lt;/li&gt;
  &lt;/ul&gt;
  &lt;h3 id=&quot;VNHS&quot;&gt;Полный код примера&lt;/h3&gt;
  &lt;p id=&quot;8koG&quot;&gt;Полный код примера доступен на &lt;a href=&quot;https://github.com/kantegory/mentoring/tree/master/30_express_swagger_tsoa_example&quot; target=&quot;_blank&quot;&gt;github&lt;/a&gt;.&lt;/p&gt;
  &lt;h2 id=&quot;sY4k&quot;&gt;Выводы&lt;/h2&gt;
  &lt;p id=&quot;QDRy&quot;&gt;Хотите посмотреть на сухое сравнение, тогда вам &lt;a href=&quot;https://npm-compare.com/routing-controllers,swagger-jsdoc,tsoa&quot; target=&quot;_blank&quot;&gt;сюда&lt;/a&gt;. А по моим личным ощущениям, tsoa - лучший выбор.&lt;/p&gt;

</content></entry><entry><id>kantegory:it-education</id><link rel="alternate" type="text/html" href="https://blog.kantegory.me/it-education?utm_source=teletype&amp;utm_medium=feed_atom&amp;utm_campaign=kantegory"></link><title>Переход с Python на Go и мысли о высшем образовании</title><published>2025-05-10T18:25:05.275Z</published><updated>2025-05-10T20:12:22.910Z</updated><category term="programming" label="Программирование"></category><summary type="html">Уже около года в моих черновиках для постов гуляет заметка с названием &quot;для чего нужен вуз?&quot;. Разумеется, с точки зрения IT. До сегодняшнего дня, там была только одна мысль, которую я подметил, когда писал в дневник...</summary><content type="html">
  &lt;h2 id=&quot;7cpg&quot;&gt;Предисловие&lt;/h2&gt;
  &lt;p id=&quot;Vs3M&quot;&gt;Уже около года в моих черновиках для постов гуляет заметка с названием &amp;quot;для чего нужен вуз?&amp;quot;. Разумеется, с точки зрения IT. До сегодняшнего дня, там была только одна мысль, которую я подметил, когда писал в дневник:&lt;/p&gt;
  &lt;p id=&quot;gLJ7&quot;&gt;Осознал, что вуз нужен в том числе для того, чтобы научиться писать отчёты по стандартам, да и в целом делать что-либо с учётом принятых стандартов.&lt;/p&gt;
  &lt;p id=&quot;zHWB&quot;&gt;Сегодня я гулял по хабру и наткнулся на две похожих статьи об одном и том же, но от разных людей:&amp;quot;&lt;a href=&quot;https://habr.com/ru/articles/907360/&quot; target=&quot;_blank&quot;&gt;Переход с Python на Go: мысли человека, которому иногда сложно&lt;/a&gt;&amp;quot; и &amp;quot;&lt;a href=&quot;https://habr.com/ru/companies/ru_mts/articles/902244/&quot; target=&quot;_blank&quot;&gt;Как я начал учить Go и правда ли он похож на Python. Мой личный опыт&lt;/a&gt;&amp;quot;.&lt;/p&gt;
  &lt;p id=&quot;1vvw&quot;&gt;Я прочитал их обе и некоторые вещи из первой статьи вернули меня к размышлениям о том, для чего же всё-таки нужно высшее образование.&lt;/p&gt;
  &lt;h2 id=&quot;OR73&quot;&gt;Мысли по ходу прочтения первой статьи&lt;/h2&gt;
  &lt;p id=&quot;i6kY&quot;&gt;Заранее оговорюсь, что с golang у меня почти нет реального опыта (один пет-проект, который я писал по видео на youtube и несколько дней с документацией), а на python я пишу уже более десяти лет, но все мысли автора, приведённые далее, исходят корнями к общему отсутствию базовых знаний, а не к знаниям в конкретных языках программирования.&lt;/p&gt;
  &lt;p id=&quot;Bjn6&quot;&gt;Автор пишет:&lt;/p&gt;
  &lt;blockquote id=&quot;c0Ls&quot;&gt;Сначала я подумал, что это какая-то обёртка, типа как &lt;code&gt;if __name__ == &amp;quot;__main__&amp;quot;&lt;/code&gt; в Python, но, похоже, нет. Тут без &lt;code&gt;main&lt;/code&gt; вообще ничего не запускается. Это немного сбивает. Почему язык не может просто начать выполнять файл с первой строки?&lt;/blockquote&gt;
  &lt;p id=&quot;6yOV&quot;&gt;Моя мысль: python интерпретируемый, а go - компилируемый язык. Потому он и не выполняется с первой строки. Это, если угодно, жанровая необходимость.&lt;/p&gt;
  &lt;p id=&quot;jZor&quot;&gt;Интересно, что он при этом в самом начале упоминает, что заканчивал онлайн-школу. Это как раз и объясняет, видимо, весь последующий текст. Я ничего не имею против онлайн-школ, раскрою эту мысль чуть ниже.&lt;/p&gt;
  &lt;p id=&quot;H8hv&quot;&gt;Удивление &amp;quot;странным типам&amp;quot; тоже ни к селу, ни к городу. Очевидно, что в python типизация неявная и динамическая, в то время как в golang она явная и статическая:&lt;/p&gt;
  &lt;blockquote id=&quot;Dakw&quot;&gt;Я привык, что переменные сами понимают, кто они. В Python пишешь &lt;code&gt;a = 5&lt;/code&gt;, потом &lt;code&gt;a = &amp;quot;текст&amp;quot;&lt;/code&gt; — и всё нормально.&lt;br /&gt;В Go, если ты сказал, что &lt;code&gt;a int&lt;/code&gt;, то всё — никаких &amp;quot;текстов&amp;quot; туда не положишь. Я не сразу понял, как это работает. Ещё есть &lt;code&gt;:=&lt;/code&gt;, и я не сразу понял, чем &lt;code&gt;:=&lt;/code&gt; отличается от &lt;code&gt;=&lt;/code&gt;. Оказалось, &lt;code&gt;:=&lt;/code&gt; — это когда переменную создаёшь. А &lt;code&gt;=&lt;/code&gt; — когда она уже есть. Почему нельзя просто одно оставить?&lt;/blockquote&gt;
  &lt;p id=&quot;MMTa&quot;&gt;В такие моменты, я отчётливо понимаю, почему некоторые разработчики относятся к питонистам как-то пренебрежительно. Мне вполне ясно, чем вызвано удивление автора, но я не вполне понимаю для чего вообще было писать такой пост.&lt;/p&gt;
  &lt;p id=&quot;z9XI&quot;&gt;Блистательное утверждение, орфография автора сохранена:&lt;/p&gt;
  &lt;blockquote id=&quot;HrHp&quot;&gt;Разницу между слайсами и массивами и прочие тонкости реализации очень любят спрашивать на собеседованиях, рассчитывая отсеить людей без высшего образования.&lt;/blockquote&gt;
  &lt;p id=&quot;WGQo&quot;&gt;Скажу, как человек, который прособеседовал более сотни кандидатов: отсутствие или наличие высшего образования не отражает реальных знаний. Но люди с высшим образованием мыслят несколько иначе. Это проверено на опыте и не единожды.&lt;/p&gt;
  &lt;p id=&quot;wPaR&quot;&gt;Итог этого поста вполне очевиден и закономерен, - его заминусовали на хабре. В комментариях пишут:&lt;/p&gt;
  &lt;blockquote id=&quot;IxVi&quot;&gt;Но больше всего мне непонятен посыл статьи.&lt;/blockquote&gt;
  &lt;blockquote id=&quot;pJOk&quot;&gt;Судя по приведенным примерам, вам и в питоне пока многое не понятно.&lt;/blockquote&gt;
  &lt;blockquote id=&quot;BEME&quot;&gt;Ничего против вкатунов не имею, но 3 года работать и не знать разницы между статической и динамической типизацией - это сильно.&lt;/blockquote&gt;
  &lt;blockquote id=&quot;V2Iv&quot;&gt;может скажем ему?&lt;/blockquote&gt;
  &lt;h2 id=&quot;vGzD&quot;&gt;Мысли о второй статье&lt;/h2&gt;
  &lt;p id=&quot;z33b&quot;&gt;Очевидно, что автор имеет опыт работы и понимает то, о чём он пишет. В некоторых моментах он даже довольно глубоко копнул, когда писал про запуск golang-кода внутри python (в виде собранной библиотеки, конечно).&lt;/p&gt;
  &lt;p id=&quot;zpd0&quot;&gt;Отсюда я узнал довольно много нового для себя, а некоторые знания по go освежил. Рекомендую к прочтению всем интересующимся.&lt;/p&gt;
  &lt;p id=&quot;2rzm&quot;&gt;Можно ли при этом однозначно сказать, что у этого автора есть высшее образование? Нет. Однозначно сказать нельзя. Можно ли сказать, что у автора есть какая-то база в программировании? Да, она есть. И это куда важнее, на мой взгляд.&lt;/p&gt;
  &lt;h2 id=&quot;jnWU&quot;&gt;Мысли о высшем образовании в IT&lt;/h2&gt;
  &lt;p id=&quot;n7Ly&quot;&gt;Возвращаясь к моим мыслям о первой статье:&lt;/p&gt;
  &lt;blockquote id=&quot;jA48&quot;&gt;Скажу, как человек, который прособеседовал более сотни кандидатов: отсутствие или наличие высшего образование не отражает реальных знаний. Но люди с высшим образованием мыслят несколько иначе. Это проверено на опыте и не единожды.&lt;/blockquote&gt;
  &lt;p id=&quot;cuHz&quot;&gt;Является ли при этом высшее образование панацеей? Нет, не является.&lt;/p&gt;
  &lt;p id=&quot;KZQt&quot;&gt;Главное отличие в мышлении человека, получавшего высшее образование - умение систематизировать знания и пользоваться ими.&lt;/p&gt;
  &lt;p id=&quot;kOdl&quot;&gt;Если же мы говорим о высшем образовании в IT, то именно благодаря высшему образованию, формируется та самая &amp;quot;база&amp;quot;, к которой довольно часто относят и знания в Computer Science.&lt;/p&gt;
  &lt;p id=&quot;k8Eo&quot;&gt;Под этой &amp;quot;базой&amp;quot; я понимаю не только Computer Science, но и наличие прочих разнообразных навыков. Во время своего обучения, мне довелось как алгоритмы писать на разных языках, так и сети самостоятельно настраивать, сервера конфигурировать, даже рисовать картинки через python.&lt;/p&gt;
  &lt;p id=&quot;uCuG&quot;&gt;Благодаря вузу, я имею понимание и практические навыки в совершенно разных сферах. Благодаря своему рабочему опыту, я могу сказать, что со временем буквально всё, что мы рассматривали в вузе относительно IT, пригодилось мне в той или иной мере. Очень часто это снижало порог входа в новый проект, а умение систематизировать входящую информацию и формировать различные документы по своим работам, которые студенты так сильно не любят, открыли мне в своё время путь к руководящим позициям.&lt;/p&gt;
  &lt;h2 id=&quot;eyfV&quot;&gt;Мысли об онлайн-школах и преподавании&lt;/h2&gt;
  &lt;p id=&quot;Tq1N&quot;&gt;В то же время, как альтернатива 4-6 годам в вузе, на рынке уже давно существует предложение пройти курсы на 3-18 месяцев и стать готовым специалистом. Некоторые даже говорят, что сделают вас middle-разработчиком (это просто маркетинг, не ведитесь).&lt;/p&gt;
  &lt;p id=&quot;rqNG&quot;&gt;Закономерным итогом обучения в онлайн-школах является неадекватная накрутка опыта в резюме и полное отсутствие фундамента для становления специалистом. Пишу это на примере моей текущей ученицы, с которой мы уже более двух месяцев занимаемся. Я не спрашивал, сколько стоило её обучение, но итоговый результат отталкивает. Да, что-то в её голове действительно отложилось, но крепкого фундамента по фронтенду не было сформировано. Мы более двух месяцев мусолим ванильный JS, HTML, CSS, чтобы освоить общие концепции и не запираться в одном react. Иметь гибкость. Уметь ориентироваться.&lt;/p&gt;
  &lt;p id=&quot;eGTz&quot;&gt;В среднем, на выходе из онлайн-школы, мы имеем человека, который не умеет подстраиваться и адаптироваться под возникающие условия. Конечно, всё индивидуально. Почти наверняка есть люди, которые прошли курсы, но при этом самостоятельно получали и дополнительную информацию из различных источников. Их опыт может отличаться. Да и в вузах преподаватели далеко не всегда мастера своего дела. Мы тоже можем ошибаться. Более того, мы регулярно это делаем. Но ошибаться - это не страшно. Главное, чтобы студенты &lt;s&gt;ничего не поняли&lt;/s&gt; в итоге получили корректную информацию.&lt;/p&gt;
  &lt;p id=&quot;SplX&quot;&gt;В преподавании для меня самым показательным и приятным моментом является то, что есть несколько человек, которые не хотели связывать свою жизнь с IT и получали высшее образование, что называется &amp;quot;для родителей&amp;quot;, но после моих предметов заинтересовались. Почти все из них уже работают. Это для меня лучший показатель того, что я делаю это не зря.&lt;/p&gt;
  &lt;p id=&quot;MCRR&quot;&gt;Но вернёмся к критике онлайн-школ и их общей проблеме. Подведём итог.&lt;/p&gt;
  &lt;h2 id=&quot;mkzw&quot;&gt;Выводы&lt;/h2&gt;
  &lt;p id=&quot;ChiU&quot;&gt;Онлайн-школы не помогают вам построить крепкий и надёжный фундамент, они заточены на извлечение прибыли, ваша дальнейшая судьба - им безразлична. Они учат вас решать конкретные задачи (с которыми вообще-то неплохо справляется тот же GPT) конкретным образом, вместо того, чтобы объяснить общие принципы.&lt;/p&gt;
  &lt;p id=&quot;czRW&quot;&gt;Высшее образование даёт вам много знаний из разных областей в разрезе выбранной специальности. Даёт свободу выбирать, чем из этого вы в итоге будете заниматься. Помогает вам выстроить систему. Даёт гибкость и умение адаптироваться.&lt;/p&gt;

</content></entry><entry><id>kantegory:podborka-1</id><link rel="alternate" type="text/html" href="https://blog.kantegory.me/podborka-1?utm_source=teletype&amp;utm_medium=feed_atom&amp;utm_campaign=kantegory"></link><title>Сервис подборок с использованием NoORM / #1</title><published>2024-07-06T15:56:37.506Z</published><updated>2024-07-06T16:23:17.833Z</updated><category term="programming" label="Программирование"></category><summary type="html">&lt;img src=&quot;https://img3.teletype.in/files/e4/37/e4378744-d6b4-446c-9714-52b4fc15580d.png&quot;&gt;На прошлой неделе я писал про подход &quot;не только ORM&quot;, в конце которого анонсировал публикацию нескольких постов по теме пет-проекта с использованием python-библиотеки true-noorm. Это первая часть, сегодня поговорим об организационных моментах, спроектируем сервис, поищем оптимальные решения, помучаем LLM и даже частично набросаем MVP.</summary><content type="html">
  &lt;h2 id=&quot;j95S&quot;&gt;Содержание&lt;/h2&gt;
  &lt;ul id=&quot;NEpi&quot;&gt;
    &lt;li id=&quot;dd0s&quot;&gt;&lt;a href=&quot;#EgY7&quot;&gt;Введение&lt;/a&gt;&lt;/li&gt;
    &lt;li id=&quot;eN7R&quot;&gt;&lt;a href=&quot;#ZWQt&quot;&gt;Выбор технологий&lt;/a&gt;&lt;/li&gt;
    &lt;li id=&quot;zpBW&quot;&gt;&lt;a href=&quot;#gG4q&quot;&gt;Проектирование БД&lt;/a&gt;&lt;/li&gt;
    &lt;li id=&quot;lM5P&quot;&gt;&lt;a href=&quot;#57Xy&quot;&gt;Парсинг хабра&lt;/a&gt;&lt;/li&gt;
    &lt;li id=&quot;PC9Y&quot;&gt;&lt;a href=&quot;#L76i&quot;&gt;Создание БД&lt;/a&gt;&lt;/li&gt;
    &lt;li id=&quot;avFv&quot;&gt;&lt;a href=&quot;#vwiT&quot;&gt;NoORM&lt;/a&gt;&lt;/li&gt;
    &lt;li id=&quot;2hkz&quot;&gt;&lt;a href=&quot;#JkS2&quot;&gt;Точка входа&lt;/a&gt;&lt;/li&gt;
    &lt;li id=&quot;Tgma&quot;&gt;&lt;a href=&quot;#rU55&quot;&gt;Заключение&lt;/a&gt;&lt;/li&gt;
  &lt;/ul&gt;
  &lt;h2 id=&quot;EgY7&quot;&gt;Введение&lt;/h2&gt;
  &lt;p id=&quot;DY3I&quot;&gt;На прошлой неделе я &lt;a href=&quot;https://t.me/davidobryakov/1219&quot; target=&quot;_blank&quot;&gt;писал&lt;/a&gt; про подход &amp;quot;не только ORM&amp;quot;, в конце которого анонсировал публикацию нескольких постов по теме пет-проекта с использованием python-библиотеки &lt;code&gt;true-noorm&lt;/code&gt;. Это первая часть, сегодня поговорим об организационных моментах, спроектируем сервис, поищем оптимальные решения, помучаем LLM и даже частично набросаем MVP.&lt;/p&gt;
  &lt;p id=&quot;7k76&quot;&gt;В качестве проекта, на котором я хочу использовать NoORM был выбран сервис для формирования подборок из постов по интересующим темам.&lt;/p&gt;
  &lt;p id=&quot;gHVY&quot;&gt;Для начала хочется применить NoORM на этапе создания MVP. Критерии для того, чтобы можно было считать, что MVP реализовано:&lt;/p&gt;
  &lt;ul id=&quot;OGCJ&quot;&gt;
    &lt;li id=&quot;UpBr&quot;&gt;ежедневно собираются посты с хабра;&lt;/li&gt;
    &lt;li id=&quot;MMKc&quot;&gt;из всех собранных постов формируется подборка из 10-20 публикаций;&lt;/li&gt;
    &lt;li id=&quot;3n5a&quot;&gt;можно оценить каждый пост лайком или дизлайком;&lt;/li&gt;
    &lt;li id=&quot;MZf3&quot;&gt;полный контент постов сохраняется в базе (иногда бывает, что посты удаляются с хабра, а информация, которая приводится в них может быть довольно полезной).&lt;/li&gt;
  &lt;/ul&gt;
  &lt;p id=&quot;A4Dm&quot;&gt;Общая архитектура для MVP (с учётом возможностей расширения):&lt;/p&gt;
  &lt;figure id=&quot;OnKK&quot; class=&quot;m_column&quot;&gt;
    &lt;img src=&quot;https://img3.teletype.in/files/e4/37/e4378744-d6b4-446c-9714-52b4fc15580d.png&quot; width=&quot;2012&quot; /&gt;
  &lt;/figure&gt;
  &lt;p id=&quot;JWUP&quot;&gt;По мере развития сервиса, хотелось бы реализовать следующие пункты:&lt;/p&gt;
  &lt;ul id=&quot;5C3f&quot;&gt;
    &lt;li id=&quot;DLQY&quot;&gt;возможность взаимодействовать с постами и подборками через веб-интерфейс;&lt;/li&gt;
    &lt;li id=&quot;mvm8&quot;&gt;добавить простую систему рекомендаций, которая на основе лайков/дизлайков будет ранжировать собираемые посты для подборки;&lt;/li&gt;
    &lt;li id=&quot;MKjW&quot;&gt;возможность добавления новых источников для парсинга (телеграм-каналы, веб-сайты и прочее);&lt;/li&gt;
    &lt;li id=&quot;mYwI&quot;&gt;возможность формирования нескольких подборок по разным темам (категоризация).&lt;/li&gt;
  &lt;/ul&gt;
  &lt;h2 id=&quot;ZWQt&quot;&gt;Выбор технологий&lt;/h2&gt;
  &lt;p id=&quot;GpH4&quot;&gt;Если по основным технологиям всё было понятно, то вот что делать с RSS — мыслей не было никаких, я никогда ранее не работал с RSS самостоятельно, пара минут в гугле &lt;a href=&quot;https://www.pykit.org/streamline-your-content-tracking-with-pythons-rss-parsing/&quot; target=&quot;_blank&quot;&gt;привели меня к библиотеке feedparser&lt;/a&gt;.&lt;/p&gt;
  &lt;p id=&quot;mC4e&quot;&gt;В качестве технологий я хочу использовать:&lt;/p&gt;
  &lt;ul id=&quot;GPrH&quot;&gt;
    &lt;li id=&quot;Cq6J&quot;&gt;FastAPI,&lt;/li&gt;
    &lt;li id=&quot;SOOM&quot;&gt;NoORM,&lt;/li&gt;
    &lt;li id=&quot;XXyY&quot;&gt;feedparser,&lt;/li&gt;
    &lt;li id=&quot;3FsE&quot;&gt;vue.js + antd + pinia (для визуализации подборки),&lt;/li&gt;
    &lt;li id=&quot;Ev2g&quot;&gt;postgresql,&lt;/li&gt;
    &lt;li id=&quot;5bKi&quot;&gt;docker,&lt;/li&gt;
    &lt;li id=&quot;oemd&quot;&gt;nginx.&lt;/li&gt;
  &lt;/ul&gt;
  &lt;p id=&quot;5BQS&quot;&gt;На клиентскую часть решил взять vue + antd, чтобы попрактиковаться в использовании antd в рамках vue (ранее пробовал его только с react).&lt;/p&gt;
  &lt;h2 id=&quot;gG4q&quot;&gt;Проектирование БД&lt;/h2&gt;
  &lt;p id=&quot;CMH4&quot;&gt;Для рисования диаграмм БД я использую drawio. Я не преследую целей соблюдать нотацию на все сто процентов, мне нужна только наглядность и отношение сущностей между собой.&lt;/p&gt;
  &lt;figure id=&quot;3f3i&quot; class=&quot;m_original&quot;&gt;
    &lt;img src=&quot;https://img2.teletype.in/files/98/b0/98b056df-e048-4bc7-897b-a89b1b2837e0.png&quot; width=&quot;688&quot; /&gt;
  &lt;/figure&gt;
  &lt;p id=&quot;ANb5&quot;&gt;Постарался оставить минимум полей, только действительно необходимые. Таблицы названы семантически, чтобы было сразу понятно, для чего именно они будут использоваться.&lt;/p&gt;
  &lt;h2 id=&quot;57Xy&quot;&gt;Парсинг хабра&lt;/h2&gt;
  &lt;p id=&quot;8Rcr&quot;&gt;В целом, на самом хабре &lt;a href=&quot;https://habr.com/ru/docs/help/lenta/&quot; target=&quot;_blank&quot;&gt;прекрасно описано&lt;/a&gt;, что надо сделать, чтобы получить доступ к RSS. Из всех возможных RSS-лент на хабре, лично мне пока хватит двух лент:&lt;/p&gt;
  &lt;ul id=&quot;Tist&quot;&gt;
    &lt;li id=&quot;gaqg&quot;&gt;лучшие публикации сайта;&lt;/li&gt;
    &lt;li id=&quot;jHry&quot;&gt;все публикации сайта подряд.&lt;/li&gt;
  &lt;/ul&gt;
  &lt;p id=&quot;gI3q&quot;&gt;В итоге за примерно 30-35 минут накидал следующий файлик (&lt;code&gt;rss.py&lt;/code&gt;) с кодом для парсинга RSS с хабра:&lt;/p&gt;
  &lt;pre id=&quot;mRv3&quot; data-lang=&quot;python&quot;&gt;import feedparser
from dataclasses import dataclass

habr_all_publications = &amp;#x27;https://habr.com/ru/rss/articles/?fl=ru&amp;#x27;
habr_best_publications = &amp;#x27;https://habr.com/ru/rss/articles/top/daily/?fl=ru&amp;#x27;


@dataclass
class Post:
    title: str
    description: str
    meta_info: dict[str, any]


def parse(link):
    feed = feedparser.parse(link)

    posts = []

    for post in feed.entries:
        posts.append(
            Post(
                title=post.title,
                description=post.summary,
                meta_info={
                    &amp;#x27;published_at&amp;#x27;: post.published,
                    &amp;#x27;author&amp;#x27;: post.author,
                    &amp;#x27;tags&amp;#x27;: list(map(lambda tag: tag[&amp;#x27;term&amp;#x27;], post.tags)),
                    &amp;#x27;link&amp;#x27;: post.link,
                }
            )
        )

    return posts


def generate_feed(sources):
    posts = []

    for source in sources:
        posts_by_source = parse(source)

        posts.extend(posts_by_source)

    return posts

sources = [habr_all_publications, habr_best_publications]

results = generate_feed(sources)

print(&amp;#x27;Length of posts:&amp;#x27;, len(results))&lt;/pre&gt;
  &lt;p id=&quot;rEma&quot;&gt;Функция &lt;code&gt;parse&lt;/code&gt; выкачивает RSS-ленту по ссылке, собирает коллекцию постов и возвращает её. Функция &lt;code&gt;generate_feed&lt;/code&gt; проходит по всем ссылкам, полученным на вход, формирует коллекцию публикаций, обращаясь к функции &lt;code&gt;parse&lt;/code&gt;.&lt;/p&gt;
  &lt;h2 id=&quot;L76i&quot;&gt;Создание БД&lt;/h2&gt;
  &lt;p id=&quot;xrxo&quot;&gt;Поскольку, подход NoORM не предусматривает автоматического создания БД и заполнения БД, необходимо собрать SQL-скрипт для инициализации БД.&lt;/p&gt;
  &lt;p id=&quot;HKvd&quot;&gt;Я довольно ленивый, когда дело доходит до SQL, поэтому скормил в LLM такой промпт:&lt;/p&gt;
  &lt;blockquote id=&quot;vVhu&quot;&gt;&amp;quot;Напиши sql запрос для создания таблицы dt_user со следующими полями: id (первичный ключ), login, password, таблицы dt_feed со следующими полями: id (первичный ключ), name, created_at, foreign key на таблицу dt_user, таблицы dt_source с полями id (первичный ключ), name, link, type, таблицы dt_post со следующими полями: id (первичный ключ), title, description, meta_info, raw_content, mark, created_at, foreign key на таблицу dt_feed, foreign key на таблицу dt_source&amp;quot;&lt;/blockquote&gt;
  &lt;p id=&quot;L3tF&quot;&gt;На выходе получил такой результат:&lt;/p&gt;
  &lt;pre id=&quot;Mx6U&quot; data-lang=&quot;sql&quot;&gt;CREATE TABLE dt_user (
    id INT AUTO_INCREMENT PRIMARY KEY,
    login VARCHAR(255) NOT NULL,
    password VARCHAR(255) NOT NULL
);

CREATE TABLE dt_feed (
    id INT AUTO_INCREMENT PRIMARY KEY,
    name VARCHAR(255) NOT NULL,
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    user_id INT,
    FOREIGN KEY (user_id) REFERENCES dt_user(id)
);

CREATE TABLE dt_source (
    id INT AUTO_INCREMENT PRIMARY KEY,
    name VARCHAR(255) NOT NULL,
    link VARCHAR(255) NOT NULL,
    type ENUM(&amp;#x27;RSS&amp;#x27;, &amp;#x27;ATOM&amp;#x27;) NOT NULL
);

CREATE TABLE dt_post (
    id INT AUTO_INCREMENT PRIMARY KEY,
    title VARCHAR(255) NOT NULL,
    description TEXT,
    meta_info JSON, -- если используется MySQL версии 5.7 и выше
    raw_content TEXT,
    mark BOOLEAN DEFAULT FALSE,
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    feed_id INT,
    source_id INT,
    FOREIGN KEY (feed_id) REFERENCES dt_feed(id),
    FOREIGN KEY (source_id) REFERENCES dt_source(id)
);&lt;/pre&gt;
  &lt;p id=&quot;L4Gs&quot;&gt;В целом, довольно неплохо, но мне надо для psql, поэтому решил перезапросить с уточнением:&lt;/p&gt;
  &lt;blockquote id=&quot;gUlj&quot;&gt;&amp;quot;Напиши sql запрос с использованием СУБД postgresql для создания таблицы dt_user со следующими полями: id (первичный ключ), login, password, таблицы dt_feed со следующими полями: id (первичный ключ), name, created_at, foreign key на таблицу dt_user, таблицы dt_source с полями id (первичный ключ), name, link, type, таблицы dt_post со следующими полями: id (первичный ключ), title, description, meta_info, raw_content, mark, created_at, foreign key на таблицу dt_feed, foreign key на таблицу dt_source&amp;quot;&lt;/blockquote&gt;
  &lt;pre id=&quot;19vO&quot; data-lang=&quot;sql&quot;&gt;CREATE TABLE dt_user (
    id SERIAL PRIMARY KEY,
    login VARCHAR(255) NOT NULL,
    password VARCHAR(255) NOT NULL
);

CREATE TABLE dt_feed (
    id SERIAL PRIMARY KEY,
    name VARCHAR(255) NOT NULL,
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    user_id INTEGER REFERENCES dt_user(id) ON DELETE CASCADE
);

CREATE TABLE dt_source (
    id SERIAL PRIMARY KEY,
    name VARCHAR(255) NOT NULL,
    link VARCHAR(255) NOT NULL,
    type VARCHAR(10) NOT NULL CHECK (type = &amp;#x27;RSS&amp;#x27; OR type = &amp;#x27;ATOM&amp;#x27;)
);

CREATE TABLE dt_post (
    id SERIAL PRIMARY KEY,
    title VARCHAR(255) NOT NULL,
    description TEXT,
    meta_info JSONB, -- JSONB поддерживает индексацию и поиск
    raw_content TEXT,
    mark BOOLEAN DEFAULT FALSE,
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    feed_id INTEGER REFERENCES dt_feed(id) ON DELETE CASCADE,
    source_id INTEGER REFERENCES dt_source(id) ON DELETE RESTRICT
);&lt;/pre&gt;
  &lt;p id=&quot;OEJF&quot;&gt;Как видно, скрипт вполне неплохой получился. От себя, я добавил только &lt;code&gt;IF NOT EXISTS&lt;/code&gt; к созданию каждой таблицы. А по названию полей LLM попыталась расставить типы и вполне неплохо с этим справилась. Интересно, что здесь даже предусмотрено сразу ограничение типов источника, что пока не слишком актуально, но в целом, — очень даже неплохо. Подытоживая, такого скрипта мне более, чем достаточно для MVP. &lt;/p&gt;
  &lt;p id=&quot;KoJ1&quot;&gt;Теперь, когда скрипт &lt;code&gt;init.sql&lt;/code&gt; готов, осталось только накидать простенький &lt;code&gt;docker-compose.yml&lt;/code&gt;, чтобы запустить БД:&lt;/p&gt;
  &lt;pre id=&quot;00V4&quot; data-lang=&quot;yaml&quot;&gt;services:
    db:
        image: postgres
        container_name: podborka_db
        environment:
            - POSTGRES_DB=maindb
            - POSTGRES_USER=maindb
            - POSTGRES_PASSWORD=maindb
            - POSTGRES_HOST=db
            - POSTGRES_PORT=5432

        ports:
            - 15432:5432

        volumes:
            - ./dbs/postgres-data:/var/lib/postgresql/data
            - ./init.sql:/docker-entrypoint-initdb.d/init.sql&lt;/pre&gt;
  &lt;p id=&quot;3Ynj&quot;&gt;Проверим, что всё создалось, зайдя в БД:&lt;/p&gt;
  &lt;pre id=&quot;fDNL&quot; data-lang=&quot;bash&quot;&gt;maindb=# \dt;
          List of relations
 Schema |   Name    | Type  | Owner  
--------+-----------+-------+--------
 public | dt_feed   | table | maindb
 public | dt_post   | table | maindb
 public | dt_source | table | maindb
 public | dt_user   | table | maindb
(4 rows)&lt;/pre&gt;
  &lt;p id=&quot;gyib&quot;&gt;Как видим, всё на месте. Можно переходить к настройке работы через NoORM.&lt;/p&gt;
  &lt;h2 id=&quot;vwiT&quot;&gt;NoORM&lt;/h2&gt;
  &lt;p id=&quot;Seuo&quot;&gt;Чтобы использовать NoORM вместе с PostgreSQL, надо установить адаптор. В данный момент, я остановился на библиотеке &lt;code&gt;psycopg2&lt;/code&gt;. В будущем планирую перейти на &lt;code&gt;asyncpg&lt;/code&gt;, но для MVP в этом никакой необходимости нет.&lt;/p&gt;
  &lt;p id=&quot;CGfS&quot;&gt;Установим зависимости (я использую именно binary-версию для psycopg2):&lt;/p&gt;
  &lt;pre id=&quot;3RUi&quot; data-lang=&quot;bash&quot;&gt;pip3 install true-noorm psycopg2-binary&lt;/pre&gt;
  &lt;p id=&quot;RxpV&quot;&gt;На текущем этапе, нам достаточно будет реализовать базовый набор CRUD-функций для каждой из таблиц в виде самостоятельных функций. Начнём с таблицы пользователей:&lt;/p&gt;
  &lt;pre id=&quot;Vm5g&quot; data-lang=&quot;python&quot;&gt;from dataclasses import dataclass

import noorm.psycopg2 as nm


@dataclass
class DbUser:
    id: int
    login: str

@nm.sql_fetch_all(DbUser, &amp;quot;SELECT id, login FROM dt_user&amp;quot;)
def get_all_users():
    ...

@nm.sql_one_or_none(
    DbUser, &amp;quot;SELECT id, login FROM dt_user id = :id&amp;quot;
)
def get_user_by_id(id: int):
    return nm.params(id=id)&lt;/pre&gt;
  &lt;p id=&quot;Zw9h&quot;&gt;Автор библиотеки призывает использовать именно такой подход, когда датакласс и функция/функции, которые его используют, — находятся настолько близко друг к другу, насколько это возможно.&lt;/p&gt;
  &lt;p id=&quot;E4OP&quot;&gt;Реализуем функцию для создания нового пользователя через INSERT-запрос (опять же, посредством допроса LLMки):&lt;/p&gt;
  &lt;pre id=&quot;R9ZM&quot; data-lang=&quot;python&quot;&gt;@nm.sql_execute(&amp;quot;INSERT INTO dt_user (login, password) VALUES (%s, %s)&amp;quot;)
def create_new_user(login: str, password: str):
    return nm.params(login, password)


def main():
    import psycopg2

    with psycopg2.connect(
     database=&amp;#x27;maindb&amp;#x27;, user=&amp;#x27;maindb&amp;#x27;,
     password=&amp;#x27;maindb&amp;#x27;, host=&amp;#x27;localhost&amp;#x27;,
     port=&amp;#x27;15432&amp;#x27;
 ) as conn:
        create_new_user(conn, &amp;#x27;test&amp;#x27;, &amp;#x27;test&amp;#x27;)
        conn.commit()

main()&lt;/pre&gt;
  &lt;p id=&quot;WJDv&quot;&gt;Синтаксис корректного запроса (имею ввиду, что в данном случае параметры не именованные и это неочевидный момент) я подглядел в примерах, которые лежали внутри кода библиотеки. И первое, что меня расстроило в работе с &lt;code&gt;NoORM&lt;/code&gt;: в результате выполнения запроса я не получил никакой информации о только что созданной записи, только &lt;code&gt;None&lt;/code&gt;. А всё-таки, хотелось бы получить, хотя бы, &lt;code&gt;id&lt;/code&gt;. С другой стороны, этим автор библиотеки неявно подталкивает нас к тому, чтобы делать в такой ситуации два запроса и сохранять при этом простоту взаимодействия.&lt;/p&gt;
  &lt;p id=&quot;nvK8&quot;&gt;Опишем такие же круды для остальных таблиц. Полный код проекта можно посмотреть на моём &lt;a href=&quot;https://github.com/kantegory/podborka&quot; target=&quot;_blank&quot;&gt;github&lt;/a&gt;.&lt;/p&gt;
  &lt;h2 id=&quot;JkS2&quot;&gt;Точка входа&lt;/h2&gt;
  &lt;p id=&quot;zNAZ&quot;&gt;Теперь, когда мы уже написали все функции для работы с БД, а также простой инструмент для парсинга RSS-лент, нам следует подружить две эти части нашего приложения между собой. Для этого я немного модифицировал файл &lt;code&gt;rss.py&lt;/code&gt;:&lt;/p&gt;
  &lt;pre id=&quot;fHd4&quot; data-lang=&quot;python&quot;&gt;import feedparser
from dataclasses import dataclass, asdict

import psycopg2
import json

from db.posts import create_new_post


@dataclass
class Post:
    title: str
    description: str
    meta_info: dict[str, any]


def parse(link):
    feed = feedparser.parse(link)

    posts = []

    for post in feed.entries:
        posts.append(
            Post(
                title=post.title,
                description=post.summary,
                meta_info={
                    &amp;#x27;published_at&amp;#x27;: post.published,
                    &amp;#x27;author&amp;#x27;: post.author,
                    &amp;#x27;tags&amp;#x27;: list(map(lambda tag: tag[&amp;#x27;term&amp;#x27;], post.tags)),
                    &amp;#x27;link&amp;#x27;: post.link,
                    # TODO: добавить поля
                    # &amp;#x27;raw_content&amp;#x27;: &amp;#x27;-&amp;#x27;,
                    # &amp;#x27;mark&amp;#x27;: &amp;#x27;-&amp;#x27;,
                    # &amp;#x27;feed_id&amp;#x27;: 1,
                    # &amp;#x27;source_id&amp;#x27;: 1,
                }
            )
        )

    return posts


def generate_feed(sources):
    posts = []

    for source in sources:
        posts_by_source = parse(source)

        posts.extend(posts_by_source)

    return posts


def save_posts(posts):
    with psycopg2.connect(database=&amp;#x27;maindb&amp;#x27;, user=&amp;#x27;maindb&amp;#x27;, password=&amp;#x27;maindb&amp;#x27;, host=&amp;#x27;localhost&amp;#x27;, port=&amp;#x27;15432&amp;#x27;) as conn:
        for post in posts:
            create_new_post(conn, **{ **asdict(post), &amp;#x27;meta_info&amp;#x27;: json.dumps(post.meta_info) })

        conn.commit()


def load_posts(sources):
    results = generate_feed(sources)

    print(&amp;#x27;First result:&amp;#x27;, results[0])

    print(&amp;#x27;Length of posts:&amp;#x27;, len(results))

    save_posts(results)&lt;/pre&gt;
  &lt;p id=&quot;Zmta&quot;&gt;По сути, мы дописали две функции: &lt;code&gt;save_posts&lt;/code&gt; для записи результатов парсинга в БД и &lt;code&gt;load_posts&lt;/code&gt; для объединения процессов генерации нашей подборки с последующим сохранением в БД.&lt;/p&gt;
  &lt;p id=&quot;4TG2&quot;&gt;Настало время реализовать точку входа, которая в будущем перейдёт под FastAPI, файл &lt;code&gt;main.py&lt;/code&gt;:&lt;/p&gt;
  &lt;pre id=&quot;LbV4&quot; data-lang=&quot;python&quot;&gt;from parser.rss import load_posts

habr_all_publications = &amp;#x27;https://habr.com/ru/rss/articles/?fl=ru&amp;#x27;
habr_best_publications = &amp;#x27;https://habr.com/ru/rss/articles/top/daily/?fl=ru&amp;#x27;

sources = [habr_all_publications, habr_best_publications]

load_posts(sources)&lt;/pre&gt;
  &lt;p id=&quot;Njaf&quot;&gt;Как видим, здесь мы получаем источники и запускаем процесс парсинга. Результат исполнения следующий:&lt;/p&gt;
  &lt;pre id=&quot;v2Dm&quot; data-lang=&quot;bash&quot;&gt;$ python3 main.py 
Rirst result: Post(title=&amp;#x27;Team Lead VS Engineering Manager&amp;#x27;, description=&amp;#x27;&amp;lt;img src=&amp;quot;https://habrastorage.org/getpro/habr/upload_files/800/cdc/5f1/800cdc5f15df57e3ef7d1ff7d736a6ed.jpeg&amp;quot; /&amp;gt;&amp;lt;p&amp;gt;Приветствую! Меня зовут Василиса Версус и я в прошлом трижды СТО в небольших стартапах (20-50 чел), а также head of инфраструктуры / разработки в таких компаниях как Яндекс и Сбермаркет&amp;lt;/p&amp;gt;&amp;lt;p&amp;gt;Сегодня мне очень хочется поделиться несколькими мыслями на тему карьерного развития. И недостающими пазлами между позицией тимлида и желаемой многими позиции СтанцииТехническогоОбслуживания.&amp;lt;/p&amp;gt; &amp;lt;a href=&amp;quot;https://habr.com/ru/articles/827094/?utm_source=habrahabr&amp;amp;amp;utm_medium=rss&amp;amp;amp;utm_campaign=827094#habracut&amp;quot;&amp;gt;Читать далее&amp;lt;/a&amp;gt;&amp;#x27;, meta_info={&amp;#x27;published_at&amp;#x27;: &amp;#x27;Sat, 06 Jul 2024 15:16:46 GMT&amp;#x27;, &amp;#x27;author&amp;#x27;: &amp;#x27;dcversus&amp;#x27;, &amp;#x27;tags&amp;#x27;: [&amp;#x27;engineering management&amp;#x27;, &amp;#x27;team leading&amp;#x27;, &amp;#x27;development&amp;#x27;, &amp;#x27;career advice&amp;#x27;, &amp;#x27;cto&amp;#x27;, &amp;#x27;тимлидство&amp;#x27;], &amp;#x27;link&amp;#x27;: &amp;#x27;https://habr.com/ru/articles/827094/?utm_source=habrahabr&amp;amp;utm_medium=rss&amp;amp;utm_campaign=827094&amp;#x27;})
Length of posts: 61&lt;/pre&gt;
  &lt;p id=&quot;0ab5&quot;&gt;На всякий случай, проверим это и запросом в БД:&lt;/p&gt;
  &lt;pre id=&quot;lT1X&quot; data-lang=&quot;bash&quot;&gt;maindb=# select count(*) from dt_post;
 count 
-------
    61
(1 row)&lt;/pre&gt;
  &lt;h2 id=&quot;rU55&quot;&gt;Заключение&lt;/h2&gt;
  &lt;p id=&quot;ymJM&quot;&gt;Честно признаться, я и не думал, что меня может настолько заинтересовать этот пет-проект. В эту минуту я вижу, что количество слов в моей заметке уже подходит к двум тысячам.&lt;/p&gt;
  &lt;p id=&quot;5IBU&quot;&gt;Ничего лучше, кроме как разделить описание работы над этим пет-проектом на несколько частей, я, увы, не придумал. Поэтому, продолжение с FastAPI следует...&lt;/p&gt;

</content></entry><entry><id>kantegory:read-not-only-orm</id><link rel="alternate" type="text/html" href="https://blog.kantegory.me/read-not-only-orm?utm_source=teletype&amp;utm_medium=feed_atom&amp;utm_campaign=kantegory"></link><title>Что я прочитал - не только ORM</title><published>2024-06-30T14:29:02.795Z</published><updated>2024-07-06T16:27:21.400Z</updated><category term="programming" label="Программирование"></category><tt:hashtag>чтояпрочитал</tt:hashtag><tt:hashtag>orm</tt:hashtag><tt:hashtag>бд</tt:hashtag><summary type="html">&lt;img src=&quot;https://img4.teletype.in/files/78/38/78380773-da4c-4ae3-9e61-5d3d9e560266.png&quot;&gt;В статье автор описывает устройство ORM и основную, на его взгляд, проблему такого подхода — &quot;персистетные объекты&quot;. И предлагает альтернативу в виде кастомной библиотеки, позволяющей работать с БД без использования персистетных объектов, при этом не засоряя основной код SQL-запросами.</summary><content type="html">
  &lt;tt-tags id=&quot;9iOF&quot;&gt;
    &lt;tt-tag name=&quot;чтояпрочитал&quot;&gt;#чтояпрочитал&lt;/tt-tag&gt;
    &lt;tt-tag name=&quot;orm&quot;&gt;#orm&lt;/tt-tag&gt;
    &lt;tt-tag name=&quot;бд&quot;&gt;#бд&lt;/tt-tag&gt;
  &lt;/tt-tags&gt;
  &lt;p id=&quot;BnMl&quot;&gt;Ссылка на оригинал: &lt;a href=&quot;https://habr.com/ru/articles/818761/&quot; target=&quot;_blank&quot;&gt;https://habr.com/ru/articles/818761/&lt;/a&gt;&lt;br /&gt;Ссылка на гитхаб проекта: &lt;a href=&quot;https://github.com/amaslyaev/noorm&quot; target=&quot;_blank&quot;&gt;https://github.com/amaslyaev/noorm&lt;/a&gt;&lt;/p&gt;
  &lt;h2 id=&quot;NF0I&quot;&gt;Кратко&lt;/h2&gt;
  &lt;p id=&quot;qKr1&quot;&gt;В статье автор описывает устройство ORM и основную, на его взгляд, проблему такого подхода — &amp;quot;персистетные объекты&amp;quot;. И предлагает альтернативу в виде кастомной библиотеки, позволяющей работать с БД без использования персистетных объектов, при этом не засоряя основной код SQL-запросами.&lt;/p&gt;
  &lt;figure id=&quot;fhar&quot; class=&quot;m_original&quot;&gt;
    &lt;img src=&quot;https://img4.teletype.in/files/78/38/78380773-da4c-4ae3-9e61-5d3d9e560266.png&quot; width=&quot;828&quot; /&gt;
  &lt;/figure&gt;
  &lt;p id=&quot;Y3fY&quot;&gt;Выдержка про проблему персистентных объектов:&lt;/p&gt;
  &lt;blockquote id=&quot;J4Ew&quot;&gt;Примечательно здесь то, что работа с базой данных идёт через персистентные объекты, являющиеся экземплярами «модельных» классов, описывающих структуру БД. Эти персистентные объекты умеют себя прочитать из базы и в неё себя записать. Они живут внутри открытой сессии. И ещё эти объекты умеют «лениво» дотягивать из базы связанные с ними другие персистентные объекты. Эти самые персистентные объекты — корень всех проблем:&lt;br /&gt;&lt;br /&gt;1. По сути, это передача мутабельного объекта в другой процесс. Безобразно тупая затея. Мы запросили сущность «пользователь Вася» из базы данных в процесс своего бэкенда, и теперь где у нас теперь мастер-копия? Как мы их собираемся синхронизировать, в какой момент, и что собираемся делать с возможными коллизиями?&lt;br /&gt;&lt;br /&gt;2. Что случается с живущими в сессии объектами когда сессия закрывается? Что если они продолжают быть нужны в логике приложения? Что если эта логика продолжает считать, что это по-прежнему нормальные объекты, принадлежащие живой сессии?&lt;br /&gt;&lt;br /&gt;3. Невозможно найти единственно правильный баланс между eager- и lazy-загрузкой. Если увлекаемся lazy, получаем проблему N+1, и всё начинает страшно тормозить. Если увлекаемся eager, на каждый невинный чих ORM пытается вычитать полбазы, и тоже всё тормозит. Короче, у нас две педали, но обе они педали тормоза.&lt;/blockquote&gt;
  &lt;h2 id=&quot;Y3ks&quot;&gt;Какой подход предлагает автор взамен?&lt;/h2&gt;
  &lt;p id=&quot;brne&quot;&gt;Выделение отдельного модуля с логикой БД и SQL-запросами, который может выглядеть примерно так:&lt;/p&gt;
  &lt;pre id=&quot;wkXg&quot; data-lang=&quot;python&quot;&gt;from dataclasses import dataclass
import noorm.sqlite3 as nm

@dataclass
class DbUser:
 id: int
 username: str
 email: str

@nm.sql_fetch_all(DbUser, &amp;quot;SELECT rowid AS id, username, email FROM users&amp;quot;)
def get_users():
 pass&lt;/pre&gt;
  &lt;p id=&quot;CCmL&quot;&gt;Использование датакласса позволяет однозначно определить возвращаемую структуру данных и ограничить её тип, что может быть довольно полезно при использовании этой самой структуры. Кроме того, в подсказке в IDE будет выводиться весь набор её свойств.&lt;/p&gt;
  &lt;h2 id=&quot;9qPw&quot;&gt;Выводы&lt;/h2&gt;
  &lt;p id=&quot;tmX4&quot;&gt;В целом, подход имеет место быть и я даже уже начал собирать небольшой пет-проект с использованием NoORM. В ближайшие дни будет несколько постов по этой теме. Подумываю о том, чтобы снять об этом даже полноценное видео (не о самом подходе, а скорее о пет-проекте).&lt;/p&gt;

</content></entry><entry><id>kantegory:read-productive-habits-for-it-engineer</id><link rel="alternate" type="text/html" href="https://blog.kantegory.me/read-productive-habits-for-it-engineer?utm_source=teletype&amp;utm_medium=feed_atom&amp;utm_campaign=kantegory"></link><title>Что я прочитал: &quot;какие привычки освоить it-шнику, чтобы стать продуктивнее (или здоровее)?&quot;</title><published>2024-06-26T18:26:56.657Z</published><updated>2024-06-26T18:26:56.657Z</updated><tt:hashtag>чтояпрочитал</tt:hashtag><tt:hashtag>продуктивность</tt:hashtag><summary type="html">В целом, со многими пунктами я согласен, но в статье мне не нравится, что на текст тут явно забили. Много помарок и само изложение хромает.</summary><content type="html">
  &lt;tt-tags id=&quot;ImUP&quot;&gt;
    &lt;tt-tag name=&quot;чтояпрочитал&quot;&gt;#чтояпрочитал&lt;/tt-tag&gt;
    &lt;tt-tag name=&quot;продуктивность&quot;&gt;#продуктивность&lt;/tt-tag&gt;
  &lt;/tt-tags&gt;
  &lt;p id=&quot;VviF&quot;&gt;Ссылка на оригинал: &lt;a href=&quot;https://habr.com/ru/articles/818923/&quot; target=&quot;_blank&quot;&gt;https://habr.com/ru/articles/818923/&lt;/a&gt; &lt;/p&gt;
  &lt;p id=&quot;vy9O&quot;&gt;Кратко (полностью скопировано из самой статьи)&lt;/p&gt;
  &lt;p id=&quot;GXCG&quot;&gt;Общий список привычек на ежедневную основу&lt;/p&gt;
  &lt;p id=&quot;qTQl&quot;&gt;- Пространственные упражнения: особые упражнения для формирования настроя на день.&lt;br /&gt;- Принятие холодного душа: холодный душ / обтирания / умывания для поднятия общего тонуса и настроя на день.&lt;br /&gt;- Планирование следующего дня: планирование задач на следующий день в конце текущего.&lt;br /&gt;- Чтение: чтение художественной / профессиональной литературы.&lt;br /&gt;- Стакан воды с утра: выпивание стакана воды после пробуждения для увлажнения организма и стимуляции обмена веществ.&lt;br /&gt;- Ходьба / Бег: регулярные прогулки или бег для поддержания физической формы и энергии.&lt;br /&gt;- Сон по расписанию: соблюдение определенного режима сна, с учетом жизненных циклов организма и мира.&lt;br /&gt;- Медитация: медитирование для улучшения концентрации, снятия стресса и общего благополучия.&lt;/p&gt;
  &lt;p id=&quot;29q1&quot;&gt;В целом, со многими пунктами я согласен, но в статье мне не нравится, что на текст тут явно забили. Много помарок и само изложение хромает.&lt;/p&gt;
  &lt;p id=&quot;7mhi&quot;&gt;На мой взгляд, точно будет работать:&lt;/p&gt;
  &lt;p id=&quot;Y49C&quot;&gt;- планирование следующего дня: я, к примеру, планирую все свои дела раз в неделю, а в конце дня просто сверяюсь с планом;&lt;br /&gt;- чтение: меня пока что хватает только на ежедневное чтение разных статей и постов в тг, до книжек руки не доходят;&lt;br /&gt;- стакан воды с утра: от воды в любом случае хуже не будет, а с утра действительно может помочь лучше проснуться;&lt;br /&gt;- ходьба: именно ходьба, а не бег, потому что бег травмоопасен, а гулять на свежем воздухе — очень полезно;&lt;br /&gt;- сон по расписанию: в ноябре прошлого года я уже &lt;a href=&quot;https://t.me/davidobryakov/1181&quot; target=&quot;_blank&quot;&gt;писал&lt;/a&gt; про режим, рад поделиться тем, что уже более 3 недель удерживаю прекрасный режим без особых трудностей и делаю это довольно аккуратно. Мне помогают два будильника с интервалом 1.5 часа друг от друга. Первый за 1.5 часа до момента, когда мне надо проснуться, а второй ровно в то время, когда надо проснуться. Плюсом к этому, я включил сценарий светового будильника и когда я просыпаюсь с утра, чтобы выключить второй будильник, — в комнате уже светло, помогает легко встать.&lt;/p&gt;
  &lt;p id=&quot;9J9x&quot;&gt;Дополнительный пункт от меня: баланс между работой и жизнью (aka &amp;quot;work/life balance&amp;quot;). Я поставил себе чёткие рамки начала и окончания рабочего дня и это действительно помогает удерживаться от переработок и уменьшает энтропию.&lt;/p&gt;
  &lt;p id=&quot;9xzz&quot;&gt;По остальным пунктам у меня есть сомнения. Как минимум, тот же холодный душ много кому противопоказан. Рекомендуют принимать контрастный, вместо холодного, да и то не более двух минут, чтобы не повлиять негативно на свой организм. Я пробовал принимать контрастный душ и выяснил, что летом он вообще не ощущается контрастным, а вот зимой — наоборот. В среднем, любой душ с утра положительно сказывается на настроении.&lt;/p&gt;
  &lt;p id=&quot;4M6A&quot;&gt;Пространственные упражнения, как их называет автор, я лично не делаю. Зато делаю ежедневно утреннюю суставную разминку и вечернюю суставную разминку. Это во-первых позволяет снять напряжение в мышцах, а во-вторых тело не будет болеть так сильно, как могло бы, после целого дня за компьютером.&lt;/p&gt;
  &lt;p id=&quot;bgFh&quot;&gt;Медитацию я не пробовал и пока не горю желанием. Жду кризиса среднего возраста. Некоторые мейнстримные привычки, типа той же медитации, меня немного отталкивают как раз-таки своей мейнстримностью.&lt;/p&gt;

</content></entry><entry><id>kantegory:clean-code-comments</id><link rel="alternate" type="text/html" href="https://blog.kantegory.me/clean-code-comments?utm_source=teletype&amp;utm_medium=feed_atom&amp;utm_campaign=kantegory"></link><title>Комментарии в коде</title><published>2024-06-24T18:49:33.614Z</published><updated>2024-06-24T18:49:33.614Z</updated><category term="programming" label="Программирование"></category><tt:hashtag>дискуссия</tt:hashtag><summary type="html">На днях, автор канала Cross Join опубликовал пост с рассуждениями на тему того, насколько в коде нужны комментарии.</summary><content type="html">
  &lt;tt-tags id=&quot;OtNv&quot;&gt;
    &lt;tt-tag name=&quot;дискуссия&quot;&gt;#дискуссия&lt;/tt-tag&gt;
  &lt;/tt-tags&gt;
  &lt;p id=&quot;q2LM&quot;&gt;На днях, автор канала Cross Join опубликовал &lt;a href=&quot;https://t.me/crossjoin/329&quot; target=&quot;_blank&quot;&gt;пост&lt;/a&gt; с рассуждениями на тему того, насколько в коде нужны комментарии.&lt;/p&gt;
  &lt;p id=&quot;mK0O&quot;&gt;Основной проблемой он считает то, что комментарии будут терять свою актуальность по мере внесения изменений в код.&lt;/p&gt;
  &lt;p id=&quot;qAMj&quot;&gt;Как возможную альтернативу, он предлагает писать более подробные сообщения к коммитам, поскольку они как раз привязаны к конкретным точкам во времени.&lt;/p&gt;
  &lt;p id=&quot;3uex&quot;&gt;На мой взгляд, комментарии нужны и полезны. Как минимум, следует использовать документирующие комментарии, которые в некоторых случаях (например, использование jsdoc), позволяют описать типы входных и выходных аргументов у функции, дополнить её общим описанием, что упрощает работу с вашим кодом. Но вероятнее всего, этот пример один из немногих, когда комментарии действительно полезны и необходимы.&lt;/p&gt;
  &lt;p id=&quot;k9bQ&quot;&gt;Существуют разные точки зрения на комментирование кода. Автор книги &amp;quot;Чистый код&amp;quot;, к примеру, &lt;a href=&quot;https://dev.to/kaww/clean-code-improve-your-code-comments-blh&quot; target=&quot;_blank&quot;&gt;призывает&lt;/a&gt; писать код без комментариев, объясняя это тем, что каждый оставленный комментарий - это неудача и комментировать код стоит только в крайнем случае.&lt;/p&gt;
  &lt;p id=&quot;G1bN&quot;&gt;Конечно, в идеале, код должен легко читаться и без комментариев. На мой взгляд, допустимы документирующие комментарии, а также ссылки на референсы. К примеру, если вы добавляете новую библиотеку и дописываете её конфигурацию в общий конфигурационный файл, можно сослаться на документацию этой библиотеки.&lt;/p&gt;
  &lt;p id=&quot;YjM4&quot;&gt;Я же, в свою очередь, признаюсь, что очень люблю оставлять иногда комментарии даже в самых очевидных местах, потому что мне приходится на постоянной основе переключаться между несколькими проектами, а всего в голове удержать не удаётся. Поэтому скорость переключения контекста, в моём случае, повышается за счёт оставленных подсказок и напоминаний.&lt;/p&gt;
  &lt;p id=&quot;bw09&quot;&gt;А как вы подходите к написанию комментариев в коде?&lt;/p&gt;

</content></entry><entry><id>kantegory:input-phone-react-component</id><link rel="alternate" type="text/html" href="https://blog.kantegory.me/input-phone-react-component?utm_source=teletype&amp;utm_medium=feed_atom&amp;utm_campaign=kantegory"></link><title>Компонент для номера телефона</title><published>2024-04-09T10:55:42.026Z</published><updated>2024-07-06T16:26:59.486Z</updated><category term="frontend" label="Интерфейсы и frontend"></category><tt:hashtag>frontend</tt:hashtag><tt:hashtag>react</tt:hashtag><summary type="html">Около месяца назад я писал пост про библиотеку imask, чтобы стандартизировать ввод номера телефона, с помощью использования маски. Тогда же я упомянул, что основным минусом, на мой взгляд, является то, что нужно написать много дополнительного кода, чтобы достичь желаемого результата.</summary><content type="html">
  &lt;tt-tags id=&quot;6Lvv&quot;&gt;
    &lt;tt-tag name=&quot;frontend&quot;&gt;#frontend&lt;/tt-tag&gt;
    &lt;tt-tag name=&quot;react&quot;&gt;#react&lt;/tt-tag&gt;
  &lt;/tt-tags&gt;
  &lt;p id=&quot;JNu1&quot;&gt;Около месяца назад я &lt;a href=&quot;https://t.me/davidobryakov/1207&quot; target=&quot;_blank&quot;&gt;писал пост&lt;/a&gt; про библиотеку &lt;a href=&quot;https://imask.js.org/&quot; target=&quot;_blank&quot;&gt;imask&lt;/a&gt;, чтобы стандартизировать ввод номера телефона, с помощью использования маски. Тогда же я упомянул, что основным минусом, на мой взгляд, является то, что нужно написать много дополнительного кода, чтобы достичь желаемого результата.&lt;/p&gt;
  &lt;p id=&quot;GuVM&quot;&gt;Пару недель назад я как раз столкнулся с подобной задачей в рамках проекта на react и захотел попробовать реализовать это с помощью imask, но столкнулся с проблемой: нужно отображать список стран, с привязанным к ним кодом. В imask есть возможность решить это, но нет готового набора кодов номеров телефонов. Я вспомнил о &lt;a href=&quot;https://github.com/typesnippet/antd-phone-input&quot; target=&quot;_blank&quot;&gt;решении&lt;/a&gt;, которое мои сотрудники находили. Попробовал его адаптировать и понял, что я особо не могу его кастомизировать (по дизайну требовалось выводить двухбуквенное название страны, а не флаг), несмотря на его описание, — &amp;quot;Advanced, highly customizable phone input component for Ant Design.&amp;quot;&lt;/p&gt;
  &lt;p id=&quot;hVbl&quot;&gt;Поняв, что нахожусь в сложной ситуации, я пошёл искать библиотеки, которые предоставляют просто список телефонных кодов по странам, чтобы реализовать это с помощью imask и нашёл &lt;a href=&quot;https://www.npmjs.com/package/country-codes-list&quot; target=&quot;_blank&quot;&gt;такое решение&lt;/a&gt;. По сути, это простая библиотечка, которая возвращает нам объект из пар ключ-значение в том формате, в котором нам необходимо. Казалось бы, всё замечательно. Можно брать &lt;a href=&quot;https://imask.js.org/guide.html#masked-dynamic&quot; target=&quot;_blank&quot;&gt;динамически маски&lt;/a&gt; в imask, генерировать их с помощью этих самых кодов и идти спокойно пить чай. Но в этот момент я осознал, что у разных стран, буквально, могут быть разные маски для ввода номера телефона и это окончательно закопало мою прекрасную идею об использовании imask для решения этой задачи.&lt;/p&gt;
  &lt;p id=&quot;ByHt&quot;&gt;Пример с самого сайта imask, где наглядно видно, что маски вообще не подчиняются единым правилам и могут выглядеть абсолютно как угодно:&lt;/p&gt;
  &lt;pre id=&quot;KH3a&quot; data-lang=&quot;javascript&quot;&gt;[
    {
      mask: &amp;#x27;+00 {21} 0 000 0000&amp;#x27;,
      startsWith: &amp;#x27;30&amp;#x27;,
      lazy: false,
      country: &amp;#x27;Greece&amp;#x27;
    },
    {
      mask: &amp;#x27;+0 000 000-00-00&amp;#x27;,
      startsWith: &amp;#x27;7&amp;#x27;,
      lazy: false,
      country: &amp;#x27;Russia&amp;#x27;
    },
    {
      mask: &amp;#x27;+00-0000-000000&amp;#x27;,
      startsWith: &amp;#x27;91&amp;#x27;,
      lazy: false,
      country: &amp;#x27;India&amp;#x27;
    },
    {
      mask: &amp;#x27;0000000000000&amp;#x27;,
      startsWith: &amp;#x27;&amp;#x27;,
      country: &amp;#x27;unknown&amp;#x27;
    }
]&lt;/pre&gt;
  &lt;p id=&quot;kVgW&quot;&gt;Ресёрч пришлось продолжать. Я нашёл &lt;a href=&quot;https://www.npmjs.com/package/libphonenumber-js&quot; target=&quot;_blank&quot;&gt;библиотеку&lt;/a&gt;, которая позволяла валидировать номер телефона относительно двухбуквенного кода страны, к которой этот номер относится. Кроме прочего, в этой библиотеке есть и возможность форматирования номера телефона, как раз под маску, принятую в конкретной стране. В описании библиотеки также упоминался &lt;a href=&quot;https://www.npmjs.com/package/react-phone-number-input&quot; target=&quot;_blank&quot;&gt;react-компонент&lt;/a&gt;, который использует эту самую библиотеку. И на основе уже этого компонента, я собрал своё решение с использованием &lt;a href=&quot;https://ant.design/&quot; target=&quot;_blank&quot;&gt;ant-design&lt;/a&gt; (разумеется, оно не идеальное, но достаточно хорошее, чтобы использовать в условном продакшне):&lt;/p&gt;
  &lt;pre id=&quot;u3Fz&quot; data-lang=&quot;javascript&quot;&gt;import PhoneInput from &amp;#x27;react-phone-number-input&amp;#x27;;
import PropTypes from &amp;#x27;prop-types&amp;#x27;;
import { getCountries } from &amp;#x27;react-phone-number-input&amp;#x27;;
import { Select as AntdSelect } from &amp;#x27;antd&amp;#x27;;

const phoneCountryLabels = () =&amp;gt; {
    const countryLabels = {};

    getCountries().forEach((country) =&amp;gt; (countryLabels[country] = country));

    return countryLabels;
};

const CountrySelect = ({ value, onChange, labels, variant = &amp;#x27;filled&amp;#x27;, ...rest }) =&amp;gt; (
    &amp;lt;AntdSelect
        {...rest}
        value={value}
        showSearch
        optionFilterProp=&amp;quot;label&amp;quot;
        onChange={onChange}
        variant={variant}
        size=&amp;quot;large&amp;quot;
        style={{ &amp;#x27;--ant-select-single-item-height-lg&amp;#x27;: &amp;#x27;3rem&amp;#x27; }}
    /&amp;gt;
);

CountrySelect.propTypes = {
    value: PropTypes.string,
    onChange: PropTypes.func.isRequired,
    labels: PropTypes.objectOf(PropTypes.string).isRequired,
    variant: PropTypes.string,
};

const PhoneNumberInput = ({ onChange, country = &amp;#x27;US&amp;#x27; }) =&amp;gt; {
    return (
        &amp;lt;div className=&amp;quot;base-phone-number&amp;quot;&amp;gt;
            &amp;lt;PhoneInput
                onChange={onChange}
                defaultCountry={country}
                international
                limitMaxLength
                labels={phoneCountryLabels()}
                countrySelectComponent={CountrySelect}
                numberInputProps={{
                    className: &amp;#x27;ant-input ant-input-filled css-var-r1 ant-input-css-var base-input&amp;#x27;,
                }}
            /&amp;gt;
        &amp;lt;/div&amp;gt;
    );
};

PhoneNumberInput.propTypes = {
    onChange: PropTypes.func.isRequired,
    country: PropTypes.string,
};

export default PhoneNumberInput;&lt;/pre&gt;
  &lt;p id=&quot;1WXM&quot;&gt;А как вы решали подобные задачи? Делитесь, будет интересно узнать.&lt;/p&gt;
  &lt;p id=&quot;izm3&quot;&gt;P. S. как появится немного времени, планирую тоже самое адаптировать под vue-компонент и поделиться результатом&lt;/p&gt;

</content></entry><entry><id>kantegory:python-backend-job-requirements-2024</id><link rel="alternate" type="text/html" href="https://blog.kantegory.me/python-backend-job-requirements-2024?utm_source=teletype&amp;utm_medium=feed_atom&amp;utm_campaign=kantegory"></link><title>Что нужно знать python backend developer для устройства на работу</title><published>2024-03-27T07:42:47.295Z</published><updated>2024-03-27T08:08:07.049Z</updated><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://img4.teletype.in/files/bf/a2/bfa26aad-c5f9-448e-bd57-e24f66329ca8.png"></media:thumbnail><tt:hashtag>django</tt:hashtag><tt:hashtag>python</tt:hashtag><tt:hashtag>backend</tt:hashtag><tt:hashtag>fastapi</tt:hashtag><summary type="html">&lt;img src=&quot;https://img3.teletype.in/files/af/9f/af9fe03d-a61f-488e-90d2-4162e9515114.png&quot;&gt;Пару лет назад я уже писал подобный пост. Тогда он был основан на моём личном опыте в найме (текущий, в общем-то тоже), пришло время актуализировать список. Если считаете, что я что-то упустил, то жду ваших предложений в комментариях.</summary><content type="html">
  &lt;tt-tags id=&quot;XMo8&quot;&gt;
    &lt;tt-tag name=&quot;django&quot;&gt;#django&lt;/tt-tag&gt;
    &lt;tt-tag name=&quot;python&quot;&gt;#python&lt;/tt-tag&gt;
    &lt;tt-tag name=&quot;backend&quot;&gt;#backend&lt;/tt-tag&gt;
    &lt;tt-tag name=&quot;fastapi&quot;&gt;#fastapi&lt;/tt-tag&gt;
  &lt;/tt-tags&gt;
  &lt;figure id=&quot;tgjX&quot; class=&quot;m_original&quot;&gt;
    &lt;img src=&quot;https://img3.teletype.in/files/af/9f/af9fe03d-a61f-488e-90d2-4162e9515114.png&quot; width=&quot;1583&quot; /&gt;
  &lt;/figure&gt;
  &lt;p id=&quot;Hr6K&quot;&gt;Пару лет назад я уже писал &lt;a href=&quot;https://t.me/davidobryakov/1140&quot; target=&quot;_blank&quot;&gt;подобный пост&lt;/a&gt;. Тогда он был основан на моём личном опыте в найме (текущий, в общем-то тоже), пришло время актуализировать список. Если считаете, что я что-то упустил, то жду ваших предложений в комментариях.&lt;/p&gt;
  &lt;h2 id=&quot;uwWf&quot;&gt;Фреймворки&lt;/h2&gt;
  &lt;ul id=&quot;XoKF&quot;&gt;
    &lt;li id=&quot;8OBm&quot;&gt;&lt;a href=&quot;https://docs.djangoproject.com/en/5.0/intro/tutorial01/&quot; target=&quot;_blank&quot;&gt;Django&lt;/a&gt; (+ &lt;a href=&quot;https://www.django-rest-framework.org/tutorial/quickstart/&quot; target=&quot;_blank&quot;&gt;DRF&lt;/a&gt;),&lt;/li&gt;
    &lt;li id=&quot;VpgE&quot;&gt;&lt;a href=&quot;https://fastapi.tiangolo.com/&quot; target=&quot;_blank&quot;&gt;FastAPI&lt;/a&gt;/&lt;a href=&quot;https://flask.palletsprojects.com/en/3.0.x/&quot; target=&quot;_blank&quot;&gt;Flask&lt;/a&gt; (+ &lt;a href=&quot;https://alembic.sqlalchemy.org/en/latest/tutorial.html&quot; target=&quot;_blank&quot;&gt;alembic&lt;/a&gt;, &lt;a href=&quot;https://docs.sqlalchemy.org/en/20/&quot; target=&quot;_blank&quot;&gt;sqlalchemy&lt;/a&gt;).&lt;/li&gt;
  &lt;/ul&gt;
  &lt;h2 id=&quot;xGU2&quot;&gt;Базы данных&lt;/h2&gt;
  &lt;ul id=&quot;Gb5s&quot;&gt;
    &lt;li id=&quot;NqSC&quot;&gt;&lt;a href=&quot;https://redis.io/docs/get-started/&quot; target=&quot;_blank&quot;&gt;redis&lt;/a&gt;,&lt;/li&gt;
    &lt;li id=&quot;ufSp&quot;&gt;&lt;a href=&quot;https://www.postgresql.org/docs/current/index.html&quot; target=&quot;_blank&quot;&gt;postgresql&lt;/a&gt;.&lt;/li&gt;
  &lt;/ul&gt;
  &lt;h2 id=&quot;8ohp&quot;&gt;Основы&lt;/h2&gt;
  &lt;ul id=&quot;IsjD&quot;&gt;
    &lt;li id=&quot;7nqy&quot;&gt;&lt;a href=&quot;https://developer.mozilla.org/ru/docs/Web/HTTP&quot; target=&quot;_blank&quot;&gt;HTTP-протокол&lt;/a&gt;, &lt;a href=&quot;https://developer.mozilla.org/ru/docs/Web/HTTP/Methods&quot; target=&quot;_blank&quot;&gt;HTTP-методы&lt;/a&gt;,&lt;/li&gt;
    &lt;li id=&quot;cpK3&quot;&gt;что такое &lt;a href=&quot;https://habr.com/ru/articles/483202/&quot; target=&quot;_blank&quot;&gt;REST, REST API, RESTful&lt;/a&gt;,&lt;/li&gt;
    &lt;li id=&quot;19R4&quot;&gt;что такое &lt;a href=&quot;https://developer.mozilla.org/ru/docs/Web/HTTP/CORS&quot; target=&quot;_blank&quot;&gt;CORS&lt;/a&gt; и как его победить,&lt;/li&gt;
    &lt;li id=&quot;kLdy&quot;&gt;linux/unix на уровне понимания работы по SSH, права доступа,&lt;/li&gt;
    &lt;li id=&quot;22uJ&quot;&gt;понимание концепции &lt;a href=&quot;https://ru.hexlet.io/blog/posts/chto-takoe-mvc-rasskazyvaem-prostymi-slovami&quot; target=&quot;_blank&quot;&gt;MVC&lt;/a&gt; и принципа работы &lt;a href=&quot;https://dev.to/tobias-piotr/patterns-and-practices-for-using-sqlalchemy-20-with-fastapi-49n8&quot; target=&quot;_blank&quot;&gt;паттерна репозиторий&lt;/a&gt; (больше нужен для работы с SQLAlchemy),&lt;/li&gt;
    &lt;li id=&quot;oJJ4&quot;&gt;&lt;a href=&quot;https://github.com/yakimka/python_interview_questions&quot; target=&quot;_blank&quot;&gt;вопросы для подготовки к интервью&lt;/a&gt; (список там довольно большой, обращайте внимание на те моменты, по которым вы совсем ничего не знаете).&lt;/li&gt;
  &lt;/ul&gt;
  &lt;h2 id=&quot;AyXy&quot;&gt;Что нужно знать в django&lt;/h2&gt;
  &lt;h3 id=&quot;FrZl&quot;&gt;Обязательно&lt;/h3&gt;
  &lt;ul id=&quot;wiWD&quot;&gt;
    &lt;li id=&quot;jE1t&quot;&gt;работа с django ORM: &lt;a href=&quot;https://docs.djangoproject.com/en/5.0/topics/db/queries/&quot; target=&quot;_blank&quot;&gt;https://docs.djangoproject.com/en/5.0/topics/db/queries/&lt;/a&gt;,&lt;/li&gt;
    &lt;li id=&quot;uN5x&quot;&gt;миграции в django ORM: &lt;a href=&quot;https://docs.djangoproject.com/en/5.0/topics/migrations/&quot; target=&quot;_blank&quot;&gt;https://docs.djangoproject.com/en/5.0/topics/migrations/&lt;/a&gt;,&lt;/li&gt;
    &lt;li id=&quot;L4lT&quot;&gt;менеджеры моделей: &lt;a href=&quot;https://docs.djangoproject.com/en/5.0/topics/db/managers/&quot; target=&quot;_blank&quot;&gt;https://docs.djangoproject.com/en/5.0/topics/db/managers/&lt;/a&gt;,&lt;/li&gt;
    &lt;li id=&quot;09Sy&quot;&gt;сериализаторы, представления и права доступа из DRF: &lt;a href=&quot;https://www.django-rest-framework.org/tutorial/quickstart/&quot; target=&quot;_blank&quot;&gt;https://www.django-rest-framework.org/tutorial/quickstart/&lt;/a&gt;,&lt;/li&gt;
    &lt;li id=&quot;vTSh&quot;&gt;работа с фильтрами через django-filter: &lt;a href=&quot;https://django-filter.readthedocs.io/en/stable/guide/usage.html&quot; target=&quot;_blank&quot;&gt;https://django-filter.readthedocs.io/en/stable/guide/usage.html&lt;/a&gt;.&lt;/li&gt;
  &lt;/ul&gt;
  &lt;h3 id=&quot;w4OC&quot;&gt;Опционально&lt;/h3&gt;
  &lt;ul id=&quot;FAxo&quot;&gt;
    &lt;li id=&quot;DNt3&quot;&gt;формы: &lt;a href=&quot;https://docs.djangoproject.com/en/5.0/ref/forms/&quot; target=&quot;_blank&quot;&gt;https://docs.djangoproject.com/en/5.0/ref/forms/&lt;/a&gt;,&lt;/li&gt;
    &lt;li id=&quot;amvD&quot;&gt;admin actions: &lt;a href=&quot;https://docs.djangoproject.com/en/5.0/ref/contrib/admin/actions/&quot; target=&quot;_blank&quot;&gt;https://docs.djangoproject.com/en/5.0/ref/contrib/admin/actions/&lt;/a&gt;,&lt;/li&gt;
    &lt;li id=&quot;FtKQ&quot;&gt;оптимизация запросов в Django ORM: &lt;a href=&quot;https://t.me/davidobryakov/1195&quot; target=&quot;_blank&quot;&gt;https://t.me/davidobryakov/1195&lt;/a&gt;,&lt;/li&gt;
    &lt;li id=&quot;QK3i&quot;&gt;отправка почтовых уведомлений: &lt;a href=&quot;https://docs.djangoproject.com/en/5.0/topics/email/&quot; target=&quot;_blank&quot;&gt;https://docs.djangoproject.com/en/5.0/topics/email/&lt;/a&gt;.&lt;/li&gt;
  &lt;/ul&gt;
  &lt;h3 id=&quot;CADY&quot;&gt;На уровне концепции&lt;/h3&gt;
  &lt;ul id=&quot;pXEL&quot;&gt;
    &lt;li id=&quot;h0Hx&quot;&gt;работа с админкой (кастомизация вывода, фильтры итд): &lt;a href=&quot;https://docs.djangoproject.com/en/5.0/ref/contrib/admin/&quot; target=&quot;_blank&quot;&gt;https://docs.djangoproject.com/en/5.0/ref/contrib/admin/&lt;/a&gt;,&lt;/li&gt;
    &lt;li id=&quot;gqex&quot;&gt;сигналы: &lt;a href=&quot;https://docs.djangoproject.com/en/5.0/topics/signals/&quot; target=&quot;_blank&quot;&gt;https://docs.djangoproject.com/en/5.0/topics/signals/&lt;/a&gt; (на уровне концепции)&lt;/li&gt;
  &lt;/ul&gt;
  &lt;h3 id=&quot;bEKe&quot;&gt;Будет плюсом&lt;/h3&gt;
  &lt;ul id=&quot;YoI5&quot;&gt;
    &lt;li id=&quot;9PyV&quot;&gt;важная проблема с админкой, о которой стоит знать: &lt;a href=&quot;https://stackoverflow.com/questions/16755312/django-admin-change-form-load-quite-slow&quot; target=&quot;_blank&quot;&gt;https://stackoverflow.com/questions/16755312/django-admin-change-form-load-quite-slow&lt;/a&gt;&lt;/li&gt;
  &lt;/ul&gt;
  &lt;h3 id=&quot;c1yB&quot;&gt;Полезные библиотеки и ссылки для django&lt;/h3&gt;
  &lt;ul id=&quot;PNdi&quot;&gt;
    &lt;li id=&quot;Y6NA&quot;&gt;библиотека для установки настроек CORS: &lt;a href=&quot;https://github.com/adamchainz/django-cors-headers&quot; target=&quot;_blank&quot;&gt;https://github.com/adamchainz/django-cors-headers&lt;/a&gt;,&lt;/li&gt;
    &lt;li id=&quot;2G88&quot;&gt;библиотека для авторизации и регистрации через DRF: &lt;a href=&quot;https://djoser.readthedocs.io/en/latest/&quot; target=&quot;_blank&quot;&gt;https://djoser.readthedocs.io/en/latest/&lt;/a&gt;, &lt;/li&gt;
    &lt;li id=&quot;53ib&quot;&gt;библиотека для создания динамической конфигурации приложения, хранимой в БД: &lt;a href=&quot;https://django-constance.readthedocs.io/en/latest/&quot; target=&quot;_blank&quot;&gt;https://django-constance.readthedocs.io/en/latest/&lt;/a&gt;, &lt;/li&gt;
    &lt;li id=&quot;tNqI&quot;&gt;библиотека, добавляющая хуки жизненного цикла для моделей Django: &lt;a href=&quot;https://rsinger86.github.io/django-lifecycle/&quot; target=&quot;_blank&quot;&gt;https://rsinger86.github.io/django-lifecycle/&lt;/a&gt;, &lt;/li&gt;
    &lt;li id=&quot;Xgfk&quot;&gt;библиотека, предоставляющая кастомную админку с большим количеством настроек: &lt;a href=&quot;https://django-jazzmin.readthedocs.io/&quot; target=&quot;_blank&quot;&gt;https://django-jazzmin.readthedocs.io/&lt;/a&gt;,&lt;/li&gt;
    &lt;li id=&quot;0IIb&quot;&gt;практические советы для начинающих Django-разработчиков: &lt;a href=&quot;https://t.me/davidobryakov/1184&quot; target=&quot;_blank&quot;&gt;https://t.me/davidobryakov/1184&lt;/a&gt;,&lt;/li&gt;
    &lt;li id=&quot;NMkB&quot;&gt;подборка лучших библиотек для Django: &lt;a href=&quot;https://t.me/davidobryakov/1139&quot; target=&quot;_blank&quot;&gt;https://t.me/davidobryakov/1139&lt;/a&gt;,&lt;br /&gt;курс по Django на MDN: &lt;a href=&quot;https://developer.mozilla.org/ru/docs/Learn/Server-side/Django&quot; target=&quot;_blank&quot;&gt;https://developer.mozilla.org/ru/docs/Learn/Server-side/Django&lt;/a&gt;,&lt;/li&gt;
    &lt;li id=&quot;6v5T&quot;&gt;мой шаблон для Django-проектов: &lt;a href=&quot;https://github.com/kantegory/django-template&quot; target=&quot;_blank&quot;&gt;https://github.com/kantegory/django-template&lt;/a&gt;.&lt;/li&gt;
  &lt;/ul&gt;
  &lt;h2 id=&quot;t5JI&quot;&gt;Что нужно знать/уметь в FastAPI/Flask&lt;/h2&gt;
  &lt;h3 id=&quot;JYT9&quot;&gt;Обязательно&lt;/h3&gt;
  &lt;ul id=&quot;C7b6&quot;&gt;
    &lt;li id=&quot;e3aE&quot;&gt;работа с ORM SQLAlchemy: &lt;a href=&quot;https://docs.sqlalchemy.org/en/20/intro.html&quot; target=&quot;_blank&quot;&gt;https://docs.sqlalchemy.org/en/20/intro.html&lt;/a&gt;,&lt;/li&gt;
    &lt;li id=&quot;AhXf&quot;&gt;работа с alembic: &lt;a href=&quot;https://alembic.sqlalchemy.org/en/latest/&quot; target=&quot;_blank&quot;&gt;https://alembic.sqlalchemy.org/en/latest/&lt;/a&gt;,&lt;/li&gt;
    &lt;li id=&quot;KfSu&quot;&gt;понимание async/await: &lt;a href=&quot;https://docs.python.org/3/library/asyncio-task.html&quot; target=&quot;_blank&quot;&gt;https://docs.python.org/3/library/asyncio-task.html&lt;/a&gt;.&lt;/li&gt;
  &lt;/ul&gt;
  &lt;h3 id=&quot;X6rV&quot;&gt;Опционально&lt;/h3&gt;
  &lt;ul id=&quot;UHvu&quot;&gt;
    &lt;li id=&quot;zjdz&quot;&gt;работа с TortoiseORM: &lt;a href=&quot;https://tortoise.github.io/index.html&quot; target=&quot;_blank&quot;&gt;https://tortoise.github.io/index.html&lt;/a&gt;.&lt;/li&gt;
  &lt;/ul&gt;
  &lt;h3 id=&quot;hP2S&quot;&gt;Полезные ссылки по FastAPI/Flask&lt;/h3&gt;
  &lt;ul id=&quot;f0Cp&quot;&gt;
    &lt;li id=&quot;eknN&quot;&gt;библиотека для работы с пользователями, авторизацией и регистрацией: &lt;a href=&quot;https://fastapi-users.github.io/fastapi-users/latest/&quot; target=&quot;_blank&quot;&gt;https://fastapi-users.github.io/fastapi-users/latest/&lt;/a&gt;,&lt;/li&gt;
    &lt;li id=&quot;ygWF&quot;&gt;админка, работающая с TortoiseORM: &lt;a href=&quot;https://github.com/fastapi-admin/fastapi-admin&quot; target=&quot;_blank&quot;&gt;https://github.com/fastapi-admin/fastapi-admin&lt;/a&gt;,&lt;/li&gt;
    &lt;li id=&quot;I80S&quot;&gt;админка, работающая с SQLAlchemy: &lt;a href=&quot;https://github.com/aminalaee/sqladmin&quot; target=&quot;_blank&quot;&gt;https://github.com/aminalaee/sqladmin&lt;/a&gt;,&lt;/li&gt;
    &lt;li id=&quot;dwDw&quot;&gt;библиотека, упрощающая взаимодействие с SQLAlchemy: &lt;a href=&quot;https://github.com/e-kondr01/fastapi-sqlalchemy-toolkit&quot; target=&quot;_blank&quot;&gt;https://github.com/e-kondr01/fastapi-sqlalchemy-toolkit&lt;/a&gt;.&lt;/li&gt;
  &lt;/ul&gt;
  &lt;h3 id=&quot;e0xW&quot;&gt;Общие требования&lt;/h3&gt;
  &lt;ul id=&quot;Ioz0&quot;&gt;
    &lt;li id=&quot;JdPE&quot;&gt;работа с очередями задач с помощью Celery + Redis/Celery + RabbitMQ (выполнение периодических или отложенных задач, например, отправка электронной почты),&lt;/li&gt;
    &lt;li id=&quot;RHpQ&quot;&gt;настройка общения между сервисами посредством RabbitMQ&lt;/li&gt;
  &lt;/ul&gt;
  &lt;h2 id=&quot;8QuE&quot;&gt;Инструменты&lt;/h2&gt;
  &lt;ul id=&quot;5mWj&quot;&gt;
    &lt;li id=&quot;NLd0&quot;&gt;git (тренажёр: &lt;a href=&quot;https://learngitbranching.js.org/?locale=ru_RU&quot; target=&quot;_blank&quot;&gt;https://learngitbranching.js.org/?locale=ru_RU&lt;/a&gt;),&lt;/li&gt;
    &lt;li id=&quot;d89c&quot;&gt;&lt;a href=&quot;https://docs.docker.com/guides/get-started/&quot; target=&quot;_blank&quot;&gt;docker&lt;/a&gt;, docker compose (на уровне: могу прочесть конфиг, могу запустить проект, могу написать свой простой конфиг),&lt;/li&gt;
    &lt;li id=&quot;9PRQ&quot;&gt;&lt;a href=&quot;https://docs.gunicorn.org/en/stable/&quot; target=&quot;_blank&quot;&gt;gunicorn&lt;/a&gt;/&lt;a href=&quot;https://www.uvicorn.org/&quot; target=&quot;_blank&quot;&gt;uvicorn&lt;/a&gt; (иметь представление о том что это и для чего используется),&lt;/li&gt;
    &lt;li id=&quot;aWr7&quot;&gt;nginx (понимание на уровне директив location и upstream).&lt;/li&gt;
  &lt;/ul&gt;

</content></entry><entry><id>kantegory:web-os-puter</id><link rel="alternate" type="text/html" href="https://blog.kantegory.me/web-os-puter?utm_source=teletype&amp;utm_medium=feed_atom&amp;utm_campaign=kantegory"></link><title>Браузерные ОС</title><published>2024-03-17T19:56:45.654Z</published><updated>2024-03-17T19:56:45.654Z</updated><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://img4.teletype.in/files/70/0c/700c0080-ed61-454d-af84-70c9a9cb556b.png"></media:thumbnail><category term="frontend" label="Интерфейсы и frontend"></category><summary type="html">&lt;img src=&quot;https://img1.teletype.in/files/0b/e1/0be194cb-1c27-4828-a1db-a2fa281035b7.png&quot;&gt;На одной из прошлых работ гендиректор сказал мне, что считает, что операционные системы со временем перетекут в браузер, в качестве аргумента он упомянул Electron, который является базой для большого числа современных десктоп-приложений. Я запомнил это, но не придавал значения этим словам, хотя они с каждым днём всё ближе к реальности.</summary><content type="html">
  &lt;p id=&quot;rSyd&quot;&gt;На одной из прошлых работ гендиректор сказал мне, что считает, что операционные системы со временем перетекут в браузер, в качестве аргумента он упомянул Electron, который является базой для большого числа современных десктоп-приложений. Я запомнил это, но не придавал значения этим словам, хотя они с каждым днём всё ближе к реальности.&lt;/p&gt;
  &lt;p id=&quot;L2Io&quot;&gt;Пару лет назад я &lt;a href=&quot;https://t.me/davidobryakov/1142&quot; target=&quot;_blank&quot;&gt;писал&lt;/a&gt; про &lt;a href=&quot;https://webvm.io/&quot; target=&quot;_blank&quot;&gt;WebVM&lt;/a&gt;, который представляет из себя linux без графической оболочки, работающий через &lt;a href=&quot;https://webassembly.org/&quot; target=&quot;_blank&quot;&gt;WASM&lt;/a&gt;. Пару недель назад видел несколько постов про InternetOS &lt;a href=&quot;https://github.com/HeyPuter/puter&quot; target=&quot;_blank&quot;&gt;puter&lt;/a&gt;. По сути, она представляет из себя графическую оболочку рабочего стола, работающую на веб-технологиях (js + jQuery).&lt;/p&gt;
  &lt;p id=&quot;hP34&quot;&gt;В отличие от WebVM, puter это только GUI, операции происходят на сервере (заметно, что на каждое действие, отправляется запрос в API). Документации для API-сервера пока что нет. Его исходного кода мне также не удалось найти (допускаю, что плохо искал, хотя и заглянул в каждый репозиторий). Разработчики &lt;a href=&quot;https://github.com/HeyPuter/puter/issues/54#issuecomment-1985378539&quot; target=&quot;_blank&quot;&gt;обещают&lt;/a&gt; опубликовать документацию по API-серверу к концу марта, чего я буду ждать с нетерпением.&lt;/p&gt;
  &lt;figure id=&quot;dLzM&quot; class=&quot;m_column&quot;&gt;
    &lt;img src=&quot;https://img1.teletype.in/files/0b/e1/0be194cb-1c27-4828-a1db-a2fa281035b7.png&quot; width=&quot;2560&quot; /&gt;
    &lt;figcaption&gt;Внешний вид Puter&lt;/figcaption&gt;
  &lt;/figure&gt;
  &lt;p id=&quot;9Zne&quot;&gt;Как вы думаете, есть ли будущее у браузерных операционных систем? Смогут ли они со временем стать заменой привычным десктопным оболочкам?&lt;/p&gt;
  &lt;p id=&quot;PatV&quot;&gt;Мне кажется, что определённый тренд в этом направлении есть и во многом это происходит уже сегодня, правда в облачном гейминге. Количество сервисов, предоставляющих подобную услугу, уже довольно большое, значит, это находит некий отклик среди аудитории. Думаю, в ближайшие годы мы будем наблюдать всё больше развития в этой сфере.&lt;/p&gt;

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