ignore_basictypes_return for a ConversionPoint.from_fn
breakshaft
Генерация преобразований типов на лету
Зачем это нужно:
Базовая задача библиотеки - применение методов внедрения зависимостей типа ()->SomeObject к потребителям
(*,dep_n:SomeObject,*)->*
Однако, кроме прямых применений внедрения зависимостей, библиотека позволяет выстраивать цепочки преобразований
(Any)->A | (A)->B | (B)->C | consumer(A,B,C)
Особенности библиотеки:
- Выстраивает индивидуальный граф всех возможных преобразований для каждого потребителя
- Генерирует метод подготовки зависимостей по запросу
- Поддерживает асинхронный контекст
- Поддерживает внедрение зависимости через синхронные/асинхронные менеджеры контекста
- Поддерживает
Union-типы в зависимостях - Учитывает default-параметры
- Позволяет выстраивать конвейеры преобразований
- Опционально разворачивает кортежи в возвращаемых значениях
Ограничения библиотеки:
- Выбор графа преобразований вызывает комбинаторный взрыв
- Кэширование графов преобразований не поддерживается
- При некоммутативности сгенерированного графа, имеется опасность неконсистентного выбора пути, поскольку порядок обхода методов, а также графа, не гарантирован
- Обращение к методам преобразования в сгенерированном методе происходит посредством словаря ссылок - ожидается изменение поведения
- Поддерживается только in-time генерация методов
Базовое применение:
from dataclasses import dataclass
from breakshaft.convertor import ConvRepo
# Объявляем объекты, с которыми будем работать
@dataclass
class A:
a: int
@dataclass
class B:
b: float
repo = ConvRepo()
# Добавляем в репозиторий все преобразования типов, которые можно использовать
@repo.mark_injector()
def b_to_a(b: B) -> A:
return A(int(b.b))
@repo.mark_injector()
def a_to_b(a: A) -> B:
return B(float(a.a))
@repo.mark_injector()
def int_to_a(i: int) -> A:
return A(i)
@repo.mark_injector()
def int_to_b(i: int) -> B:
return B(float(i))
@repo.mark_injector()
def a_to_int(a: A) -> int:
return a.a
@repo.mark_injector()
def b_to_int(b: B) -> int:
return int(b.b)
# Целевая функция
def consumer(dep: A) -> int:
return dep.a
fn = repo.get_conversion(
(B,), # Аргументы генерируемого метода
consumer, # Целевая функция, которая будет вызвана
# Параметры построения графа преобразований
force_commutative=True,
# Выбрасывать ошибку если невозможно выделить эксклюзивную последовательность преобразований (default=True)
allow_async=False, # Запретить использовать асинхронные преобразования (default=True)
allow_sync=True, # Разрешить использовать синхронные преобразования (default=True)
# Параметры генерации итогового метода
force_async=False, # Не требовать генерировать асинхронный метод (default=False)
)
tst = fn(B(1.1))
assert tst == 1
Сборка конвейеров преобразований:
Пусть, имеется несколько методов-потребителей, которые необходимо вызывать последовательно:
from breakshaft.convertor import ConvRepo
repo = ConvRepo()
# Объявляем A и B, а также методы преобразований - как в прошлом примере
type cons2ret = str # избегаем использования builtin-типов, чтобы избежать простых коллизий
def consumer1(dep: A) -> B:
return B(float(42))
def consumer2(dep: B) -> cons2ret:
return str(dep.b)
def consumer3(dep: cons2ret) -> int:
return int(float(dep))
pipeline = repo.create_pipeline(
(B,),
[consumer1, consumer2, consumer3],
force_commutative=True,
allow_sync=True,
allow_async=False,
force_async=False
)
dat = pipeline(B(42))
assert dat == 42
Как получить граф преобразований:
from breakshaft.graph_walker import GraphWalker
from breakshaft.convertor import ConvRepo
from breakshaft import util_mermaid # методы для отрисовки графов в mermaid flowchart
repo = ConvRepo()
# Объявляем A и B, а также методы преобразований - как в прошлом примере
walker = GraphWalker()
from_types = (int,)
g = walker.generate_callgraph(
repo.convertor_set, # множество методов преобразований
frozenset(from_types), # все "внутренние" механизмы используют frozenset
consumer, # "целевой" метод
)
Граф с выбором путей
util_mermaid.draw_callgraph_mermaid(
g, # граф преобразований
split_duplicates=True # не "склеивать" дублирующиеся хвосты ветвей
)
flowchart TD
%%defs:
4637413733674907887["3"]
_2014196705430320452("\(\< class \'\_\_main\_\_.A\'\> \) \-\> NoneType: consumer \[0\]")
0_0_26854043587429446["branch select \[1\] \[2\]"]
0_0__7470993180194732607("\(\< class \'\_\_main\_\_.B\'\> \) \-\> A: b\_to\_a \[0\]")
0_0_0_0_7892409774476285690["1"]
0_0_0_0__2352457504263425844("\(\< class \'int\'\> \) \-\> B: int\_to\_b \[1\]")
0_0_0_0_0_0_133146708735736(((0)))
0_0_7688981453858411382("\(\< class \'int\'\> \) \-\> A: int\_to\_a \[1\]")
1_0_0_0_133146708735736(((0)))
%%edges:
head(((head))) --> 4637413733674907887
0_0__7470993180194732607 ---> 0_0_0_0_7892409774476285690
0_0_26854043587429446 -.-> 0_0_7688981453858411382
_2014196705430320452 ---> 0_0_26854043587429446
0_0_7688981453858411382 ---> 1_0_0_0_133146708735736
0_0_0_0_7892409774476285690 -.-> 0_0_0_0__2352457504263425844
0_0_0_0__2352457504263425844 ---> 0_0_0_0_0_0_133146708735736
4637413733674907887 -.-> _2014196705430320452
0_0_26854043587429446 -.-> 0_0__7470993180194732607
Далее, выделяется множество всех комбинаций выборов путей
Warning
В этом месте происходит комбинаторный взрыв
exploded = walker.explode_callgraph_branches(g, frozenset(from_types))
print('flowchart TD')
for s_i, selected in enumerate(exploded):
print(
util_mermaid.draw_callgraph_mermaid(
# explode возвращает множество вариантов, а отрисовка принимает только целиковый граф
Callgraph(frozenset({selected})),
split_duplicates=True,
# Не печатать `flowchart TD`
skip_title=True,
# каждому подграфу будет добавляться префикс, необходимо для "разделения" отрисовки по ветвям
prefix=f'{s_i}_'
)
)
flowchart TD
%%defs:
0_6479277657578663668["3"]
0_5230748712811156587("\(\< class \'\_\_main\_\_.A\'\> \) \-\> NoneType: consumer \[1\]")
0_0_0_3895278563721661331["2"]
0_0_0__7339131078346638087("\(\< class \'\_\_main\_\_.B\'\> \) \-\> A: b\_to\_a \[1\]")
0_0_0_0_0__8039019063582738327["1"]
0_0_0_0_0__4308564652945379484("\(\< class \'int\'\> \) \-\> B: int\_to\_b \[1\]")
%%edges:
head(((head))) --> 0_6479277657578663668
0_0_0_3895278563721661331 -.-> 0_0_0__7339131078346638087
0_0_0_0_0__8039019063582738327 -.-> 0_0_0_0_0__4308564652945379484
0_5230748712811156587 ---> 0_0_0_3895278563721661331
0_0_0__7339131078346638087 ---> 0_0_0_0_0__8039019063582738327
0_6479277657578663668 -.-> 0_5230748712811156587
%%defs:
1__220022767842101243["2"]
1__4215159630864764736("\(\< class \'\_\_main\_\_.A\'\> \) \-\> NoneType: consumer \[1\]")
0_0_1_3788926318971690339["1"]
0_0_1_5732874305176457742("\(\< class \'int\'\> \) \-\> A: int\_to\_a \[1\]")
%%edges:
head(((head))) --> 1__220022767842101243
1__220022767842101243 -.-> 1__4215159630864764736
1__4215159630864764736 ---> 0_0_1_3788926318971690339
0_0_1_3788926318971690339 -.-> 0_0_1_5732874305176457742
Далее, из всех вариантов отбрасываются те, в которых меньше всего на пути преобразования используются исходные объекты, а из них отбрасываются самые длинные пути
Warning
На этом этапе можно наткнуться на некоммутативность, а также неконсистентность выбора пути преобразований в случае если в результате фильтрации получилось больше одного варианта
filtered = walker.filter_exploded_callgraph_branch(exploded)
print('flowchart TD')
for s_i, selected in enumerate(exploded):
print(
util_mermaid.draw_callgraph_mermaid(
Callgraph(frozenset({selected})),
split_duplicates=True,
skip_title=True,
prefix=f'{s_i}_'
)
)
flowchart TD
%%defs:
0_6479277657578663668["3"]
0_5230748712811156587("\(\< class \'\_\_main\_\_.A\'\> \) \-\> NoneType: consumer \[1\]")
0_0_0_3895278563721661331["2"]
0_0_0__7339131078346638087("\(\< class \'\_\_main\_\_.B\'\> \) \-\> A: b\_to\_a \[1\]")
0_0_0_0_0__8039019063582738327["1"]
0_0_0_0_0__4308564652945379484("\(\< class \'int\'\> \) \-\> B: int\_to\_b \[1\]")
%%edges:
head(((head))) --> 0_6479277657578663668
0_0_0_3895278563721661331 -.-> 0_0_0__7339131078346638087
0_0_0_0_0__8039019063582738327 -.-> 0_0_0_0_0__4308564652945379484
0_5230748712811156587 ---> 0_0_0_3895278563721661331
0_0_0__7339131078346638087 ---> 0_0_0_0_0__8039019063582738327
0_6479277657578663668 -.-> 0_5230748712811156587