Source code for typer_router.to_typer
import importlib
from typing import TYPE_CHECKING, Dict, Set
import typer
from typer_router.exc import NoParentRouteException
if TYPE_CHECKING:
from typer_router.route import Route
from typer_router.router import Router
TyperDict = Dict[str, typer.Typer]
[docs]def create_typer_app_from_router(router: "Router", **typer_kwargs) -> typer.Typer:
app = typer.Typer(**typer_kwargs)
nested_apps: TyperDict = {}
for route in router.routes:
# Handle the parents of the leaf nodes, as those will just be container typers
try:
parent = route.parent
except NoParentRouteException:
parent = None
if parent:
container_typer = nested_apps.get(parent.import_path)
if container_typer is None:
container_typer = typer.Typer(name=parent.name)
nested_apps[parent.import_path] = container_typer
# Connect the leaf node to the container
add_route_to_typer_app(route, router, container_typer)
else:
# This is a root command, add directly to main typer
add_route_to_typer_app(route, router, app)
# Now create the nested container typers to fill in the gaps.
# For example, if we have the following routes:
# - a.b.c.d
# - a.b.f.g
# - a.b.h.i.j
# Then c, d, f, g, i, and j have already been created but b and h still need to be created.
for route in router.routes:
for sub_route in route.subroutes:
path = sub_route.import_path
if path not in nested_apps:
container_typer = typer.Typer(name=sub_route.name)
nested_apps[path] = container_typer
# app.add_typer(container_typer, name=sub_route.name)
# Now one last pass to connect all the typers together.
# The leaf nodes and their containers are already connected
# But we need to connect any intermediate containers and also connect
# the top level containers to the main app.
# For example, if we have the following routes:
# - a.b.c.d
# - a.b.f.g
# - a.b.h.i.j
# - a.k
# We now need to connect b to a, k to a, and h to b.
# This will make more sense after reading the prior block of comments
already_connected: Set[str] = {route.import_path for route in router.routes}
# Start from connecting to the main app
for route in router.routes:
# Need to create from leaf inwards so that commands will exist when connecting to main app
for sub_route in reversed(route.subroutes):
# E.g. for sub_route:
# a.b.c.d we will get a.b.c, a.b, a
# a.b.h.i.j we will get a.b.h.i, a.b.h, a.b, a
# a.k we will get a
if sub_route.import_path in already_connected:
continue
app_to_connect = nested_apps[sub_route.import_path]
try:
parent_app = nested_apps[sub_route.parent.import_path]
except NoParentRouteException:
parent_app = app
parent_app.add_typer(app_to_connect, name=sub_route.name)
already_connected.add(sub_route.import_path)
return app
[docs]def add_route_to_typer_app(route: "Route", router: "Router", app: typer.Typer):
# Load the python file from the route's import path
# and get the route's function from the file
full_import_path = router.full_import_path_for(route)
module = importlib.import_module(full_import_path)
func = getattr(module, route.function_name)
app.command(route.name)(func)