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.