idaholab / MontePy

MontePy is the most user friendly Python library (API) to read, edit, and write MCNP input files.
https://www.montepy.org/
MIT License
32 stars 7 forks source link

Adding more input context to all error messages. #581

Closed MicahGale closed 2 weeks ago

MicahGale commented 1 month ago

Description

This wraps almost errors from MCNP_Object with information of where in a file this error came from. This works by:

  1. Creating montepy.errors.add_line_number_to_exception. This takes an error and a MCNP_Object instance. If possible it takes the _input information and appends that information to the error message (see below).
  2. Create a metaclass (_ExceptionContextAdder) for MCNP_Object this modifies all attributes. If it's _private or __dunder__ it does nothing. Otherwise for callable and property it gets wrapped using _wrap_attr_call. This just wraps the function call in a try except
  3. Modularized the pretty-printing function from ParsingError for use here to print the input, see below.

Here is an example stack trace.

---------------------------------------------------------------------------
KeyError                                  Traceback (most recent call last)
File ~/dev/montepy/montepy/cell.py:528, in Cell.update_pointers(self, cells, materials, surfaces)
    527 try:
--> 528     self._material = materials[self.old_mat_number]
    529 except KeyError:

File ~/dev/montepy/montepy/numbered_object_collection.py:458, in NumberedObjectCollection.__getitem__(self, i)
    457 if ret is None:
--> 458     raise KeyError(f"Object with number {i} not found in {type(self)}")
    459 return ret

KeyError: "Object with number 1 not found in <class 'montepy.materials.Materials'>"

During handling of the above exception, another exception occurred:

BrokenObjectLinkError                     Traceback (most recent call last)
Cell In[2], line 1
----> 1 problem = montepy.read_input("tests/inputs/test.imcnp")

File ~/dev/montepy/montepy/input_parser/input_reader.py:31, in read_input(destination, mcnp_version, replace)
     29 problem = mcnp_problem.MCNP_Problem(destination)
     30 problem.mcnp_version = mcnp_version
---> 31 problem.parse_input(replace=replace)
     32 return problem

File ~/dev/montepy/montepy/mcnp_problem.py:397, in MCNP_Problem.parse_input(self, check_input, replace)
    395     else:
    396         raise e
--> 397 self.__update_internal_pointers(check_input)

File ~/dev/montepy/montepy/mcnp_problem.py:413, in MCNP_Problem.__update_internal_pointers(self, check_input)
    410         raise e
    412 self.__load_data_inputs_to_object(self._data_inputs)
--> 413 self._cells.update_pointers(
    414     self.cells,
    415     self.materials,
    416     self.surfaces,
    417     self._data_inputs,
    418     self,
    419     check_input,
    420 )
    421 for surface in self._surfaces:
    422     try:

File ~/dev/montepy/montepy/cells.py:172, in Cells.update_pointers(self, cells, materials, surfaces, data_inputs, problem, check_input)
    165         cell.update_pointers(cells, materials, surfaces)
    166     except (
    167         BrokenObjectLinkError,
    168         MalformedInputError,
    169         ParticleTypeNotInProblem,
    170         ParticleTypeNotInCell,
    171     ) as e:
--> 172         handle_error(e)
    173         continue
    174 self.__setup_blank_cell_modifiers(problem, check_input)

File ~/dev/montepy/montepy/cells.py:130, in Cells.update_pointers.<locals>.handle_error(e)
    128     warnings.warn(f"{type(e).__name__}: {e.message}", stacklevel=3)
    129 else:
--> 130     raise e

File ~/dev/montepy/montepy/cells.py:165, in Cells.update_pointers(self, cells, materials, surfaces, data_inputs, problem, check_input)
    163 for cell in self:
    164     try:
--> 165         cell.update_pointers(cells, materials, surfaces)
    166     except (
    167         BrokenObjectLinkError,
    168         MalformedInputError,
    169         ParticleTypeNotInProblem,
    170         ParticleTypeNotInCell,
    171     ) as e:
    172         handle_error(e)

File ~/dev/montepy/montepy/mcnp_object.py:45, in _ExceptionContextAdder._wrap_attr_call.<locals>.wrapped(*args, **kwargs)
     43 if len(args) > 0 and isinstance(args[0], MCNP_Object):
     44     self = args[0]
---> 45     add_line_number_to_exception(e, self)
     46 else:
     47     raise e

File ~/dev/montepy/montepy/errors.py:254, in add_line_number_to_exception(error, broken_robot)
    252 args = (message,) + args[1:]
    253 error.args = args
--> 254 raise error.with_traceback(trace)

File ~/dev/montepy/montepy/mcnp_object.py:41, in _ExceptionContextAdder._wrap_attr_call.<locals>.wrapped(*args, **kwargs)
     38 @functools.wraps(func)
     39 def wrapped(*args, **kwargs):
     40     try:
---> 41         return func(*args, **kwargs)
     42     except Exception as e:
     43         if len(args) > 0 and isinstance(args[0], MCNP_Object):

File ~/dev/montepy/montepy/cell.py:530, in Cell.update_pointers(self, cells, materials, surfaces)
    528         self._material = materials[self.old_mat_number]
    529     except KeyError:
--> 530         raise BrokenObjectLinkError(
    531             "Cell", self.number, "Material", self.old_mat_number
    532         )
    533 else:
    534     self._material = None

BrokenObjectLinkError:     tests/inputs/test.imcnp, line 1

         2| C cells
         3| c # hidden vertical Do not touch
         4| c
         5| 1 1 20
         6|          -1000  $ dollar comment
         7|         imp:n,p,e=1 U=350 trcl=5
Material 1 is missing from the input from the definition of: Cell 1

Some thoughts:

  1. ~This shows the exception as a re-catch. Does that matter?~ This is due to the catch raise a new exception wrapping.
  2. This shows the wrapping and decorating functions in the stack trace; does that matter?

Fixes #579, fixes #362

Checklist

tjlaboss commented 1 month ago
Traceback (most recent call last):
  File "grab_beginning_comment.py", line 1, in <module>
    import montepy
  File "montepy/__init__.py", line 10, in <module>
    from . import input_parser
  File "montepy/input_parser/__init__.py", line 6, in <module>
    from . import input_reader
  File "montepy/input_parser/input_reader.py", line 2, in <module>
    from montepy import mcnp_problem
  File "montepy/mcnp_problem.py", line 397
    f"{'\n'.join(obj._input.input_lines)}"
                                          ^
SyntaxError: f-string expression part cannot include a backslash
MicahGale commented 1 month ago

What version of python was this on? I didn't get a syntax error in 3.12.

tjlaboss commented 3 weeks ago

3.9.9

tjlaboss commented 3 weeks ago

I fixed it with fa4ea74