Examples

These pages embed the real, buildable projects under examples/ in the repository — the same files the CI builds — so nothing here can drift from working code.

The greeter example

examples/greeter/ is a self-contained downstream project that consumes apiary the way any library would: find_package(Apiary) plus the apiary_* CMake helpers, with no Einsums coupling. It binds a header-only C++ library — a class with a renamed Python type, two constructors, a method, a getter/setter property, and a free function — into an importable greeter extension.

1. Annotate the C++

The only apiary-specific things in the header are the APIARY_* macros from <apiary/Annotations.hpp>. They expand to nothing in a normal compile; the apiary tool reads them when it walks the AST. APIARY_EXPOSE marks what to bind, APIARY_RENAME sets the Python name, and an APIARY_GETTER / APIARY_SETTER pair becomes a single read/write property.

examples/greeter/include/greeter/Greeter.hpp
#pragma once

#include <apiary/Annotations.hpp>

#include <cctype>
#include <string>
#include <utility>

namespace greeter {

/// A friendly greeter — the obligatory binding-tool "hello world".
///
/// Demonstrates the common annotations: a renamed, non-copyable class with
/// constructors, a method, and a read/write property built from a
/// getter/setter pair.
class APIARY_EXPOSE APIARY_RENAME("Greeter") Greeter {
  public:
    /// Greet with the default word ("Hello").
    APIARY_EXPOSE Greeter() : _greeting("Hello") {}

    /// Greet with a custom word.
    APIARY_EXPOSE explicit Greeter(std::string greeting) : _greeting(std::move(greeting)) {}

    /// Return ``"<greeting>, <name>!"``.
    APIARY_EXPOSE std::string say(std::string const &name) const { return _greeting + ", " + name + "!"; }

    /// The greeting word, exposed as a read/write Python property ``greeting``.
    APIARY_GETTER("greeting") std::string const &greeting() const { return _greeting; }
    APIARY_SETTER("greeting") void set_greeting(std::string value) { _greeting = std::move(value); }

  private:
    std::string _greeting;
};

/// A free function — bound as a module-level function ``shout``.
APIARY_EXPOSE inline std::string shout(std::string const &text) {
    std::string out = text;
    for (char &c : out) {
        c = static_cast<char>(std::toupper(static_cast<unsigned char>(c)));
    }
    return out + "!";
}

} // namespace greeter

2. Supply the module body

You write only the PYBIND11_MODULE shell. apiary_aggregate_extension generates <greeter/Modules.hpp> (which declares greeter_register_all() and each module’s register function); the entry point includes it and calls the aggregator.

examples/greeter/src/module.cpp
// The PYBIND11_MODULE body. apiary_aggregate_extension() generates the
// <greeter/Modules.hpp> header (declaring greeter_register_all() + each
// module's register function); we just include it and call the aggregator.
#include <greeter/Modules.hpp>

#include <pybind11/pybind11.h>

PYBIND11_MODULE(greeter, m) {
    m.doc() = "Apiary example extension";
    greeter_register_all(m);
}

3. Wire it up in CMake

Three helpers do the work: apiary_detect_toolchain probes the compiler for the include paths libtooling needs, apiary_add_bindings generates the binding translation unit (plus a .pyi stub and docs JSON), and apiary_aggregate_extension assembles everything into one extension. Linking apiary::annotations puts the macro header on the include path.

examples/greeter/CMakeLists.txt
cmake_minimum_required(VERSION 3.20)
project(apiary_greeter_example LANGUAGES CXX)

set(CMAKE_CXX_STANDARD 20)
set(CMAKE_CXX_STANDARD_REQUIRED ON)

find_package(Apiary REQUIRED)            # apiary::apiary, apiary::annotations, apiary_* helpers
find_package(Python REQUIRED COMPONENTS Interpreter Development.Module)
find_package(pybind11 CONFIG REQUIRED)

# --- The C++ library being bound (header-only here) -----------------------
add_library(greeter INTERFACE)
target_include_directories(greeter INTERFACE "${CMAKE_CURRENT_SOURCE_DIR}/include")
# Linking apiary::annotations puts the APIARY_* macro header on the include
# path (it expands to nothing in a normal compile) AND, because
# apiary_add_bindings collects this target's usage requirements, makes the
# header resolvable when the apiary tool parses the sources.
target_link_libraries(greeter INTERFACE apiary::annotations)

# --- Apiary codegen -------------------------------------------------------
# Probe the build compiler once for the include paths libtooling needs.
apiary_detect_toolchain(CXX_STANDARD 20)

set(_gen "${CMAKE_CURRENT_BINARY_DIR}/gen")

# Generate the binding TU (+ .pyi + docs JSON) for this module's headers.
apiary_add_bindings(
    BINDING DOCS_JSON
    HEADERS           "${CMAKE_CURRENT_SOURCE_DIR}/include/greeter/Greeter.hpp"
    SOURCE_INCLUDES   greeter/Greeter.hpp
    REGISTER_FUNCTION greeter_register_core
    DEPENDS_TARGETS   greeter
    OUTPUT_DIR        "${_gen}"
    OUTPUT_NAME       greeter_core
    CXX_STANDARD      20
    OUT_BINDING _tu  OUT_STUB _stub  OUT_DOCS_JSON _docs
)

# Assemble the module(s) into one extension. MAIN supplies the
# PYBIND11_MODULE body and includes the generated register header.
apiary_aggregate_extension(
    NAME greeter_ext
    MAIN     "${CMAKE_CURRENT_SOURCE_DIR}/src/module.cpp"
    BINDINGS ${_tu}
    MODULES  core
    REGISTER_PREFIX greeter_register_
    MODULES_HEADER      "${_gen}/include/greeter/Modules.hpp"
    MODULES_INCLUDE_DIR "${_gen}/include"
    STUBS ${_stub}  STUBS_TARGET greeter_stubs
    FRAG_DIR "${_gen}"  PKG_DIR "${CMAKE_CURRENT_BINARY_DIR}"
)

# Name the extension ``greeter`` and land it where ``import greeter`` finds it
# (PYTHONPATH=build), then link the library it binds.
set_target_properties(greeter_ext PROPERTIES
    OUTPUT_NAME greeter
    LIBRARY_OUTPUT_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}"
)
target_link_libraries(greeter_ext PRIVATE greeter)

4. Use it from Python

The renamed class, the property synthesized from the getter/setter pair, and the free function are all just ordinary Python:

examples/greeter/test_greeter.py

g = greeter.Greeter()
assert g.say("world") == "Hello, world!", g.say("world")

g2 = greeter.Greeter("Hi")
assert g2.say("Apiary") == "Hi, Apiary!"

# Read/write property built from the getter/setter pair.
assert g2.greeting == "Hi"
g2.greeting = "Hey"
assert g2.say("you") == "Hey, you!"

# Free function.
assert greeter.shout("loud") == "LOUD!"

print("greeter example: OK")

Build and run

With an installed Apiary on CMAKE_PREFIX_PATH (or swap the find_package for an add_subdirectory of the apiary tree):

$ cmake -S examples/greeter -B examples/greeter/build \
    -DCMAKE_PREFIX_PATH=<apiary-install-prefix>
$ cmake --build examples/greeter/build
$ PYTHONPATH=examples/greeter/build python3 examples/greeter/test_greeter.py
greeter example: OK