Skip to content

Direct Path

Direct path scheme is a form of lazy import that resolves fully-qualified dot-separated Python object references at runtime.

Operations

First, we define a module that implements available operations.

ops.py
1
2
3
4
5
6
def add(x: int, y: int) -> int:
    return x + y


def sub(x: int, y: int) -> int:
    return x - y

The code is straightforward.

ops.py
1
2
3
4
5
6
def add(x: int, y: int) -> int:
    return x + y


def sub(x: int, y: int) -> int:
    return x - y

We define function add which accepts two integers and returns a sum of them. The fully qualified path for this function will be myapp.ops.add

ops.py
1
2
3
4
5
6
def add(x: int, y: int) -> int:
    return x + y


def sub(x: int, y: int) -> int:
    return x - y

Then we define its sub counterpart. The fully qualified path for this function will be myapp.ops.sub

Application Core

First, let's define the application's core:

__main__.py
import sys
from collections.abc import Callable

from gufo.loader import ImportPathResolver

resolver = ImportPathResolver[Callable[[int, int], int]]()
OPS = {
    "add": "myapp.ops.add",
    "sub": "myapp.ops.sub",
}


def main(op: str, x: int, y: int) -> None:
    fn = resolver(OPS[op])
    r = fn(x, y)
    print(r)


main(sys.argv[1], int(sys.argv[2]), int(sys.argv[3]))

Lets explain the code:

__main__.py
import sys
from collections.abc import Callable

from gufo.loader import ImportPathResolver

resolver = ImportPathResolver[Callable[[int, int], int]]()
OPS = {
    "add": "myapp.ops.add",
    "sub": "myapp.ops.sub",
}


def main(op: str, x: int, y: int) -> None:
    fn = resolver(OPS[op])
    r = fn(x, y)
    print(r)


main(sys.argv[1], int(sys.argv[2]), int(sys.argv[3]))

Import the Python modules sys to parse the CLI argument.

Warning

We use sys.argv only for demonstration purposes. Use argsparse or alternatives in real-world applications.

__main__.py
import sys
from collections.abc import Callable

from gufo.loader import ImportPathResolver

resolver = ImportPathResolver[Callable[[int, int], int]]()
OPS = {
    "add": "myapp.ops.add",
    "sub": "myapp.ops.sub",
}


def main(op: str, x: int, y: int) -> None:
    fn = resolver(OPS[op])
    r = fn(x, y)
    print(r)


main(sys.argv[1], int(sys.argv[2]), int(sys.argv[3]))
We need a Callable type to define operations' signature.

__main__.py
import sys
from collections.abc import Callable

from gufo.loader import ImportPathResolver

resolver = ImportPathResolver[Callable[[int, int], int]]()
OPS = {
    "add": "myapp.ops.add",
    "sub": "myapp.ops.sub",
}


def main(op: str, x: int, y: int) -> None:
    fn = resolver(OPS[op])
    r = fn(x, y)
    print(r)


main(sys.argv[1], int(sys.argv[2]), int(sys.argv[3]))
We also need ImportPartResolver to perform lazy import.

__main__.py
import sys
from collections.abc import Callable

from gufo.loader import ImportPathResolver

resolver = ImportPathResolver[Callable[[int, int], int]]()
OPS = {
    "add": "myapp.ops.add",
    "sub": "myapp.ops.sub",
}


def main(op: str, x: int, y: int) -> None:
    fn = resolver(OPS[op])
    r = fn(x, y)
    print(r)


main(sys.argv[1], int(sys.argv[2]), int(sys.argv[3]))
ImportPathResolver provides a callable that resolves a fully-qualified import path into a Python object.

In this example, the resolver is expected to return a function that accepts two integer arguments and produces an integer result.

__main__.py
import sys
from collections.abc import Callable

from gufo.loader import ImportPathResolver

resolver = ImportPathResolver[Callable[[int, int], int]]()
OPS = {
    "add": "myapp.ops.add",
    "sub": "myapp.ops.sub",
}


def main(op: str, x: int, y: int) -> None:
    fn = resolver(OPS[op])
    r = fn(x, y)
    print(r)


main(sys.argv[1], int(sys.argv[2]), int(sys.argv[3]))

Next, we define the mapping between operation names and their implementations.

Note

In this example, we do not explicitly import the modules upfront. Imports are performed lazily only when a specific operation is requested.

__main__.py
import sys
from collections.abc import Callable

from gufo.loader import ImportPathResolver

resolver = ImportPathResolver[Callable[[int, int], int]]()
OPS = {
    "add": "myapp.ops.add",
    "sub": "myapp.ops.sub",
}


def main(op: str, x: int, y: int) -> None:
    fn = resolver(OPS[op])
    r = fn(x, y)
    print(r)


main(sys.argv[1], int(sys.argv[2]), int(sys.argv[3]))

Our main function accepts the operation's name and two integer arguments. Then it prints the result.

__main__.py
import sys
from collections.abc import Callable

from gufo.loader import ImportPathResolver

resolver = ImportPathResolver[Callable[[int, int], int]]()
OPS = {
    "add": "myapp.ops.add",
    "sub": "myapp.ops.sub",
}


def main(op: str, x: int, y: int) -> None:
    fn = resolver(OPS[op])
    r = fn(x, y)
    print(r)


main(sys.argv[1], int(sys.argv[2]), int(sys.argv[3]))
We resolve operation names to fully-qualified import paths using the OPS mapping, and then resolve each path into the actual function implementation.

This is the point at which the corresponding module is imported and the target callable is loaded.

Your editor must be able to infer that fn is a function that accepts two integers and returns an integer.

__main__.py
import sys
from collections.abc import Callable

from gufo.loader import ImportPathResolver

resolver = ImportPathResolver[Callable[[int, int], int]]()
OPS = {
    "add": "myapp.ops.add",
    "sub": "myapp.ops.sub",
}


def main(op: str, x: int, y: int) -> None:
    fn = resolver(OPS[op])
    r = fn(x, y)
    print(r)


main(sys.argv[1], int(sys.argv[2]), int(sys.argv[3]))

Then we call fn method of the plugin. Your editor must show the r variable has the type of int

__main__.py
import sys
from collections.abc import Callable

from gufo.loader import ImportPathResolver

resolver = ImportPathResolver[Callable[[int, int], int]]()
OPS = {
    "add": "myapp.ops.add",
    "sub": "myapp.ops.sub",
}


def main(op: str, x: int, y: int) -> None:
    fn = resolver(OPS[op])
    r = fn(x, y)
    print(r)


main(sys.argv[1], int(sys.argv[2]), int(sys.argv[3]))

Then we print the result, and our core function is finally complete.

__main__.py
import sys
from collections.abc import Callable

from gufo.loader import ImportPathResolver

resolver = ImportPathResolver[Callable[[int, int], int]]()
OPS = {
    "add": "myapp.ops.add",
    "sub": "myapp.ops.sub",
}


def main(op: str, x: int, y: int) -> None:
    fn = resolver(OPS[op])
    r = fn(x, y)
    print(r)


main(sys.argv[1], int(sys.argv[2]), int(sys.argv[3]))
We're extracting our arguments directly from sys.argv. Then we call our core function. The core is complete.