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:
"""
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.
Imports
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,
import-from, and
use, 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 |
|---|---|---|
|
|
single module |
|
|
regular package |
|
|
namespace package |
|
|
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”):
kosrunner/
__init__.ki
job.ki
server.ki
store.ki
;; 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:
ki/
loop.ki
nbody.ki
(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:
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:
;; 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 and import-from.
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.pyandfoo_ki.ki, neverfoo.py+foo.ki.Separate packages. Put each language in its own directory so the package name disambiguates, and the base names can match:
py/ nbody.py ki/ nbody.ki
(import-from py nbody) ;; py/nbody.py (import-from ki nbody) ;; ki/nbody.ki
py/andki/are two separate top-level namespace packages (no__init__needed), sopy.nbodyandki.nbodyare 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.pyprepends the script’s own directory tosys.path[0], so files next toapp.pyimport by bare name. Note the interaction withPYTHONSAFEPATH(equivalentlypython -P): when set, it disables that automatic prepend. If a runner setsPYTHONSAFEPATH=1globally, clear it (PYTHONSAFEPATH=) for the target that needs bare sibling imports, or put the directory onPYTHONPATHexplicitly.An installed package.
pip install .puts your declared packages on the path for good. For shipping an installed command, see Packaging an installed command.