From a71e9fd42422152e6b30101e4619cb7574c5c527 Mon Sep 17 00:00:00 2001 From: Qwen Code Assistant Date: Sat, 28 Mar 2026 14:37:50 +0000 Subject: [PATCH] =?UTF-8?q?refactor:=20=D0=BB=D0=B5=D0=BD=D0=B8=D0=B2?= =?UTF-8?q?=D1=8B=D0=B9=20=D1=80=D0=B5=D0=B7=D0=BE=D0=BB=D0=B2=20=D0=BF?= =?UTF-8?q?=D1=80=D0=B8=D0=BE=D1=80=D0=B8=D1=82=D0=B5=D1=82=D0=BE=D0=B2=20?= =?UTF-8?q?=D0=B1=D0=B5=D0=B7=20=D0=B7=D0=B0=D0=BC=D0=B5=D0=BD=D1=8B=20?= =?UTF-8?q?=D0=B2=20=D1=80=D0=B5=D0=BF=D0=BE=D0=B7=D0=B8=D1=82=D0=BE=D1=80?= =?UTF-8?q?=D0=B8=D0=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Изменения: - _resolve_relative_priorities() возвращает словарь вместо замены - Приоритеты не заменяются в ConversionPoint - resolved_priorities передаётся в filter_exploded_callgraph_branch - get_aggregate_priority использует resolved_priorities если есть Преимущества: - Относительные приоритеты сохраняются в репозитории - Можно добавлять новые инжекторы после get_conversion() - Нет мутации состояния репозитория - Каждый вызов get_conversion() использует актуальные приоритеты Co-authored-by: Qwen-Coder --- src/breakshaft/convertor.py | 56 ++++++++++++++++++---------------- src/breakshaft/graph_walker.py | 18 +++++++---- uv.lock | 4 +-- 3 files changed, 44 insertions(+), 34 deletions(-) diff --git a/src/breakshaft/convertor.py b/src/breakshaft/convertor.py index 3e822e6..f3eaf1f 100644 --- a/src/breakshaft/convertor.py +++ b/src/breakshaft/convertor.py @@ -52,9 +52,9 @@ class ConvRepo: allow_sync: bool = True, force_async: bool = False ): - # Разрешаем относительные приоритеты - self._resolve_relative_priorities() - + # Разрешаем относительные приоритеты (не заменяя, а получая словарь) + resolved_priorities = self._resolve_relative_priorities() + filtered_injectors = self.filtered_injectors(allow_async, allow_sync) pipeline_callseq = [] orig_from_types = tuple(from_types) @@ -71,7 +71,13 @@ class ConvRepo: else: injects = extract_return_type(fn) - callseq = self.get_callseq(filtered_injectors, frozenset(from_types), fn, force_commutative) + callseq = self.get_callseq( + filtered_injectors, + frozenset(from_types), + fn, + force_commutative, + resolved_priorities + ) pipeline_callseq += callseq @@ -133,7 +139,8 @@ class ConvRepo: injectors: frozenset[ConversionPoint], from_types: frozenset[type], fn: Callable | Iterable[ConversionPoint] | ConversionPoint, - force_commutative: bool) -> list[ConversionPoint]: + force_commutative: bool, + resolved_priorities: Optional[dict[ConversionPoint, float]] = None) -> list[ConversionPoint]: cg = self.walker.generate_callgraph(injectors, from_types, fn) if cg is None: @@ -142,12 +149,12 @@ class ConvRepo: for inj in injectors: available_types.add(inj.injects) available_types.update(inj.requires) - + # Определяем требуемые типы required_types = set() if callable(fn): required_types = extract_func_argtypes(fn) - + raise NoConversionPath( from_types=tuple(from_types), target=fn, @@ -157,7 +164,11 @@ class ConvRepo: exploded = self.walker.explode_callgraph_branches(cg, from_types) - selected = self.walker.filter_exploded_callgraph_branch(exploded) + # Передаём resolved_priorities в filter_exploded_callgraph_branch + selected = self.walker.filter_exploded_callgraph_branch( + exploded, + resolved_priorities=resolved_priorities + ) if len(selected) == 0: raise NoConversionPath( from_types=tuple(from_types), @@ -222,8 +233,8 @@ class ConvRepo: reason="force_async=True requires allow_async=True" ) - # Разрешаем относительные приоритеты - self._resolve_relative_priorities() + # Разрешаем относительные приоритеты (не заменяя, а получая словарь) + resolved_priorities = self._resolve_relative_priorities() filtered_injectors = self.filtered_injectors(allow_async, allow_sync) @@ -231,7 +242,8 @@ class ConvRepo: filtered_injectors, frozenset(from_types), fn, - force_commutative + force_commutative, + resolved_priorities ) ret_fn = self.renderer.render(from_types, callseq, force_async=force_async, store_sources=self.store_sources) @@ -253,30 +265,22 @@ class ConvRepo: """ Разрешить относительные приоритеты и вычислить абсолютные значения. - Проходит по всем инжекторам и если есть относительные приоритеты, - вычисляет абсолютные значения на основе графа зависимостей. + Не заменяет приоритеты в репозитории, а возвращает словарь + {ConversionPoint: float_priority} для использования в graph_walker. + + Returns: + Dict[ConversionPoint, float] или None если нет относительных приоритетов """ injectors = list(self.convertor_set) # Проверяем есть ли относительные приоритеты has_relative = any(isinstance(cp.priority, RelativePriority) for cp in injectors) if not has_relative: - return + return None try: priorities = resolve_priorities(injectors) - - # Применяем разрешённые приоритеты - # Создаём новые ConversionPoint с абсолютными приоритетами - new_injectors = set() - for cp in injectors: - if cp in priorities: - new_injectors.add(cp.copy_with(priority=priorities[cp])) - else: - new_injectors.add(cp) - - # Обновляем репозиторий - self._convertor_set = new_injectors + return priorities except CycleDetectedError as e: # Переупаковываем в наше исключение diff --git a/src/breakshaft/graph_walker.py b/src/breakshaft/graph_walker.py index 5a57d53..8c8dacb 100644 --- a/src/breakshaft/graph_walker.py +++ b/src/breakshaft/graph_walker.py @@ -134,7 +134,8 @@ class GraphWalker: def filter_exploded_callgraph_branch(cls, variants: list[CallgraphVariant], priority_injectors: Optional[frozenset[ConversionPoint | Callable]] = None, - relevance_metric: Optional[Callable[[CallgraphVariant], int | float]] = None) \ + relevance_metric: Optional[Callable[[CallgraphVariant], int | float]] = None, + resolved_priorities: Optional[dict[ConversionPoint, float]] = None) \ -> list[CallgraphVariant]: if relevance_metric is None: @@ -148,7 +149,7 @@ class GraphWalker: for metric in template_metrics: if len(variants) == 1: break - new_variants = cls.filter_exploded_callgraph_branch(variants, priority_injectors, metric) + new_variants = cls.filter_exploded_callgraph_branch(variants, priority_injectors, metric, resolved_priorities) if len(new_variants) > 0: variants = new_variants @@ -156,21 +157,26 @@ class GraphWalker: if len(variants) > 1: # Вычисляем aggregate priority для каждого варианта (сумма приоритетов всех инжекторов в пути) def get_aggregate_priority(variant: CallgraphVariant) -> float: - priority = variant.injector.priority + # Используем resolved_priorities если есть, иначе берём из cp.priority + if resolved_priorities and variant.injector in resolved_priorities: + priority = resolved_priorities[variant.injector] + else: + priority = variant.injector.priority if isinstance(variant.injector.priority, (int, float)) else 0.0 + for subg in variant.subgraphs: for subv in subg.variants: priority += get_aggregate_priority(subv) return priority - + # Сортировка по aggregate priority (обратный порядок - выше приоритет = раньше) # Затем по имени функции для детерминизма variants.sort(key=lambda x: (-get_aggregate_priority(x), universal_qualname(x.injector.fn))) - + # Выбираем вариант с наивысшим aggregate приоритетом max_priority = get_aggregate_priority(variants[0]) selected = [v for v in variants if get_aggregate_priority(v) == max_priority] variants = selected - + return variants if len(variants) < 2: diff --git a/uv.lock b/uv.lock index 7f61796..ef3e1ee 100644 --- a/uv.lock +++ b/uv.lock @@ -1,10 +1,10 @@ version = 1 -revision = 2 +revision = 3 requires-python = ">=3.13" [[package]] name = "breakshaft" -version = "0.1.0.post2" +version = "0.1.6.post5" source = { editable = "." } dependencies = [ { name = "hatchling" },