Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

HH-196544 add dependency graph #658

Merged
merged 1 commit into from
Nov 17, 2023
Merged

HH-196544 add dependency graph #658

merged 1 commit into from
Nov 17, 2023

Conversation

712u3
Copy link
Contributor

@712u3 712u3 commented Nov 13, 2023

@712u3 712u3 requested a review from a team as a code owner November 13, 2023 07:52
@sintell
Copy link
Member

sintell commented Nov 13, 2023

https://github.com/hhru/frontik/blob/master/docs/preprocessors.md

Вот тут очень нехватает изменений, чтобы отражало текущую ситуацию

self.finished = False
self.task: asyncio.Task | None = None

async def run(self, handler: PageHandler, topological_sorter: TopologicalSorter) -> None:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Есть вопрос ко всех схеме по поводу её самописности, но тут коммент к конкретному месту:
имхо хендлер не надо здесь отдельный параметром делать, думаю лучше, чтобы была какая-то зависимость, сразу зарезолвленная, которая вернет CurrentHandler


if asyncio.iscoroutinefunction(self.func):
if self.task is None:
self.task = asyncio.create_task(self.func(*self.args))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

но ведь может быть и async function которая является синхронной зависимостью?
Например когда внутри есть какие-то походы, но их надо дождаться?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

тут следующей строчкой сразу же await этой таски
или ты о чем?
таска нужна только из-за субграфа (а он только из "маст_би") чтобы если один граф уже стартанул зависимость, второй не запускал новую, а авейтил существующую

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

хм, она ж тогда финишед станет и следующий граф не возьмет, это похоже надо убрать,
во время разработки какие-то проблемы были

topological_sorter.done(self)
return

for i in range(len(self.args)):
Copy link
Member

@sintell sintell Nov 13, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

а есть причина, почему тут не for arg, i in enumerate(self.args)?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

потому что модифицируем self.args в рамках цикла

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ну так ты получаешь тот же самый индекс и можешь итерироваться, просто тебе не нужно писать лишнюю строчку arg = self.args[i]

Copy link
Member

@sintell sintell Nov 15, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

я только перепутал, наоборот: for i, arg in enumerate(self.args):

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

image

return make_full_name(self.func)


def dep(dependency: Preprocessor | Callable | list[Callable]) -> Any:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

А назови функцию, чтобы было понятно что она делает, не экономь на буквах

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

это название потом в сервисы в аргументы поедет, думаешь удобно будет если оно длинное?

т.е. условно в xhh

@preprocessor
async def vacation_check(handler: PageHanler, session=make_dependency(get_session))

vs

@preprocessor
async def vacation_check(handler: PageHanler, session=dep(get_session))

arg = self.args[i]
if isinstance(arg, Dependency):
if not arg.finished:
msg = f'Graph corrupted, run {self}, before finishing {arg}'
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

А зачем эта строчечка в отдельной переменной? Там за лимит длины строки чтоли вылазим? Может надо чот подтюнить?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

это руфф поавтофиксил, https://docs.astral.sh/ruff/rules/f-string-in-exception/
типа дважды потом сообщение в логи
мне в целом не принципиально, можно вырубить

return DependencyMarker(dependency)

msg = 'Bad dependency type, only func or list[func]'
raise ValueError(msg)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Тут точно не вылазим за длинну строки, зачем тут переменная?

done, pending = await asyncio.wait(pending_tasks, return_when=asyncio.FIRST_COMPLETED)
for d in done:
if d.exception() is not None:
raise d.exception() # type: ignore
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

А почему тут тип заигнорен?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

я не смог победить mypy)
он говорит Exception must be derived from BaseException [misc]
посмотрел в некоторых либах так делают


def get_dependency_graph(page_method_func: Callable, handler_cls: type) -> DependencyGraph:
"""
build meta_graph or take ready deepcopy as main_graph
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

or take ready deepcopy это как?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

имел в виду, что у нас есть что-то типа статического объекта
my_module.MyPageHandler.get_page
и если у него нету ._dep_graph, то мы новый строим, проверяем циклы
а если уже есть то делаем deepcopy этого ._dep_graph
чтобы модифицировать, отмечать кого выполнили, кого нет

func_preprocessors = get_preprocessors(page_method_func)
handler_dependencies = getattr(handler_cls, 'dependencies', [])

analyze_dependency_params(meta_graph, root_dep, False, True)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

А давай везде, где ты булевые аргументы передаешь — передадим их как kwargs?

for dependency in chain(func_preprocessors, handler_dependencies):
register_sub_dependency(meta_graph, root_dep, dependency, False, True)

analyze_dependency_params(meta_graph, root_dep, True, False)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Давай переименуем метод, чтобы было понятно, что он не только что-то анализирует, но и меняет что-то внутри аргументов

handler_dependencies = getattr(handler_cls, 'dependencies', [])

analyze_dependency_params(meta_graph, root_dep, False, True)
for dependency in chain(func_preprocessors, handler_dependencies):
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Я правильно понимаю, что в одном цикле нельзя как раз из-за того, что нужно analize_dependency_params вызвать, которая stateful, и состояние может поменяться между вызовами?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

думаю этот вопрос связан со следующим про tier1, подумаю как там получше коммент написать

в целом зависимости в некотором смысле синглтоны, мы по имени функции можем получить объект типа Dependency. Т.е. если в графе много раз встречается какой-нибудь get_session это будет единая зависимость, которая выполнится один раз
Но бывает такое что у нас не нормальная зависимость, а "полученная из фабрики" типа такого

def preproc_factory(params):
    @preprocessor
    def real_preproc(handler):
        pass
    return real_preproc

Такая фабрика выдает разные функции с одним именем. В графе может несколько раз в качестве препроцессора/зависимости встретиться вызов фабрики. Выполнять в итоге несколько раз?
В старой схеме выполняли 1 раз если одно имя.
Поэтому сделал так что если уже регистрировали в графе что-то с конкретным именем, то следующие игнорируем. Чтобы мочь управлять какая конкретно зарегистрируется (где-то было нужно), сделал так, что то что явно указано в сигнатуре (а не в глубинах графа) имеет больший приоритет

Чтобы собрать сначала приоритетные фабрики, нужно пройтись по верхам не заходя вглубь. А полный рекурентный обход сначала в ветви идет, так что два прогона, плоский и глубокий

add link to graph
deep scan sub_dep parameters
if there are two different dependency with same name (factory's dep), then leave only first
if they both from tier1 signature raise ValueError('Dependency conflict')
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Что такое tier1 signature ?

@712u3
Copy link
Contributor Author

712u3 commented Nov 15, 2023

Была просьба добавить этот текст

Q

  1. не сложно ли будет поддерживать алгоритм
  2. добавляет проблем к статической передаче параметров внутрь (аналог фастапи)

A

  1. Уже чуток упростил в ревьюфиксах. Можно будет упростить еще, если а) избавимся от старыз препроцов б) отпадет необходимость в рантайме собирать суб_граф и мерджить с основным
  2. Уже поправил в ревьюфиксах, в графе есть special_dependencies, можно туда добавлять всякие query_reader/cookie_reader/etc

@712u3 712u3 force-pushed the HH-196544 branch 2 times, most recently from 54f34ea to 2cdc0c4 Compare November 15, 2023 15:04
Copy link

hh-sonar bot commented Nov 16, 2023

SonarQube Quality Gate

Quality Gate passed

Bug A 0 Bugs
Vulnerability A 0 Vulnerabilities
Security Hotspot A 0 Security Hotspots
Code Smell A 0 Code Smells

No Coverage information No Coverage information
No Duplication information No Duplication information

@hhrelease hhrelease merged commit 7fd2b9b into master Nov 17, 2023
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants