Skip to content

Relative Imports#

Relative imports allow modules within a package to reference each other using . (current) and .. (parent) notation.

Prefer Absolute Imports

Relative imports can be ambiguous. We recommend absolute imports in most cases. Use relative imports only for tightly coupled internal package code.


Relative Import Syntax#

Syntax Meaning
.module Same directory
..module Parent directory
...module Grandparent directory

Same-Level Imports (.)#

Import from modules in the same directory.

relative_sibling/

relative_sibling/main.jac
import from pkg.sibling { SIBLING_VALUE, sibling_func }
import from pkg.base { BASE_VALUE }

with entry {
    print(f"Relative import - BASE_VALUE: {BASE_VALUE}");
    print(f"Relative import - SIBLING_VALUE: {SIBLING_VALUE}");
    print(f"Relative import - sibling_func(): {sibling_func()}");
}
View on GitHub

relative_sibling/pkg/base.jac
glob BASE_VALUE = "Base value";

def base_func -> str {
    return "Base function";
}
View on GitHub

relative_sibling/pkg/sibling.jac
import from .base { BASE_VALUE, base_func }

glob SIBLING_VALUE = f"Sibling uses {BASE_VALUE}";

def sibling_func -> str {
    return f"Sibling calls: {base_func()}";
}
View on GitHub
relative_sibling/
├── main.jac
└── pkg/
    ├── base.jac       # Defines BASE_VALUE, base_func
    └── sibling.jac    # Uses .base to import
Output

```

Relative import - BASE_VALUE: Base value Relative import - SIBLING_VALUE: Sibling uses Base value Relative import - sibling_func(): Sibling calls: Base function ```


Parent-Level Imports (..)#

Import from the parent directory.

relative_parent/

relative_parent/main.jac
import from project.sub.deep { DEEP_VALUE, deep_func }
import from project.config { CONFIG_VALUE }

with entry {
    print(f"Parent relative - CONFIG_VALUE: {CONFIG_VALUE}");
    print(f"Parent relative - DEEP_VALUE: {DEEP_VALUE}");
    print(f"Parent relative - deep_func(): {deep_func()}");
}
View on GitHub

relative_parent/project/config.jac
glob CONFIG_VALUE = "Project config";
glob DEBUG = True;
View on GitHub

relative_parent/project/sub/deep.jac
import from ..config { CONFIG_VALUE, DEBUG }

glob DEEP_VALUE = f"Deep module using config: {CONFIG_VALUE}";

def deep_func -> str {
    return f"Deep function, DEBUG={DEBUG}";
}
View on GitHub
relative_parent/
├── main.jac
└── project/
    ├── config.jac         # Defines CONFIG_VALUE, DEBUG
    └── sub/
        └── deep.jac       # Uses ..config to reach parent
Output

```

Parent relative - CONFIG_VALUE: Project config Parent relative - DEEP_VALUE: Deep module using config: Project config Parent relative - deep_func(): Deep function, DEBUG=True ```


Mixed Absolute and Relative#

You can combine both import styles, but prefer absolute imports for clarity.

mixed_imports/

mixed_imports/main.jac
import from library.base { BASE_ID }
import from library.extended { EXTENDED_ID, get_extended_id }
import library.base as lib_base;

with entry {
    print(f"Mixed - BASE_ID (from import): {BASE_ID}");
    print(f"Mixed - EXTENDED_ID: {EXTENDED_ID}");
    print(f"Mixed - get_extended_id(): {get_extended_id()}");
    print(f"Mixed - lib_base.BASE_ID (alias): {lib_base.BASE_ID}");
}
View on GitHub

mixed_imports/library/base.jac
glob BASE_ID = 1000;

def get_base_id -> int {
    return BASE_ID;
}
View on GitHub

mixed_imports/library/extended.jac
import from .base { BASE_ID, get_base_id }

glob EXTENDED_ID = BASE_ID + 1;

def get_extended_id -> str {
    return f"Base: {get_base_id()}, Extended: {EXTENDED_ID}";
}
View on GitHub
mixed_imports/
├── main.jac
└── library/
    ├── base.jac
    └── extended.jac
Output

```

Mixed - BASE_ID (from import): 1000 Mixed - EXTENDED_ID: 1001 Mixed - get_extended_id(): Base: 1000, Extended: 1001 Mixed - lib_base.BASE_ID (alias): 1000 ```



Relative Import Boundaries

Relative imports only work within packages. You cannot use .. to escape beyond your project's root.

Best Practice

Default to absolute imports. They're explicit and don't break when you reorganize code.