Tishka17 @Tishka17
Пользователь
Information
- Rating
- 1,859-th
- Location
- Москва, Москва и Московская обл., Россия
- Date of birth
- Registered
- Activity
Specialization
Backend Developer, Mobile Application Developer
Lead
Python
Docker
Linux
SQL
Git
Golang
Android SDK
Настройки - не константы. Они гузятся из внешних источников, часто не сразу в момент иницализации кода, а где-то после старта. В тестах часто мы их переопределяем, а некоторые объекты изначально работавшими с одним экземпляром настроек начинают работать с несколькими. Импртирование же в любой модуль всех настроек целиком усложняет потом поиск какие же настройки реально нужны
Всегда удивляло использование
@lru_cache
как реализацию ленивых глобалов. Я бы сильно подумал и скорее всего вместо таких фокусов взял и воспользовался Depedency Injection (просто при старте передал эти настройки куда надо)Не смотря на то, что во много я согласен с автором, некоторые выводы мне кажутся странным. Например,
Он не лукавит. В этом суть понятия "абстракция". Поинт абстрагирования не в том чтобы написать abstract перед классом, а в том чтобы скрыть неважные детали. Там где вы предлагаете проектировать API - вы буквально предлагаете заняться абстрагированием.
Выписывание интерфейса вообще не обязано добавить абстракции коду, это вы правильно подметили. Но пробелма как раз в том, что DIP не про слово abstract, а про скрытие деталей.
При этом механические отличия всё ещё остаются. Если вы "спроектировали API", но не положили интерфейс туда где он используется, в следующей версии нет никаких проблем добавить туда публичных методов, завязанных на детали, а потом начать юзать. Выделение интерфейса как раз помогает отделить API от всего остального. Расположение интерфейса рядом с БЛ - помогает следить что мы не используем какие-то детали вроде библиотеки кассандры в этом API.
Да, у этого есть цена, тут нет вопросов.
Прошу прощения, я почему-то думаю по дефолту, что люди придерживаются слоистой архитектуры с выделением соотетствующих абстракций. И тут есть варианты куда у нас направлена связь между БЛ и БД. Если слоев нет, хотелось бы конечно понимать как устроено деление приложения на компоненты.
Репозиторий - это просто один и видов "шлюзов" для доступа к БД. Я решил использовать более широкий термин потому, что не уверен что мы под репозиторием понимаем одно и то же.
Я в рамках данной дискуссии не против возврата модели алхимии, я просто пытаюсь понять кто занимается конвертациями (и сколько их), обычно это задача шлюза для доступа к внешней системе (к БД, например), чтобы бизнес логика занималась своими задачами
звучит как нарушение изоляции слоев. "Сервис" - это и есть "приложение". Мы для этого и выделяем шлюзы к БД, чтобы дальше работать с удобными типами. Иначе опять же вопрос - а зачем тогда эта "абстракция"?
Имелось ввиду, использовать форматы предназначенные для хранения конфигурации, а не для превращения в переменные окружения. Например, поддерживающих нативно иерархию или хотя бы разделы
В зависимости от проекта требования к работе с настройками могут быть разные. Часто хочется грузить их из конфига, из параметров командной строки, из конфига путь к которому в параметрах командной строке, из переменных окружения, из всего сразу или ещё как. Не для всех приложений это актуально (как я понял - не для вас), но такие требования существуют.
При этом для разных источников настроек мы хотим разное их представление. То, что в переменных окружения лежит как
MYAPP_DB_ADDR
, в файле будет параметромaddr
в секции[db]
или ещё как.На самом деле тут ситуаци такова, что чем больше у вас микросервисов, тем более унифицированы ваши подходы и тем выгоднее вам иметь меньше фич, но работающих единообразно. Но для другой компании удобным может оказаться чуточку другой набор фич.
Я не буду вдаваться в крайности и говорить, что pydantic-settings однозначно надо выкинуть (хотя хочется так говорить), но определенно пользоваться надо им очень аккуратно, а преподносится он как готовое решение. Мои рекомендации
Под каждый источник данных иметь свой класс PydanticSettings со своей настройкой алиасов и т.п. Это позволит нормально переключакться между ними.
В конечных классах использовать просто параметры инита или свои датаклассы. При инстанциировании перекладывать из пидантика в них. Это позволит менять источники данных без изменения бл и адаптеров.
Включать case_sensitive и прочие опции
Не использовать вложенные сеттинги для тех источников, которые нативно это не умеют.
При загрузке энва явно передавать путь.
Проектировать формат и структуру конфигурации, а не полагаться на библиотеку
Обычно это делает инстурмент запуска. Криминальное тут то, что если уже какой-то инструмент прочитал энвы до того как ты загрузил .env, будет несоответствие. Если так хочется юзать dotenv, то проще настроить запуск программы с помощью команды dotenv: `doetnv python app.py`. Как раз будет вам и .энв и мне переменные окружения заполненные до запуска.
А вы пробовали настроить выбор откуда грузить не на момент написания кода, а в рантайме? Например, в тестах юзать дефолты и ничего не грузить, если передан параметр --config - грузить по указанному пути и не юзать переменные окружения, а если ничего не указали - грузить .энв и переменные окружения? В общем, любую кастомную логику.
Мое мнение, что если у нас штук 5 переменных окружения - проще прочитать руками. Если сильно больше - имеет смысл подумать над использованием нормального конфига.
Давайте подумаем какие методы у нас доступны в advanced-alchemy? Почти все позволяют указывать непонятные параметры (**kwargs), то есть очень неконкретные. Что же у нас вместо этого есть в алхимии
-
session.get()
- получить по айди-
session.add()
- добавить объект-
session.delete()
- удалить объектБери и юзай. А если нужны кастомные методы типа
list
с фильтрами (а кому нужен list без фильтров непонятно) - сделай конкретную обертку над execute.Я не буду отрицать, что там есть интересные идеи, но они сделаны слишком обобщенно, что честно говоря непонятно что нам это дает.
Не надо вызывать dotenv. Переменные окружения должны быть известны к моменту старта приложения, а не модифицироваться внутри него. Они должны прилетать из окружения, а не заполняться приложением самим себе.
Если же мы рассматривает дотэнв просто как конфиг, то имеет смысл взять полноценный формат конфигов, такой как toml/yaml. Там хотя бы иерархия и стандарт есть, в то время как диалекты дотэнва разные, а файл плоский
SQL Alchemy преподносит как минимум три существенных преимущества:
Query builder. Это не часть ORM, это просто способ безопасно строить динамический SQL
Data Mapper. В отличие от Active Record мы можем маппить на доменные модели. Либо использовать модели алхимии как доменные. Во втором случае сама модель (инстанс), в целом, все ещё просто отображает данные, в БД она сам не пойдет, хотя класс и выглядит грязнее.
UoW - мы можем трекать изменения моделей, добавлять в буфер новые чтобы потом разом сохранить.
Первое (query builder) это хорошая фича для реализации слоя работы с БД, в БЛ ей не место. Второе (mapper) позволяет организовывать связь данных в БД и в БЛ. А UoW - это уже часть логики работы с данными, а именно когда они будут попадать в базу данных.
Ни одна из этих крутых фич не требует вам в бизнес логике использовать термины базы данных для построения запросов. Вы все ещё можете сделать `def find(name: str| None, price: int | None)`, а превращение этого в SQL скрыть внутри репозитория, не ломая вышеназванные фичи. А подход с GenericRepo - это как раз та сущность, что ничего толком не дает, а только загрязняет логику - мы переносим часть QueryBuilder в БЛ).
Я бы не был так строг по отношению к моделям, но вот интерфейс метода list максимально неявный, что просто лишает этот репозиторий преимуществ по сравнению с использованием голого session
Вот только pydantic settings делает очень неявные предполжения и может прочитать переменную окружения
UsEr
вместоUSER
IoC-контейнер для python
https://github.com/reagento/dishka/
Я хочу возразить. В основном гуглю то, что не требует глубины мышления - названия функций, какие-то особенности фреймворков. А глубина мышления как раз есть, она позволяет по двум словам понять о чем речь, как оно может быть устроено и выбрать на том же SO правильный в данном контексте ответ, даже если плюсики говорят другое. Мелкие детали легко находятся и постоянно меняются. А общие принципы и есть та глубина мышления, их сложно нагуглить, но они при этом прослеживаются везде.
Назвать проект venv - это жестоко. Запутать всех решили?
я взял небольшой массив размером 1000000 чисел (это же 4мб памяти, да?) что-то падает, не подскажете почему?
Попробуйте код чуть посложнее - напишите функцию поиска максимума в ФП стиле
Это хорошо, что вы не видели этот паттерн. Я видел и судя по другим комментариям, люди продолжают использовать. IoC-контейнеры же есть не у всех и не у всех скоуп синглтон называется именно так (это классическое название, но все же). Так что когда говорят "синглтон", без уточнений ожидалсь бы, что речь именно о самостоятельной сущности, а не детали реализации какой-то библиотеки (пусть даже такой большой как спринг и аналоги). Второе ожидание, что люди знакомые с IoC-контейнеоами знают и паттерн и способных их отличить. Вот вы справились, а кто-то - нет, и мне кажется виноват не автор
Что же касается книги - возраст не повод её выкидывать. Книга все ещё актуальная и рекомендуется к прочтению всем кто переходит от уровень junior дальше.