<?xml version="1.0" encoding="utf-8" ?><rss version="2.0" xmlns:tt="http://teletype.in/" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:media="http://search.yahoo.com/mrss/"><channel><title>David Dobryakov</title><generator>teletype.in</generator><description><![CDATA[Немного преподаю, немного управляю]]></description><image><url>https://teletype.in/files/97/fe/97fe4532-7027-4c54-a30c-612284c966d7.jpeg</url><title>David Dobryakov</title><link>https://blog.kantegory.me/</link></image><link>https://blog.kantegory.me/?utm_source=teletype&amp;utm_medium=feed_rss&amp;utm_campaign=kantegory</link><atom:link rel="self" type="application/rss+xml" href="https://teletype.in/rss/kantegory?offset=0"></atom:link><atom:link rel="next" type="application/rss+xml" href="https://teletype.in/rss/kantegory?offset=10"></atom:link><atom:link rel="search" type="application/opensearchdescription+xml" title="Teletype" href="https://teletype.in/opensearch.xml"></atom:link><pubDate>Sat, 09 May 2026 09:34:29 GMT</pubDate><lastBuildDate>Sat, 09 May 2026 09:34:29 GMT</lastBuildDate><item><guid isPermaLink="true">https://blog.kantegory.me/endpoint-optimization</guid><link>https://blog.kantegory.me/endpoint-optimization?utm_source=teletype&amp;utm_medium=feed_rss&amp;utm_campaign=kantegory</link><comments>https://blog.kantegory.me/endpoint-optimization?utm_source=teletype&amp;utm_medium=feed_rss&amp;utm_campaign=kantegory#comments</comments><dc:creator>kantegory</dc:creator><title>Оптимизация работы эндпоинта</title><pubDate>Sat, 14 Feb 2026 11:56:27 GMT</pubDate><category>Программирование</category><description><![CDATA[Часто так бывает, что новые фичи заводятся итерационным путём и сначала в формате &quot;лишь бы оно работало&quot;. Со временем, когда руки для следующей итерации высвобождаются, появляется желание подобные фичи порефакторить.]]></description><content:encoded><![CDATA[
  <p id="Uzvc">Часто так бывает, что новые фичи заводятся итерационным путём и сначала в формате &quot;лишь бы оно работало&quot;. Со временем, когда руки для следующей итерации высвобождаются, появляется желание подобные фичи порефакторить.</p>
  <p id="qgW1">Одну такую фичу я завёз на продакшн около полутора месяцев назад, а на этой неделе наконец-то дошли руки до оптимизации. </p>
  <p id="vtSq">Проект ещё только находится на пути к выходу из MVP, но уже имеет несколько активных клиентов, данные которых прилетают к нам на сервера 24/7. </p>
  <p id="og9n"><strong>Кратко о сути проблемы:</strong></p>
  <ul id="h1Sq">
    <li id="1jtf">мне надо было динамически рассчитывать статус текущего состояния некой установки, этих установок может быть несколько;</li>
    <li id="4qQk">в первой итерации статус рассчитывался через python и сохранялся в БД в момент совершения запроса на его получение.</li>
  </ul>
  <p id="f99J"><strong>Путь улучшения перфоманса выглядел так:</strong></p>
  <ul id="Sb9k">
    <li id="3KCG">я отрефакторил всё вручную и ускорил работу метода с 30 секунд до 12 секунд;</li>
    <li id="TnTG">я сходил в GPT, он отрицательно ускорил метод с 12 секунд до 20 секунд;</li>
    <li id="U6BO">я начал делать фоновый предрасчёт через celery и сократил тем самым работу метода аж до 68мс;</li>
    <li id="Ss5I">количество данных в БД увеличилось (за 3 дня насоздавалось свыше 40 тысяч записей со статусами) и работа эндпоинта замедлилась до 40 секунд;</li>
    <li id="iZW2">я начал записывать результат предрасчёта в redis и вновь вернул время работы к значению в районе ~100мс.</li>
  </ul>
  <p id="F1ni"><strong>Как можно было бы решить это иначе?</strong></p>
  <p id="WEZC">Вынести предрасчёт на уровень postgres (в другом месте этого же проекта уже есть view, которая динамически рассчитывает некоторые показатели), но поскольку накручивать триггеры в БД я не очень хочу, а сохранять новые статусы для истории и обновлённой инфографики может быть важно, было принято именно то решение, о котором я написал выше.</p>
  <p id="XFAW"><strong>Какой вывод я для себя сделал?</strong></p>
  <p id="JHgk">Задним умом я понимаю, что не просчитал основной риск: количество записей может стать довольно большим за короткий срок и это отрицательно скажется на общей производительности эндпоинта.</p>

]]></content:encoded></item><item><guid isPermaLink="true">https://blog.kantegory.me/express-autodoc</guid><link>https://blog.kantegory.me/express-autodoc?utm_source=teletype&amp;utm_medium=feed_rss&amp;utm_campaign=kantegory</link><comments>https://blog.kantegory.me/express-autodoc?utm_source=teletype&amp;utm_medium=feed_rss&amp;utm_campaign=kantegory#comments</comments><dc:creator>kantegory</dc:creator><title>Настраиваем автодокументирование для express-приложений</title><pubDate>Sat, 24 May 2025 07:11:36 GMT</pubDate><category>Программирование</category><description><![CDATA[Долгое время в express было принято использовать решения, которые довольно большую часть работы перекладывают на разработчика. Одним из самых популярных решений и по сей день является swagger-jsdoc.]]></description><content:encoded><![CDATA[
  <h2 id="zvov">Вместо предисловия</h2>
  <p id="1phR">Долгое время в express было принято использовать решения, которые довольно большую часть работы перекладывают на разработчика. Одним из самых популярных решений и по сей день является <a href="https://www.npmjs.com/package/swagger-jsdoc" target="_blank">swagger-jsdoc</a>.</p>
  <p id="OiKm">Поскольку, оно является и самым проверенным, я продолжал рассказывать о нём студентам из года в год. Но в этом году что-то пошло не так... Один из студентов спросил: &quot;а нет ли чего-то такого же удобного, как в Nest.JS?&quot;. Тут-то всё и началось.</p>
  <p id="8TZq">Я прочитал более десятка статей, пересмотрел несколько роликов в поисках оптимального решения. И вот мы здесь. Я выделил 3 решения для настройки автодокументации и подготовил сравнения и примеры для них.</p>
  <h2 id="ji23">Старый-добрый swagger-jsdoc</h2>
  <p id="FlQK">Крайне простое в использовании решение, которое требует от вас только терпения и времени. По сути, вы описываете все эндпоинты через специальные jsdoc-комментарии, по формату совпадающие с OpenAPI в YAML.</p>
  <p id="Grrf">Библиотека же ходит по вашему коду, собирает все такие комментарии и встраивает их в общий файлик OpenAPI.</p>
  <p id="jUxi">Пример кода:</p>
  <pre id="XgfF" data-lang="typescript">/**
 * @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(&#x27;/testCreate&#x27;)
  .post(exampleController.post)</pre>
  <p id="Vbnm">Плюсы у такого решения вполне очевидны, оно достаточно простое и универсальное. Подходит, даже если вы не используете typescript на бэкенде (<em>лучше всё-таки использовать...</em>). Минусы, думаю, вы и сами понимаете.</p>
  <h3 id="fR6z">Плюсы и минусы</h3>
  <p id="fog6">Плюсы:</p>
  <ul id="JQ5E">
    <li id="DKNq">простота</li>
    <li id="Qx9i">универсальность</li>
  </ul>
  <p id="H0Pe">Минусы:</p>
  <ul id="tbx2">
    <li id="yWpy">много ручной работы</li>
  </ul>
  <h3 id="TkSH">Полный код примера</h3>
  <p id="kM4k">Полный код примера доступен на <a href="https://github.com/kantegory/mentoring/tree/master/16_express_swagger_example" target="_blank">github</a>.</p>
  <h2 id="IGwH">Пишем как на Nest: routing-controllers</h2>
  <p id="s8gS">Если вы начинаете с express, но планируете перейти на Nest позднее, то routing-controllers - ваш выбор. Синтаксически это почти один-в-один совпадает с Nest.</p>
  <p id="XeAs">Пример кода:</p>
  <pre id="IH2r" data-lang="typescript">@JsonController()
export class ExampleController {
    @OpenAPI({ summary: &#x27;Test create&#x27; })
    @ResponseSchema(TestCreateResponseDto, { statusCode: 200 })
    @Post(&#x27;/testCreate&#x27;)
    post(
        @Body({ type: TestCreateDto }) body: TestCreateDto,
        @Res() response: Response,
    ): void {
        const uuid: string = randomUUID();

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

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

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

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

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

habr_all_publications = &#x27;https://habr.com/ru/rss/articles/?fl=ru&#x27;
habr_best_publications = &#x27;https://habr.com/ru/rss/articles/top/daily/?fl=ru&#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={
                    &#x27;published_at&#x27;: post.published,
                    &#x27;author&#x27;: post.author,
                    &#x27;tags&#x27;: list(map(lambda tag: tag[&#x27;term&#x27;], post.tags)),
                    &#x27;link&#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(&#x27;Length of posts:&#x27;, len(results))</pre>
  <p id="rEma">Функция <code>parse</code> выкачивает RSS-ленту по ссылке, собирает коллекцию постов и возвращает её. Функция <code>generate_feed</code> проходит по всем ссылкам, полученным на вход, формирует коллекцию публикаций, обращаясь к функции <code>parse</code>.</p>
  <h2 id="L76i">Создание БД</h2>
  <p id="xrxo">Поскольку, подход NoORM не предусматривает автоматического создания БД и заполнения БД, необходимо собрать SQL-скрипт для инициализации БД.</p>
  <p id="HKvd">Я довольно ленивый, когда дело доходит до SQL, поэтому скормил в LLM такой промпт:</p>
  <blockquote id="vVhu">&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&quot;</blockquote>
  <p id="L3tF">На выходе получил такой результат:</p>
  <pre id="Mx6U" data-lang="sql">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(&#x27;RSS&#x27;, &#x27;ATOM&#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)
);</pre>
  <p id="L4Gs">В целом, довольно неплохо, но мне надо для psql, поэтому решил перезапросить с уточнением:</p>
  <blockquote id="gUlj">&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&quot;</blockquote>
  <pre id="19vO" data-lang="sql">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 = &#x27;RSS&#x27; OR type = &#x27;ATOM&#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
);</pre>
  <p id="OEJF">Как видно, скрипт вполне неплохой получился. От себя, я добавил только <code>IF NOT EXISTS</code> к созданию каждой таблицы. А по названию полей LLM попыталась расставить типы и вполне неплохо с этим справилась. Интересно, что здесь даже предусмотрено сразу ограничение типов источника, что пока не слишком актуально, но в целом, — очень даже неплохо. Подытоживая, такого скрипта мне более, чем достаточно для MVP. </p>
  <p id="KoJ1">Теперь, когда скрипт <code>init.sql</code> готов, осталось только накидать простенький <code>docker-compose.yml</code>, чтобы запустить БД:</p>
  <pre id="00V4" data-lang="yaml">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</pre>
  <p id="3Ynj">Проверим, что всё создалось, зайдя в БД:</p>
  <pre id="fDNL" data-lang="bash">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)</pre>
  <p id="gyib">Как видим, всё на месте. Можно переходить к настройке работы через NoORM.</p>
  <h2 id="vwiT">NoORM</h2>
  <p id="Seuo">Чтобы использовать NoORM вместе с PostgreSQL, надо установить адаптор. В данный момент, я остановился на библиотеке <code>psycopg2</code>. В будущем планирую перейти на <code>asyncpg</code>, но для MVP в этом никакой необходимости нет.</p>
  <p id="CGfS">Установим зависимости (я использую именно binary-версию для psycopg2):</p>
  <pre id="3RUi" data-lang="bash">pip3 install true-noorm psycopg2-binary</pre>
  <p id="RxpV">На текущем этапе, нам достаточно будет реализовать базовый набор CRUD-функций для каждой из таблиц в виде самостоятельных функций. Начнём с таблицы пользователей:</p>
  <pre id="Vm5g" data-lang="python">from dataclasses import dataclass

import noorm.psycopg2 as nm


@dataclass
class DbUser:
    id: int
    login: str

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

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


def main():
    import psycopg2

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

main()</pre>
  <p id="WJDv">Синтаксис корректного запроса (имею ввиду, что в данном случае параметры не именованные и это неочевидный момент) я подглядел в примерах, которые лежали внутри кода библиотеки. И первое, что меня расстроило в работе с <code>NoORM</code>: в результате выполнения запроса я не получил никакой информации о только что созданной записи, только <code>None</code>. А всё-таки, хотелось бы получить, хотя бы, <code>id</code>. С другой стороны, этим автор библиотеки неявно подталкивает нас к тому, чтобы делать в такой ситуации два запроса и сохранять при этом простоту взаимодействия.</p>
  <p id="nvK8">Опишем такие же круды для остальных таблиц. Полный код проекта можно посмотреть на моём <a href="https://github.com/kantegory/podborka" target="_blank">github</a>.</p>
  <h2 id="JkS2">Точка входа</h2>
  <p id="zNAZ">Теперь, когда мы уже написали все функции для работы с БД, а также простой инструмент для парсинга RSS-лент, нам следует подружить две эти части нашего приложения между собой. Для этого я немного модифицировал файл <code>rss.py</code>:</p>
  <pre id="fHd4" data-lang="python">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={
                    &#x27;published_at&#x27;: post.published,
                    &#x27;author&#x27;: post.author,
                    &#x27;tags&#x27;: list(map(lambda tag: tag[&#x27;term&#x27;], post.tags)),
                    &#x27;link&#x27;: post.link,
                    # TODO: добавить поля
                    # &#x27;raw_content&#x27;: &#x27;-&#x27;,
                    # &#x27;mark&#x27;: &#x27;-&#x27;,
                    # &#x27;feed_id&#x27;: 1,
                    # &#x27;source_id&#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=&#x27;maindb&#x27;, user=&#x27;maindb&#x27;, password=&#x27;maindb&#x27;, host=&#x27;localhost&#x27;, port=&#x27;15432&#x27;) as conn:
        for post in posts:
            create_new_post(conn, **{ **asdict(post), &#x27;meta_info&#x27;: json.dumps(post.meta_info) })

        conn.commit()


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

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

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

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

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

sources = [habr_all_publications, habr_best_publications]

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

]]></content:encoded></item><item><guid isPermaLink="true">https://blog.kantegory.me/read-not-only-orm</guid><link>https://blog.kantegory.me/read-not-only-orm?utm_source=teletype&amp;utm_medium=feed_rss&amp;utm_campaign=kantegory</link><comments>https://blog.kantegory.me/read-not-only-orm?utm_source=teletype&amp;utm_medium=feed_rss&amp;utm_campaign=kantegory#comments</comments><dc:creator>kantegory</dc:creator><title>Что я прочитал - не только ORM</title><pubDate>Sun, 30 Jun 2024 14:29:02 GMT</pubDate><category>Программирование</category><tt:hashtag>чтояпрочитал</tt:hashtag><tt:hashtag>orm</tt:hashtag><tt:hashtag>бд</tt:hashtag><description><![CDATA[<img src="https://img4.teletype.in/files/78/38/78380773-da4c-4ae3-9e61-5d3d9e560266.png"></img>В статье автор описывает устройство ORM и основную, на его взгляд, проблему такого подхода — &quot;персистетные объекты&quot;. И предлагает альтернативу в виде кастомной библиотеки, позволяющей работать с БД без использования персистетных объектов, при этом не засоряя основной код SQL-запросами.]]></description><content:encoded><![CDATA[
  <tt-tags id="9iOF">
    <tt-tag name="чтояпрочитал">#чтояпрочитал</tt-tag>
    <tt-tag name="orm">#orm</tt-tag>
    <tt-tag name="бд">#бд</tt-tag>
  </tt-tags>
  <p id="BnMl">Ссылка на оригинал: <a href="https://habr.com/ru/articles/818761/" target="_blank">https://habr.com/ru/articles/818761/</a><br />Ссылка на гитхаб проекта: <a href="https://github.com/amaslyaev/noorm" target="_blank">https://github.com/amaslyaev/noorm</a></p>
  <h2 id="NF0I">Кратко</h2>
  <p id="qKr1">В статье автор описывает устройство ORM и основную, на его взгляд, проблему такого подхода — &quot;персистетные объекты&quot;. И предлагает альтернативу в виде кастомной библиотеки, позволяющей работать с БД без использования персистетных объектов, при этом не засоряя основной код SQL-запросами.</p>
  <figure id="fhar" class="m_original">
    <img src="https://img4.teletype.in/files/78/38/78380773-da4c-4ae3-9e61-5d3d9e560266.png" width="828" />
  </figure>
  <p id="Y3fY">Выдержка про проблему персистентных объектов:</p>
  <blockquote id="J4Ew">Примечательно здесь то, что работа с базой данных идёт через персистентные объекты, являющиеся экземплярами «модельных» классов, описывающих структуру БД. Эти персистентные объекты умеют себя прочитать из базы и в неё себя записать. Они живут внутри открытой сессии. И ещё эти объекты умеют «лениво» дотягивать из базы связанные с ними другие персистентные объекты. Эти самые персистентные объекты — корень всех проблем:<br /><br />1. По сути, это передача мутабельного объекта в другой процесс. Безобразно тупая затея. Мы запросили сущность «пользователь Вася» из базы данных в процесс своего бэкенда, и теперь где у нас теперь мастер-копия? Как мы их собираемся синхронизировать, в какой момент, и что собираемся делать с возможными коллизиями?<br /><br />2. Что случается с живущими в сессии объектами когда сессия закрывается? Что если они продолжают быть нужны в логике приложения? Что если эта логика продолжает считать, что это по-прежнему нормальные объекты, принадлежащие живой сессии?<br /><br />3. Невозможно найти единственно правильный баланс между eager- и lazy-загрузкой. Если увлекаемся lazy, получаем проблему N+1, и всё начинает страшно тормозить. Если увлекаемся eager, на каждый невинный чих ORM пытается вычитать полбазы, и тоже всё тормозит. Короче, у нас две педали, но обе они педали тормоза.</blockquote>
  <h2 id="Y3ks">Какой подход предлагает автор взамен?</h2>
  <p id="brne">Выделение отдельного модуля с логикой БД и SQL-запросами, который может выглядеть примерно так:</p>
  <pre id="wkXg" data-lang="python">from dataclasses import dataclass
import noorm.sqlite3 as nm

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

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

]]></content:encoded></item><item><guid isPermaLink="true">https://blog.kantegory.me/read-productive-habits-for-it-engineer</guid><link>https://blog.kantegory.me/read-productive-habits-for-it-engineer?utm_source=teletype&amp;utm_medium=feed_rss&amp;utm_campaign=kantegory</link><comments>https://blog.kantegory.me/read-productive-habits-for-it-engineer?utm_source=teletype&amp;utm_medium=feed_rss&amp;utm_campaign=kantegory#comments</comments><dc:creator>kantegory</dc:creator><title>Что я прочитал: &quot;какие привычки освоить it-шнику, чтобы стать продуктивнее (или здоровее)?&quot;</title><pubDate>Wed, 26 Jun 2024 18:26:56 GMT</pubDate><tt:hashtag>чтояпрочитал</tt:hashtag><tt:hashtag>продуктивность</tt:hashtag><description><![CDATA[В целом, со многими пунктами я согласен, но в статье мне не нравится, что на текст тут явно забили. Много помарок и само изложение хромает.]]></description><content:encoded><![CDATA[
  <tt-tags id="ImUP">
    <tt-tag name="чтояпрочитал">#чтояпрочитал</tt-tag>
    <tt-tag name="продуктивность">#продуктивность</tt-tag>
  </tt-tags>
  <p id="VviF">Ссылка на оригинал: <a href="https://habr.com/ru/articles/818923/" target="_blank">https://habr.com/ru/articles/818923/</a> </p>
  <p id="vy9O">Кратко (полностью скопировано из самой статьи)</p>
  <p id="GXCG">Общий список привычек на ежедневную основу</p>
  <p id="qTQl">- Пространственные упражнения: особые упражнения для формирования настроя на день.<br />- Принятие холодного душа: холодный душ / обтирания / умывания для поднятия общего тонуса и настроя на день.<br />- Планирование следующего дня: планирование задач на следующий день в конце текущего.<br />- Чтение: чтение художественной / профессиональной литературы.<br />- Стакан воды с утра: выпивание стакана воды после пробуждения для увлажнения организма и стимуляции обмена веществ.<br />- Ходьба / Бег: регулярные прогулки или бег для поддержания физической формы и энергии.<br />- Сон по расписанию: соблюдение определенного режима сна, с учетом жизненных циклов организма и мира.<br />- Медитация: медитирование для улучшения концентрации, снятия стресса и общего благополучия.</p>
  <p id="29q1">В целом, со многими пунктами я согласен, но в статье мне не нравится, что на текст тут явно забили. Много помарок и само изложение хромает.</p>
  <p id="7mhi">На мой взгляд, точно будет работать:</p>
  <p id="Y49C">- планирование следующего дня: я, к примеру, планирую все свои дела раз в неделю, а в конце дня просто сверяюсь с планом;<br />- чтение: меня пока что хватает только на ежедневное чтение разных статей и постов в тг, до книжек руки не доходят;<br />- стакан воды с утра: от воды в любом случае хуже не будет, а с утра действительно может помочь лучше проснуться;<br />- ходьба: именно ходьба, а не бег, потому что бег травмоопасен, а гулять на свежем воздухе — очень полезно;<br />- сон по расписанию: в ноябре прошлого года я уже <a href="https://t.me/davidobryakov/1181" target="_blank">писал</a> про режим, рад поделиться тем, что уже более 3 недель удерживаю прекрасный режим без особых трудностей и делаю это довольно аккуратно. Мне помогают два будильника с интервалом 1.5 часа друг от друга. Первый за 1.5 часа до момента, когда мне надо проснуться, а второй ровно в то время, когда надо проснуться. Плюсом к этому, я включил сценарий светового будильника и когда я просыпаюсь с утра, чтобы выключить второй будильник, — в комнате уже светло, помогает легко встать.</p>
  <p id="9J9x">Дополнительный пункт от меня: баланс между работой и жизнью (aka &quot;work/life balance&quot;). Я поставил себе чёткие рамки начала и окончания рабочего дня и это действительно помогает удерживаться от переработок и уменьшает энтропию.</p>
  <p id="9xzz">По остальным пунктам у меня есть сомнения. Как минимум, тот же холодный душ много кому противопоказан. Рекомендуют принимать контрастный, вместо холодного, да и то не более двух минут, чтобы не повлиять негативно на свой организм. Я пробовал принимать контрастный душ и выяснил, что летом он вообще не ощущается контрастным, а вот зимой — наоборот. В среднем, любой душ с утра положительно сказывается на настроении.</p>
  <p id="4M6A">Пространственные упражнения, как их называет автор, я лично не делаю. Зато делаю ежедневно утреннюю суставную разминку и вечернюю суставную разминку. Это во-первых позволяет снять напряжение в мышцах, а во-вторых тело не будет болеть так сильно, как могло бы, после целого дня за компьютером.</p>
  <p id="bgFh">Медитацию я не пробовал и пока не горю желанием. Жду кризиса среднего возраста. Некоторые мейнстримные привычки, типа той же медитации, меня немного отталкивают как раз-таки своей мейнстримностью.</p>

]]></content:encoded></item><item><guid isPermaLink="true">https://blog.kantegory.me/clean-code-comments</guid><link>https://blog.kantegory.me/clean-code-comments?utm_source=teletype&amp;utm_medium=feed_rss&amp;utm_campaign=kantegory</link><comments>https://blog.kantegory.me/clean-code-comments?utm_source=teletype&amp;utm_medium=feed_rss&amp;utm_campaign=kantegory#comments</comments><dc:creator>kantegory</dc:creator><title>Комментарии в коде</title><pubDate>Mon, 24 Jun 2024 18:49:33 GMT</pubDate><category>Программирование</category><tt:hashtag>дискуссия</tt:hashtag><description><![CDATA[На днях, автор канала Cross Join опубликовал пост с рассуждениями на тему того, насколько в коде нужны комментарии.]]></description><content:encoded><![CDATA[
  <tt-tags id="OtNv">
    <tt-tag name="дискуссия">#дискуссия</tt-tag>
  </tt-tags>
  <p id="q2LM">На днях, автор канала Cross Join опубликовал <a href="https://t.me/crossjoin/329" target="_blank">пост</a> с рассуждениями на тему того, насколько в коде нужны комментарии.</p>
  <p id="mK0O">Основной проблемой он считает то, что комментарии будут терять свою актуальность по мере внесения изменений в код.</p>
  <p id="qAMj">Как возможную альтернативу, он предлагает писать более подробные сообщения к коммитам, поскольку они как раз привязаны к конкретным точкам во времени.</p>
  <p id="3uex">На мой взгляд, комментарии нужны и полезны. Как минимум, следует использовать документирующие комментарии, которые в некоторых случаях (например, использование jsdoc), позволяют описать типы входных и выходных аргументов у функции, дополнить её общим описанием, что упрощает работу с вашим кодом. Но вероятнее всего, этот пример один из немногих, когда комментарии действительно полезны и необходимы.</p>
  <p id="k9bQ">Существуют разные точки зрения на комментирование кода. Автор книги &quot;Чистый код&quot;, к примеру, <a href="https://dev.to/kaww/clean-code-improve-your-code-comments-blh" target="_blank">призывает</a> писать код без комментариев, объясняя это тем, что каждый оставленный комментарий - это неудача и комментировать код стоит только в крайнем случае.</p>
  <p id="G1bN">Конечно, в идеале, код должен легко читаться и без комментариев. На мой взгляд, допустимы документирующие комментарии, а также ссылки на референсы. К примеру, если вы добавляете новую библиотеку и дописываете её конфигурацию в общий конфигурационный файл, можно сослаться на документацию этой библиотеки.</p>
  <p id="YjM4">Я же, в свою очередь, признаюсь, что очень люблю оставлять иногда комментарии даже в самых очевидных местах, потому что мне приходится на постоянной основе переключаться между несколькими проектами, а всего в голове удержать не удаётся. Поэтому скорость переключения контекста, в моём случае, повышается за счёт оставленных подсказок и напоминаний.</p>
  <p id="bw09">А как вы подходите к написанию комментариев в коде?</p>

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

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

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

    return countryLabels;
};

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

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

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

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

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

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

]]></content:encoded></item><item><guid isPermaLink="true">https://blog.kantegory.me/web-os-puter</guid><link>https://blog.kantegory.me/web-os-puter?utm_source=teletype&amp;utm_medium=feed_rss&amp;utm_campaign=kantegory</link><comments>https://blog.kantegory.me/web-os-puter?utm_source=teletype&amp;utm_medium=feed_rss&amp;utm_campaign=kantegory#comments</comments><dc:creator>kantegory</dc:creator><title>Браузерные ОС</title><pubDate>Sun, 17 Mar 2024 19:56:45 GMT</pubDate><media:content medium="image" url="https://img4.teletype.in/files/70/0c/700c0080-ed61-454d-af84-70c9a9cb556b.png"></media:content><category>Интерфейсы и frontend</category><description><![CDATA[<img src="https://img1.teletype.in/files/0b/e1/0be194cb-1c27-4828-a1db-a2fa281035b7.png"></img>На одной из прошлых работ гендиректор сказал мне, что считает, что операционные системы со временем перетекут в браузер, в качестве аргумента он упомянул Electron, который является базой для большого числа современных десктоп-приложений. Я запомнил это, но не придавал значения этим словам, хотя они с каждым днём всё ближе к реальности.]]></description><content:encoded><![CDATA[
  <p id="rSyd">На одной из прошлых работ гендиректор сказал мне, что считает, что операционные системы со временем перетекут в браузер, в качестве аргумента он упомянул Electron, который является базой для большого числа современных десктоп-приложений. Я запомнил это, но не придавал значения этим словам, хотя они с каждым днём всё ближе к реальности.</p>
  <p id="L2Io">Пару лет назад я <a href="https://t.me/davidobryakov/1142" target="_blank">писал</a> про <a href="https://webvm.io/" target="_blank">WebVM</a>, который представляет из себя linux без графической оболочки, работающий через <a href="https://webassembly.org/" target="_blank">WASM</a>. Пару недель назад видел несколько постов про InternetOS <a href="https://github.com/HeyPuter/puter" target="_blank">puter</a>. По сути, она представляет из себя графическую оболочку рабочего стола, работающую на веб-технологиях (js + jQuery).</p>
  <p id="hP34">В отличие от WebVM, puter это только GUI, операции происходят на сервере (заметно, что на каждое действие, отправляется запрос в API). Документации для API-сервера пока что нет. Его исходного кода мне также не удалось найти (допускаю, что плохо искал, хотя и заглянул в каждый репозиторий). Разработчики <a href="https://github.com/HeyPuter/puter/issues/54#issuecomment-1985378539" target="_blank">обещают</a> опубликовать документацию по API-серверу к концу марта, чего я буду ждать с нетерпением.</p>
  <figure id="dLzM" class="m_column">
    <img src="https://img1.teletype.in/files/0b/e1/0be194cb-1c27-4828-a1db-a2fa281035b7.png" width="2560" />
    <figcaption>Внешний вид Puter</figcaption>
  </figure>
  <p id="9Zne">Как вы думаете, есть ли будущее у браузерных операционных систем? Смогут ли они со временем стать заменой привычным десктопным оболочкам?</p>
  <p id="PatV">Мне кажется, что определённый тренд в этом направлении есть и во многом это происходит уже сегодня, правда в облачном гейминге. Количество сервисов, предоставляющих подобную услугу, уже довольно большое, значит, это находит некий отклик среди аудитории. Думаю, в ближайшие годы мы будем наблюдать всё больше развития в этой сфере.</p>

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