Last active
February 23, 2025 09:50
-
-
Save bact/ce01c9fb0049a33cfcc9043e8df7c6f9 to your computer and use it in GitHub Desktop.
Simplify Python binding from shacl2code
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# SPDX-FileCopyrightText: 2025-present Arthit Suriyawongkul | |
# SPDX-FileType: SOURCE | |
# SPDX-License-Identifier: 0BSD | |
# | |
# Generate a simplified interface to classes and functions. | |
# | |
# It import all classes and functions from the original module, | |
# removing the (long IRI-based) prefix from class names, | |
# and re-export them to the new module. | |
# | |
# Intended to use with a Python binding generated from shacl2code. | |
# | |
# Usage: | |
# | |
# 1. Generate Python binding: | |
# | |
# shacl2code generate -i https://spdx.github.io/spdx-spec/v3.0.1/rdf/spdx-model.jsonld python -o spdx3_0_1.py | |
# | |
# 2. Simplify Python binding: | |
# | |
# python gen_spdx3_model.py spdx3_0_1.py spdx3.py | |
# | |
# 3. Ensure spdx3.py is in the same directory as spdx3_0_1.py | |
# 4. Import `spdx3` instead of `spdx3_0_1` | |
# | |
# Example: | |
# Instead of: | |
# from . import spdx3_0_1.https_spdx_org_rdf_3_0_1_terms_Core_Element | |
# Use: | |
# from . import spdx3.Core_Element | |
# | |
# Note: | |
# Class prefixes prevent naming conflicts that can arise when SHACL uses | |
# classes from different namespaces. This utility removes the prefix for | |
# specific use cases where it's unnecessary. | |
import inspect | |
import importlib.util | |
import argparse | |
import os | |
_SPDX_3_0_1_CLASS_PREFIX = "https_spdx_org_rdf_3_0_1_terms_" | |
def import_and_rename_classes(module, prefix): | |
classes = {} | |
for name, obj in inspect.getmembers(module): | |
if inspect.isclass(obj) and name.startswith(prefix): | |
new_class_name = name[len(prefix) :] # Remove the prefix | |
classes[new_class_name] = obj | |
return classes | |
def import_functions(module): | |
functions = {} | |
for name, obj in inspect.getmembers(module): | |
if inspect.isfunction(obj): | |
functions[name] = obj | |
return functions | |
def load_module_from_file(module_name, file_path): | |
spec = importlib.util.spec_from_file_location(module_name, file_path) | |
module = importlib.util.module_from_spec(spec) | |
spec.loader.exec_module(module) | |
return module | |
def generate_module_file(input_path, prefix, output_path): | |
input_file_name = os.path.basename(input_path) | |
output_file_name = os.path.basename(output_path) | |
input_module_name = os.path.splitext(input_file_name)[0] | |
output_module_name = os.path.splitext(output_file_name)[0] | |
module = load_module_from_file(input_module_name, input_path) | |
classes = import_and_rename_classes(module, prefix) | |
functions = import_functions(module) | |
with open(output_path, "w") as f: | |
f.write( | |
f""" | |
# SPDX-FileType: SOURCE | |
# SPDX-License-Identifier: 0BSD | |
# | |
# Auto-generated module. DO NOT MANUALLY MODIFY. | |
# | |
# This module provides a simplified interface to classes and functions | |
# originally defined in the `{module.__name__}` module. | |
# | |
# It re-exports all classes and functions from `{module.__name__}`, | |
# removing the following prefix from class names: | |
# '{prefix}' | |
# | |
# Usage: | |
# 1. Ensure this file ({output_file_name}) is in the same directory as {input_file_name}. | |
# 2. Import this module (`{output_module_name}`) instead of `{module.__name__}`. | |
# | |
# Example: | |
# Instead of: | |
# from . import {module.__name__}.{prefix}Core_Element | |
# Use: | |
# from . import {output_module_name}.Core_Element | |
""" | |
) | |
f.write(f"\nfrom . import {module.__name__} as _reexport\n\n") | |
# Write classes | |
for new_class_name, original_class in classes.items(): | |
f.write(f"{new_class_name} = _reexport.{original_class.__name__}\n") | |
f.write("\n") | |
# Write functions | |
for func_name, func in functions.items(): | |
f.write(f"{func_name} = _reexport.{func.__name__}\n") | |
f.write("\n") | |
# Write __all__ variable | |
all_items = list(classes.keys()) + list(functions.keys()) | |
f.write("__all__ = [\n") | |
for item in all_items: | |
f.write(f" '{item}',\n") | |
f.write("]\n") | |
if __name__ == "__main__": | |
parser = argparse.ArgumentParser( | |
description="Generate a module that re-exports classes and functions from a specified module." | |
) | |
parser.add_argument( | |
"input_path", | |
type=str, | |
help="The path to the Python module input file", | |
) | |
parser.add_argument( | |
"output_path", | |
type=str, | |
help="The path to the Python module output file", | |
) | |
parser.add_argument( | |
"--class_prefix", | |
type=str, | |
default=_SPDX_3_0_1_CLASS_PREFIX, | |
help="The prefix to be removed from class names. default: %(default)s)", | |
) | |
args = parser.parse_args() | |
generate_module_file(args.input_path, args.class_prefix, args.output_path) | |
print(f"Module file generated at {args.output_path}") |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment