Skip to content

Context Interface

pykx.ctx

The context interface has been temporarily disabled for pykx.QConnection instances.

Due to the discovery of issues with the use of the context interface over IPC, it has been temporarily disabled to prevent misleading behaviour and unexpected errors from occuring. It will be re-enabled in a future version of PyKX.

Interface to q contexts and scripts which define a namespace.

The context interface provides an easy to way access q contexts (also known as namespaces when at the top level). For more information about contexts/namespaces in q please refer to Chapter 12 of Q for Mortals.

Both the local q instance at pykx.q, and pykx.Connection instances, have attributes for q namespaces, which are exposed in Python as pykx.QContext objects. These context objects have attributes for their members, which can either be sub-contexts or K objects. For example:

  • pykx.q.Q is a KdbContext instance for the builtin .Q context/namespace
  • pykx.q.ctxA.ctxB is a KdbContext instance for the .ctxA.ctxB context
  • pykx.q.ctxA.ctxB.kObject is a pykx.K instance for the .ctxA.ctxB.kObject K object

Just as in q, the .q context is accessible at the top-level, so for instance instead of accessing pykx.q.q.md5, you can access it as pykx.q.md5. Some q builtins cannot be accessed like this such as or and not as these result in Python syntax errors. Such functions can instead be accessed either with getattr, or by evaluating them as q code (e.g. pykx.q('not')).

Accessing attributes of pykx.q (or a pykx.ipc.Connection instance) which do not correspond to a context that has been loaded in memory in q will result in it trying to find a script with a matching name. This process is detailed in the flowchart below:

graph LR
  A([`pykx.q.ctxA`<br>is accessed.]);
  B{Is `.ctxA` <br>defined in<br>memory in q?};
  C{Is `.ctxA` <br>defined in<br>memory in q?};
  D["Search for a<br>matching script<br>(details below)."];
  E{Has a<br>matching script<br>been found?};
  F([`AttributeError`<br>is raised.]);
  G([`QContext`<br>is returned.]);
  H["Switch to the<br>context `.ctxA`<br>(`system &quot;d .ctxA&quot;`),
    <br>execute the script,<br>then switch back."];

  A --> B;
  B --No--> D;
  B --Yes--> G;
  C --No--> F;
  C --Yes--> G;
  E --No--> F;
  E --Yes--> H;
  D --> E;
  H --> C;

The fact that the context might not be defined even after the context interface changes the context and executes the script might be confusing. This can happen because the script can switch into other contexts, which overrides the context switch done by the context interface. Additionally the script might use fully qualified names for its definitions, which can bypass the effect of switching contexts.

Note that context switches persists across pykx.q calls (but not pykx.QConnection(...) calls). One should take care when switching contexts, as unexpectedly being in an different context can result in undesirable behaviour. QContext objects are Python context managers, which means they can be used with the with statement like so:

# q code here executes in the global context
with q.myctx:
    # q code here executes in the `.myctx` context
    pass
# q code here executes in the global context

If you would like to switch into a q context using a string for the context name, use getattr like so:

# q code here executes in the global context
with getattr(q, 'myctx'):
    # q code here executes in the `.myctx` context
    pass
# q code here executes in the global context

Script Search Logic

When the context interface cannot find a namespace (i.e. a top-level context) that is being accessed it attempts to find a q/k script that has a matching name. This process is done via a depth first search of a tree where each node corresponds to part of the path, and each leaf corresponds to a possible file. Only the first file found that exists is executed. If none of the files exist then an AttributeError is raised.

The layers of the tree are as follows:

  • Each of the paths in pykx.q.paths/pykx.ipc.Connection(...).paths (which defaults to pykx.ctx.default_paths)
  • . prefix or not
  • The name of the attribute accessed (i.e. pykx.q.script -> script)
  • .q or .k
  • No trailing _ or a trailing _ (n.b. why a q/k script path would end with an underscore)

So for example if pykx.q.script was accessed, the context .script was not defined in memory in q, and paths was set to ['.', pykx.QHOME] (where pykx.QHOME == pathlib.Path('/opt/kdb')), then the following paths would be checked in order until one is found to exist, or they have all been checked:

  1. ./.script.q
  2. ./.script.q_
  3. ./.script.k
  4. ./.script.k_
  5. ./script.q
  6. ./script.q_
  7. ./script.k
  8. ./script.k_
  9. /opt/kdb/.script.q
  10. /opt/kdb/.script.q_
  11. /opt/kdb/.script.k
  12. /opt/kdb/.script.k_
  13. /opt/kdb/script.q
  14. /opt/kdb/script.q_
  15. /opt/kdb/script.k
  16. /opt/kdb/script.k_

Best Practices

To take full advantage of the automatic script loading one should ensure that every q/k script defines at most one public context. Ideally every q/k script should define exactly one context, and the name of the context should be equivalent to the name of the file without the file extension. For instance, script.q should place its definitions within the .script namespace. This ensures that when the context interface executes a script to load a context, it doesn't load in more contexts than intended. Furthermore the context name matching the file name ensures that when that file is executed because its name matches the desired context, that context will actually be defined.

When these best practices cannot be followed it may be impossible to use the automatic loading of scripts via the context interface. In that case we can resort to manually loading scripts either by executing the q code system "l <path to script>", or by calling pykx.q._register with the path to the script.

When switching contexts within a script, one should always save the context they were in prior to their context switch, and then switch back into it afterwards, rather than explicitly switching into the global context.

Execution Contexts for Functions

Functions returned by the context interface are provided as pykx.SymbolicFunction instances.

These objects are symbol atoms whose symbol is a named function (with a fully-qualified name). They can be called like regular pykx.Function objects, but unlike regular pykx.Function objects, they will execute in the pykx.Q instance (also known as its "execution context") in which it was defined.

Refer to the IPC docs around execution contexts for more information.

CurrentDirectory

CurrentDirectory()

Bases: type(Path())

pathlib.Path instance for the current directory regardless of directory changes.

QContext

QContext(q, name, parent)

Interface to a q context.

Members of the context be accessed as if the QContext object was a dictionary, or by dotting off of the QContext object.

q: The q instance in which the context exists.
name: The name of the context.
parent: The parent context as a `QContext`, or `None` in the case of the global context.


_invalidate_cache

_invalidate_cache()

Clears the cached context, forcing it to be reloaded the next time it is accessed.

ZContext

ZContext(global_context)

Bases: QContext

Special interface to handle the .z context.

The .z context in q is not a normal context; it lacks a dictionary. To access it one must access its attributes directly.

_fully_qualified_name

_fully_qualified_name(name, parent)

Constructs the fully qualified name of a context given its name and parent.