MAGISTER CATALYST DOCUMENTATION

Python and Swift bindings

The Python and Swift interfaces expose cvar, CMessenger, and CServer, plus their support types. Both languages exchange Catalyst values through CVar, preserving expressions, metadata, and the existing serialization format.

SDK setup

The macOS arm64 distribution includes the prebuilt Python package under lib/python/catalyst and the self-contained Swift source package under share/Catalyst/swift. Set PYTHONPATH to the SDK's lib/python directory, or add the Swift package as a local dependency and link the bundled native library. See the SDK setup instructions for commands and deployment paths.

The Python extension uses CPython's stable abi3 interface with a 3.9 baseline; the Swift package requires Swift 5.9 or newer. Use native arm64 toolchains and respect the minimum macOS version in distribution.json. The Python package and native libraries must retain their SDK directory relationship.

Native integration and compatibility

The C ABI is in mc/CCatalyst.h; C++ snapshot helpers are in mc/CBindings.h. C++ clients do not need a Python or Swift runtime. Both bindings use the same native value representation and messaging format. The messaging protocol uses native-endian 64-bit framing; peers must use compatible architectures and protocols.

The Python extension is specific to the SDK's operating system and architecture, even though abi3 supports multiple CPython versions. Free-threaded Python and subinterpreters are not supported. No additional binding package is needed. The Swift wrapper uses the C ABI and does not require C++ interop. Use the SDK instructions for library search paths and application deployment.

Values

CVar owns a real cvar through an opaque, reference-counted handle. It is a mutable object in both languages; copy() produces an independent value. Index reads, session reads, and native conversions are snapshots. Use explicit writes to update an original value. Individual bridge operations synchronize access; compound read/edit/write operations need application synchronization. Neither CVar nor the Swift networking wrappers promise Sendable conformance.

Catalyst Python Swift
Null None nil (NSNull is accepted on input)
Bool bool Bool
Integer Checked int Checked signed/unsigned native integer, output Int64
Float float Float/Double input, Double output
String UTF-8 str UTF-8 String
Vector list/tuple input, list output Array
Map Dictionary with string keys Dictionary with string keys
Buffer bytes/bytearray input, bytes output Data

Integers outside the signed 64-bit range are rejected. Text conversion validates UTF-8 and preserves embedded NUL bytes. Invalid UTF-8 in a restored string is reported when converting to native text. Python cyclic collections and values nested beyond 256 levels are rejected. Swift accepts native value collections; arbitrary Objective-C collection graphs and arbitrary objects are not supported.

Symbols, function expressions, sets, packed values, None, and tagged values remain CVar objects when no lossless native conversion exists. A Catalyst None value is distinct from Null. Python sets can be passed to the constructor; Swift accepts native sets and also provides CVar.set. elements() returns set elements or map keys without an ordering guarantee. symbol, function, parse, tag access, indexed argument access, and assign expose rich values explicitly. These bindings never evaluate or simplify expressions.

from catalyst import CVar

value = CVar({"threads": 8, "paths": ["a", "b"]})
value["threads"] = 16
value.set_path(["paths", 1], "c")
snapshot = value.to_native()
expression = CVar.function("Add", CVar.symbol("x"), 2)
assert expression.name == "Add"
assert expression[1] == 2
restored = CVar.restore(expression.store())
import Catalyst

let value = try CVar(["threads": 8, "paths": ["a", "b"]] as [String: Any])
try value.set("threads", to: 16)
try value.setPath([.key("paths"), .index(1)], to: "c")
let snapshot = try value.toNative()
let expression = try CVar.function("Add", arguments: [CVar.symbol("x"), 2])
let restored = try CVar.restore(expression.store())

get always returns a CVar snapshot. Python subscripting converts that snapshot to its native counterpart where possible. Swift uses throwing get and set methods. to_cson/toCSON use the existing CSON generator for textual output; store/restore preserve binary values and metadata using existing cvar serialization with buffers inlined. They are separate from messenger framing. Raw pointers, cvar::Reference values, and CObject instances require explicit bindings and are rejected at this boundary. This includes native wrapped values, callable FuncN objects, and runtime iterators; they are C++ facilities and cannot be serialized or silently converted to language-native objects.

Python's value.erase(position, count=1) and Swift's try value.erase(at: position, count: count) remove vector elements, function arguments, or string bytes through cVarEraseRange. Counts are clamped at the end; positions beyond the end and negative inputs fail. Use whole UTF-8 character boundaries when erasing bytes from text. Each edit is synchronized and leaves earlier snapshots unchanged. Map-key deletion remains available through Python del value[key] and Swift remove(key).

Native container conversion allocates and traverses the data. Passing an existing CVar to a messenger avoids conversion through native dictionaries or arrays. Sends take a value snapshot, so later edits cannot alter an already queued message. Buffer conversion currently copies; no writable borrowed memory view or zero-copy guarantee is provided.

C++ integration code can use these snapshot helpers without serializing to text or binary:

#include <mc/CBindings.h>

CVarHandle* handle = mc::cBindValue(mc::cvar(mc::cmap{{"count", 3}}));
mc::cvar value = mc::cUnbindValue(handle);
cVarRelease(handle);

Networking and delegates

CRuntime owns a running CPool (2–256 workers, default 4). Servers and messengers retain the native runtime independently of its language wrapper. A default runtime is supplied when omitted. Releasing the runtime does not close children. Final native destruction uses a cleanup thread so releasing an object inside a callback cannot attempt to join that same pool worker.

CMessenger supports connect, connected, close, send, sendNow, receive, call, reply, session snapshots/updates, and delegate registration. send queues work. sendNow waits for local transmission; both wrappers preserve its input value. call and reply require map messages and use the reserved #C/#R keys. call retains the legacy one-outstanding-call restriction and cannot be used inside that messenger's own message callback. connect, receive, call, and sendNow can block. A receive result is a CVar, including when the message holds Null; Python None or Swift nil means timeout or clean closure. Check connection state when distinguishing those outcomes. Async failures still raise errors.

CServer supports listen, close and delegate registration. The first received cvar is the authentication value. Successful admission still produces exactly {"#":"authenticated"}. A server with no admission delegate rejects clients. Accepted messengers remain alive through their application references and can outlive the server. Merely returning true without retaining the messenger does not keep an application-owned connection alive indefinitely.

Python delegates provide admit(server, messenger, auth), handle(messenger, message), and didClose(messenger) methods. A Python server can alternatively take an admit= callable. Swift exposes CServerDelegate and CMessengerDelegate protocols with throwing methods. Both support replacing a delegate. Existing C++ encryption/decryption slots remain reserved and are not exposed as functioning encryption features.

from catalyst import CServer

connections = []

def admit(server, messenger, auth):
    if auth["token"] != "example":
        return False
    connections.append(messenger)
    return True

server = CServer(admit=admit)
server.listen(7000)
# Keep the process and server alive. Eventually close server and connections.

Callbacks run synchronously on Catalyst pool threads. False from handle queues the message for receive, including edits made through its CVar wrapper. Authentication edits likewise take effect before admission completes. Retained rejected messenger wrappers are invalidated, so later operations raise an error. While admission is pending, the messenger may be operated on only from that admission callback. Application callbacks start after native ownership transfers.

Python callbacks attach to the main interpreter and acquire its GIL. Native blocking operations release it. Swift callbacks are not main-actor callbacks; move UI work explicitly to the main actor. Async event-loop wrappers and async admission are not part of this first interface. A callback's synchronous return value must be decided before it returns. Blocking callbacks can exhaust a small pool. The existing server's reverse DNS lookup can also delay initial admission.

Native errors become CError with type information (kind), a message, and a cause where available. Python also uses standard conversion errors such as TypeError, OverflowError, and UnicodeError for invalid native inputs. Callback exceptions are contained: admission failures reject the connection; message-handler failures follow the messenger's error/closure path. checkCallbackError() retrieves and clears the last retained callback failure.

Close resources explicitly (Python also supports context managers). Delegates are retained until registrations/in-flight callbacks finish; avoid ownership cycles, or break them with close. Python registers shutdown() with atexit; it stops admission, closes connections, and drains deferred releases before interpreter finalization. It is terminal for networking in that interpreter. Do not invoke global shutdown/drain from a callback. Swift applications can close resources, release wrappers, and call try CRuntime.drain() for deterministic cleanup. Native draining reports an error if attempted from a binding callback.