From 7d15ca031c3ba60ad50bc6febdce0a7fb2c8386e Mon Sep 17 00:00:00 2001 From: kimstik Date: Tue, 15 Sep 2026 22:59:50 +0200 Subject: [PATCH 1/2] Create nested enums and exception types with the classes A class is then complete once its package's phase 1 ran; phase 2 only adds members. --- bindgen/template_sub.j2 | 19 ------------------- bindgen/template_sub_pre.j2 | 23 +++++++++++++++++++++++ 2 files changed, 23 insertions(+), 19 deletions(-) diff --git a/bindgen/template_sub.j2 b/bindgen/template_sub.j2 index f17dcac..cb34c1b 100644 --- a/bindgen/template_sub.j2 +++ b/bindgen/template_sub.j2 @@ -89,20 +89,6 @@ auto {{ns}} = static_cast(m.attr("{{ns}}")); register_default_constructor<{{c.name}} {{pointer(c)}}>({{mod}},"{{cls_name(c)}}"); {% endif %} - // nested enums - {% for enum in c.enums %} - {% if enum.anonymous %} - {% for val in enum.values %} - klass.attr("{{ val | replace("None", "None_")}}") = py::cast(int({{ enum.name+"::" if enum.name}}{{ val }})); - {% endfor %} - {% else %} - py::enum_<{{enum.name}}>(klass, "{{ enum.name.split('::')[-1] + '_e' }}", R"#({{enum.comment}})#") - {% for val in enum.values %} - .value("{{val | replace("None", "None_")}}", {{enum.name}}::{{val}}){{ ";" if loop.last }} - {% endfor %} - {% endif %} - {% endfor %} - static_cast>(klass) // constructors {% for i,con in enumerate(c.constructors) %} @@ -277,11 +263,6 @@ auto m{{ns}} = static_cast(m.attr("{{ns}}")); {% endfor %} -// exceptions -{% for ex in module.exceptions %} -register_occ_exception<{{ex.name}}>(m, "{{ex.name}}"); -{% endfor %} - // user-defined post-inclusion per module in the body {% if module_settings['include_body_post'] %} {{ module_settings['include_body_post'] }} diff --git a/bindgen/template_sub_pre.j2 b/bindgen/template_sub_pre.j2 index 129af22..75e80c6 100644 --- a/bindgen/template_sub_pre.j2 +++ b/bindgen/template_sub_pre.j2 @@ -62,6 +62,7 @@ void register_{{module.name}}_enums(py::module &main_module) { py::module m = main_module.def_submodule("{{module.name}}", R"#({{module.doc}})#"); +py::object klass; // add namespaces as submodules {% for ns in module.namespaces %} @@ -118,6 +119,28 @@ auto {{ns}} = m.def_submodule("{{ns}}"); preregister_template_{{spec.base}}<{{spec.args|join(',')}}>(m, "{{name}}"); {% endfor %} +// nested enums +{% for c in module.classes %}{% if c.enums and ((proper_new_operator(c) and proper_delete_operator(c)) or 'Standard_Transient' in c.rootclass) %} + klass = {{ "m" if not c.namespaces else c.namespaces[0] }}.attr("{{cls_name(c)}}"); + {% for enum in c.enums %} + {% if enum.anonymous %} + {% for val in enum.values %} + klass.attr("{{ val | replace("None", "None_")}}") = py::cast(int({{ enum.name+"::" if enum.name}}{{ val }})); + {% endfor %} + {% else %} + py::enum_<{{enum.name}}>(klass, "{{ enum.name.split('::')[-1] + '_e' }}", R"#({{enum.comment}})#") + {% for val in enum.values %} + .value("{{val | replace("None", "None_")}}", {{enum.name}}::{{val}}){{ ";" if loop.last }} + {% endfor %} + {% endif %} + {% endfor %} +{% endif %}{% endfor %} + +// exceptions +{% for ex in module.exceptions %} +register_occ_exception<{{ex.name}}>(m, "{{ex.name}}"); +{% endfor %} + // user-defined post-inclusion per module in the body {% if module_settings['preregister_include_body_post'] %} {{ module_settings['preregister_include_body_post'] }} From f8716be429de95a8222f17d6093e262d48c1212c Mon Sep 17 00:00:00 2001 From: kimstik Date: Wed, 16 Sep 2026 15:00:24 +0200 Subject: [PATCH 2/2] Register package members on first use when lazy is set --- bindgen/__init__.py | 40 +++++++++++++++++++++++++-------- bindgen/schemas.py | 2 ++ bindgen/template_main.j2 | 48 ++++++++++++++++++++++++++++++---------- 3 files changed, 69 insertions(+), 21 deletions(-) diff --git a/bindgen/__init__.py b/bindgen/__init__.py index eb8da3b..68a5bb3 100644 --- a/bindgen/__init__.py +++ b/bindgen/__init__.py @@ -575,7 +575,7 @@ def _filter_module(m): ) -def toposort_modules(modules, module_settings): +def module_deps(modules, module_settings): deps = {} @@ -599,14 +599,30 @@ def toposort_modules(modules, module_settings): ) # iterate over all classes, templates and typedefs - deps[m.name] = set( - cls_dict[s] - for c in m.classes + m.class_templates + typedefs - for s in c.superclass - if s in cls_dict - ) | custom_deps - {m.name} + deps[m.name] = ( + set( + cls_dict[s] + for c in m.classes + m.class_templates + typedefs + for s in c.superclass + if s in cls_dict + ) + | custom_deps + ) - {m.name} - return toposort_flatten(deps) + return deps + + +def collections_deps(modules, collection_pat): + # owners of the superclasses of the collection class templates + cls_dict = {c.name: m.name for m in modules for c in m.classes} + templates = [ + t + for m in modules + for t in m.class_templates + if t.name.startswith(collection_pat) + ] + + return {cls_dict[s] for t in templates for s in t.superclass if s in cls_dict} def render( @@ -678,6 +694,8 @@ def proper_delete_operator(cls): for c in collections: collection_types |= set(c.leaf_args()) + deps = module_deps(modules, module_settings) + jinja_env.globals.update( { "contains_string": lambda s, pats: any( @@ -714,7 +732,11 @@ def proper_delete_operator(cls): "proper_new_operator": proper_new_operator, "proper_delete_operator": proper_delete_operator, "module_names": module_names, - "sorted_modules": toposort_modules(modules, module_settings), + "sorted_modules": toposort_flatten(deps), + "module_deps": deps, + "collections_deps": collections_deps( + modules, settings["collection_pattern"] + ), "settings": settings, "collection_types": collection_types, "exclude_collections": exclude_collections, diff --git a/bindgen/schemas.py b/bindgen/schemas.py index 36187d1..e320b13 100755 --- a/bindgen/schemas.py +++ b/bindgen/schemas.py @@ -89,6 +89,8 @@ Optional("byref_types", default=[]): [str], Optional("byref_types_smart_ptr", default=[]): [str], Optional("parsing_header", default=""): str, + # members of a package registered on first use; needs lazy::desc and lazy::install from include_pre + Optional("lazy", default=False): bool, Optional("collection_pattern", default=None): str, Optional("exclude_collection", default=[]): [str], Optional("collection_include_header_pre", default=None): str, diff --git a/bindgen/template_main.j2 b/bindgen/template_main.j2 index 410349f..a958af6 100644 --- a/bindgen/template_main.j2 +++ b/bindgen/template_main.j2 @@ -25,27 +25,48 @@ void register_collections_pre_{{i_coll}}(py::module&); void register_collections_{{i_coll}}(py::module&); {% endfor %} +// collection chunks wrapped so that every table row takes the main module +static void register_collections_enums(py::module &main_module) { + py::module m_coll = main_module.def_submodule("collections", R"#(Artifical module with instantiated collection types)#"); +{% for i_coll in range(N_coll_chunks) %} + register_collections_pre_{{i_coll}}(m_coll); +{% endfor %} +} + +static void register_collections(py::module &main_module) { + py::module m_coll = static_cast(main_module.attr("collections")); +{% for i_coll in range(N_coll_chunks) %} + register_collections_{{i_coll}}(m_coll); +{% endfor %} +} + +{% if settings['lazy'] %} +static const lazy::desc {{name}}_modules[] = { +{% for mod in sorted_modules %} + {"{{mod}}", ®ister_{{mod}}_enums, ®ister_{{mod}}, "{{ module_deps[mod]|sort|join(',') }}"}, +{% endfor %} + {"collections", ®ister_collections_enums, ®ister_collections, "{{ collections_deps|sort|join(',') }}"}, +}; +{% endif %} + // main module definiiton PYBIND11_MODULE({{name}}, m) { +{% if settings['lazy'] %} +// phase 1 of every package + for (const auto &d : {{name}}_modules) d.enums(m); +{% else %} // register submodules - {% for mod in sorted_modules %} register_{{mod}}_enums(m); {% endfor %} - -// define the collections module -py::module m_coll = m.def_submodule("collections", R"#(Artifical module with instantiated collection types)#"); - // preregister collections -{% for i_coll in range(N_coll_chunks) %} -register_collections_pre_{{i_coll}}(m_coll); -{% endfor %} - + register_collections_enums(m); // register modules {% for mod in sorted_modules %} register_{{mod}}(m); {% endfor %} +{% endif %} // Add attributes if present @@ -53,9 +74,12 @@ register_collections_pre_{{i_coll}}(m_coll); m.attr("{{k}}") = py::cast("{{v}}"); {% endfor %} +{% if settings['lazy'] %} +// phase 2: per package on first use + lazy::install(m, {{name}}_modules); +{% else %} // register collection types -{% for i_coll in range(N_coll_chunks) %} -register_collections_{{i_coll}}(m_coll); -{% endfor %} + register_collections(m); +{% endif %} }