# Modules and packages A `.ki` file is a module. Its top-level forms run in order when it is loaded, and it imports by Python's rules once the import hook is active: a file is a module, a directory is a package, a directory with an `__init__.ki` or `__init__.py` is a regular package, and a directory with no `__init__` is a namespace package. Dotted names, `as` aliases, and relative imports behave as they do in Python. ## The shape of a file Definitions first, then the program's work in one function marked `@entry-point`, which runs once the file has loaded as a program and not when it is imported: ```koskari """ Greet whoever is named on the command line """ import sys def greet [name]: print "hello" name @entry-point def main []: for-each [name (item-slice sys.argv 1)]: (greet name) ``` A docstring at the top becomes the module's `__doc__`. The order that reads best is imports, then constants and configuration, then functions and classes, then the entry point last; a reader can then answer what the file needs, what it defines, and what happens when it runs. The entry's return value is the exit status, and `main` stays a function another module or a test can call; see [`entry-point`](../reference/macros/compile-entry-point.md). ## Imports ```koskari import math import os.path ;; binds os import [os.path as: p] ;; binds the leaf as p import-from pathlib Path import-from functools [lru_cache as: memoize] import-all-from mypackage ;; from mypackage import * ``` A plain dotted `import os.path` binds the root package, `os`, with `os.path` reachable as an attribute, as Python does. The bracketed `[MODULE as: NAME]` form binds the leaf. `import-all-from` binds a module's public names and is allowed only at module top level. See [`import`](../reference/specials/import.md), [`import-from`](../reference/specials/import-from.md), and [`use`](../reference/specials/use.md), which imports a macro together with its runtime sibling. ## How the import hook works Importing `koskari` teaches Python's import system the `.ki` and `.kio` extensions. A file with either extension imports as an ordinary Python module: `(import-from mymod thing)` and `from mymod import thing` read the same names, and an attribute set from Koskari or from Python is seen by both. A directory with no `__init__` is a namespace package, as it is for Python, so a bare directory of `.ki` files imports with no ceremony. The hook is normally active because Koskari installs a site `*.pth` bootstrap. In environments where that is not present (editable installs, some embedded hosts), make sure something runs `import koskari` before the first `.ki` import. A package `__init__.py` containing `import koskari` is the common insurance. ## The one rule, plus the `.ki` extension Everything below is Python's import model with `.ki`/`.kio` added as recognized source extensions: | On disk | Imports as | Kind | |---|---|---| | `foo.ki` | `foo` | single module | | `pkg/__init__.ki` (or `.py`) + `pkg/foo.ki` | `pkg`, `pkg.foo` | regular package | | `pkg/foo.ki` with **no** `pkg/__init__.*` | `pkg.foo` | namespace package | | `foo.kio` | `foo` | precompiled module | `.ki` and `.kio` modules import only from directories on `sys.path`, not from zip archives. ## A regular package: `__init__.ki` Give a directory an `__init__.ki` and it is a regular package. The init runs when the package is first imported and typically re-exports the submodules' public names using relative imports (a leading dot is "this package"): ```text kosrunner/ __init__.ki job.ki server.ki store.ki ``` ```koskari ;; kosrunner/__init__.ki import-from .job Config Job JobSet import-from .server HttpServer get-server import-from .store StorageConfig RunStore ``` `examples/scheduler/kosrunner/` is a full working example of this shape (a pure-`.ki` package whose `__init__.ki` gathers a dozen submodules). A `.py` `__init__` works equally well for a Koskari package; use `__init__.ki` when the init logic is itself Koskari. ## A namespace package: no `__init__` Leave the `__init__` out and the directory is an implicit namespace package. Its `.ki` submodules still import as `pkg.submod`; there is no package-level init and no re-exports: ```text ki/ loop.ki nbody.ki ``` ```koskari (import-from ki nbody) ;; loads ki/nbody.ki (import ki.loop) ``` This is the lightest layout: drop `.ki` files in a directory and import them as submodules, no boilerplate file required. ## A mixed package: `__init__.py` with `.ki` submodules A Python `__init__.py` alongside `.ki` submodules is a regular package too. This is the recommended shape when you want the `import koskari` insurance in the init: ```text koslife/ __init__.py ;; just: import koskari life.ki curse.ki cli.ki ``` Relative imports work across the `.ki` submodules regardless of whether the init is `.py` or `.ki`: ```koskari ;; koslife/curse.ki (import-from .life LifeBoard) ``` `examples/life/koslife/` is a working example. Deeper relative levels follow Python's dot rules (each leading dot climbs one package); see [`import`](../reference/specials/import.md) and [`import-from`](../reference/specials/import-from.md). ## The `.py` / `.ki` same-name trap A `foo.py` and a `foo.ki` in the same directory both claim the module name `foo`. The resolution follows Python's rules: builtin and frozen modules come first, then `sys.path` entries in order, and within one directory Python's own suffixes (an extension module, `.py`, `.pyc`) are tried before `.kio` and then `.ki`. So the `.py` wins here, as a `.py` anywhere earlier on `sys.path` would. A layout that leans on that ordering is hard to read; do not rely on it. Two ways to keep two-language layouts unambiguous: - Distinct base names. Differentiate before the extension: `foo_py.py` and `foo_ki.ki`, never `foo.py` + `foo.ki`. - Separate packages. Put each language in its own directory so the package name disambiguates, and the base names can match: ```text py/ nbody.py ki/ nbody.ki ``` ```koskari (import-from py nbody) ;; py/nbody.py (import-from ki nbody) ;; ki/nbody.ki ``` `py/` and `ki/` are two separate top-level namespace packages (no `__init__` needed), so `py.nbody` and `ki.nbody` are distinct fully-qualified names with no collision. This is the clean way to hold a Python variant and a Koskari variant of the same thing side by side. ## Getting a directory onto the import path Import resolution searches `sys.path`. The usual ways a directory lands there: - Running a script. `python path/to/app.py` prepends the script's own directory to `sys.path[0]`, so files next to `app.py` import by bare name. Note the interaction with `PYTHONSAFEPATH` (equivalently `python -P`): when set, it disables that automatic prepend. If a runner sets `PYTHONSAFEPATH=1` globally, clear it (`PYTHONSAFEPATH=`) for the target that needs bare sibling imports, or put the directory on `PYTHONPATH` explicitly. - An installed package. `pip install .` puts your declared packages on the path for good. For shipping an installed command, see [Packaging an installed command](packaging.md).