Source code for endf_parserpy.endf_parser_factory

############################################################
#
# Author(s):       Georg Schnabel
# Email:           g.schnabel@iaea.org
# Creation date:   2025/06/01
# Last modified:   2026/05/13
# License:         MIT
# Copyright (c) 2025-2026 International Atomic Energy Agency (IAEA)
#
############################################################

import logging
import inspect
import warnings
from .interpreter.endf_parser import EndfParserPy
from .cpp_parsers.endf_parser_cpp import EndfParserCpp


def _check_param(
    parser, param, expect_values, parser_args, do_raise=False, warn_slow=False
):
    value = parser_args[param]
    if value in expect_values:
        return True
    if do_raise:
        if len(expect_values) > 1:
            expect_values_str = "in (" + ",".join(str(v) for v in expect_values) + ")"
        elif len(expect_values) == 1:
            expect_values_str = str(expect_values[0])
        else:
            raise ValueError("expect_values must be non-empty list")
        raise ValueError(
            f"`{param}` expected to be {expect_values_str} for {parser} parser "
            f"but received {param}={value}."
        )
    if warn_slow:
        warnings.warn(
            f"Argument {param}={value} forces the selection of the (slow) Python parser",
            UserWarning,
        )

    return False


def _none_to_defaults(thedict, defaults):
    for k, d in defaults.items():
        if k not in thedict or thedict[k] is None:
            thedict[k] = d


[docs] class EndfParserFactory: """Factory class to create ENDF parsers. This class provides the :meth:`~EndfParserFactory.create` method for creating an ENDF parser object. """
[docs] @staticmethod def create( select="fastest", warn_slow=True, require_compat=False, ignore_number_mismatch=False, ignore_zero_mismatch=True, ignore_varspec_mismatch=True, fuzzy_matching=False, # Python only abuse_signpos=False, skip_intzero=False, prefer_noexp=False, accept_spaces=True, ignore_blank_lines=True, ignore_send_records=False, ignore_missing_tpid=False, accept_nan_inf=True, keep_E=False, preserve_value_strings=False, include_linenum=True, width=11, # Python only check_arrays=False, # Python only strict_datatypes=False, array_type="dict", explain_missing_variable=None, # Python only cache_dir=None, # Python only print_cache_info=None, # Python only endf_format="endf6-ext", recipes=None, # Python only parsing_funs=None, # Python only loglevel=None, # Python only validate_control_records=False, # C++ only ): """Create an ENDF parser instance. This function creates an ENDF parser object, which is either an instance of the :class:`~endf_parserpy.EndfParserPy` (Python parser) or :class:`~endf_parserpy.EndfParserCpp` (C++ parser) class depending on the supplied arguments. The three parameters described below control the behavior of the parser factory. All other parameters are arguments to the constructor of the :class:`~endf_parserpy.EndfParserPy` or :class:`~endf_parserpy.EndfParserCpp` class and most of them are available for both classes. Parameters ---------- select : string Can be ``fastest``, ``cpp``, or ``python``. If ``fastest``, selects the C++ parser if possible and falls back on the Python parser otherwise. If ``cpp`` or ``python``, selects C++ or Python parser, respectively, and fails if requested parser type cannot be selected. warn_slow : bool If ``True``, issues a :exc:`UserWarning` if user requested fastest parser but C++ parser is not available or incompatible with parser arguments. require_compat : bool If ``True``, only instantiate parser class if parser arguments are compatible with both Python and C++ parser and raise a :exc:`ValueError` exception otherwise. """ params = inspect.signature(EndfParserFactory.create).parameters.keys() real_params = dict(locals()) parser_args = {p: real_params[p] for p in params} del parser_args["select"] del parser_args["warn_slow"] del parser_args["require_compat"] epf = EndfParserFactory if require_compat: msg_tmpl = ( "Argument compatibility with both Python and C++ parser enforced " "(require_compat=True) but the arguments provided are not compatible " "with the {} parser for the following reason: {}" ) try: epf.python_compatible_args(parser_args, do_raise=True) except ValueError as exc: raise ValueError(msg_tmpl.format("Python", str(exc))) try: epf.cpp_compatible_args(parser_args, do_raise=True) except ValueError as exc: raise ValueError(msg_tmpl.format("C++", str(exc))) if select == "python": epf.python_compatible_args(parser_args, do_raise=True) return epf.get_python_parser(parser_args) if select == "cpp": epf.cpp_compatible_args(parser_args, do_raise=True) return epf.get_cpp_parser(parser_args) if select == "fastest": if not epf.cpp_compatible_args( parser_args, do_raise=False, warn_slow=warn_slow ): epf.python_compatible_args(parser_args, do_raise=True) return epf.get_python_parser(parser_args) try: return epf.get_cpp_parser(parser_args) except ImportError: if warn_slow: warnings.warn( "C++ parser not available, forced to use slow Python parser", UserWarning, ) epf.python_compatible_args(parser_args, do_raise=True) return epf.get_python_parser(parser_args) raise ValueError( "value of `select` argument must be one of `python`, `cpp`, `fastest`" )
@staticmethod def python_compatible_args(parser_args, do_raise=False): return _check_param( "Python", "validate_control_records", [False], parser_args, do_raise ) @staticmethod def get_python_parser(parser_args): parser_args = parser_args.copy() _none_to_defaults( parser_args, { "check_arrays": True, "explain_missing_variable": True, "print_cache_info": True, "loglevel": logging.WARNING, }, ) parser_args.pop("validate_control_records", None) return EndfParserPy(**parser_args) @staticmethod def cpp_compatible_args(parser_args, do_raise=False, warn_slow=False): return ( _check_param( "C++", "fuzzy_matching", [False], parser_args, do_raise, warn_slow ) and _check_param("C++", "width", [11], parser_args, do_raise, warn_slow) and _check_param( "C++", "check_arrays", [False], parser_args, do_raise, warn_slow ) and _check_param( "C++", "strict_datatypes", [False], parser_args, do_raise, warn_slow ) and _check_param( "C++", "explain_missing_variable", [None, False], parser_args, do_raise, warn_slow, ) and _check_param("C++", "recipes", [None], parser_args, do_raise, warn_slow) and _check_param( "C++", "parsing_funs", [None], parser_args, do_raise, warn_slow ) and _check_param( "C++", "loglevel", [None], parser_args, do_raise, warn_slow ) ) @staticmethod def get_cpp_parser(parser_args): parser_args = parser_args.copy() parser_args.pop("fuzzy_matching", None) parser_args.pop("width", None) parser_args.pop("check_arrays", None) parser_args.pop("strict_datatypes", None) parser_args.pop("explain_missing_variable", None) parser_args.pop("print_cache_info", [None]) parser_args.pop("cache_dir") parser_args.pop("recipes", None) parser_args.pop("parsing_funs", None) parser_args.pop("loglevel", None) return EndfParserCpp(**parser_args)