-
Notifications
You must be signed in to change notification settings - Fork 92
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Custom exceptions #771
Comments
Another one-off "could use a new exception here" would fix the confusing error ( In: from openff.toolkit.typing.engines.smirnoff import ForceField
from openff.toolkit.topology import Molecule, Topology
top = Molecule.from_smiles("C").to_topology()
parsley = ForceField("test_forcefields/test_forcefield.offxml")
parsley['vdW']._parameters = []
parsley.create_openmm_system(top) Out:
|
There are also some cases in which an custom exception is re-used in ways that could be reviewed. For example, trying to use "Coulomb" electrostatics as specified in the SMIRNOFF spec raises a openff-toolkit/openff/toolkit/typing/engines/smirnoff/parameters.py Lines 3890 to 3903 in adde241
|
Also may be some new functionality on the horizon: https://www.python.org/dev/peps/pep-0654/#except |
Another detail to consider: |
If you go down this path, I'm not sure why using in-built exceptions is not ideal, though. Could you explain? My Python experience is patchy. |
Please let me know if this should be a separate issue. On the topic of custom exceptions -- why is MessageException? Last week during a session with @j-wags we wondered why the openff-toolkit/openff/toolkit/utils/utils.py Lines 56 to 64 in 600ebb3
from openff.toolkit.utils.utils import MessageException
def message():
raise MessageException("my message")
def base():
raise Exception("my message")
Also, @mattwthompson has raised some interesting considerations in the intro. No one else has commented so throwing in my opinions here.
It is debatable about the errors you have which are just Exceptions, but if you subclass the general error type it should not break anything.
I don't see why not, so long as the superclasses make sense?
Absolutely.
Absolutely. You could subclass all your custom exceptions from Subclassing custom exceptions from existing errors and creating exception trees gets around a lot of this. For example, these existing exceptions: class MissingPackageError(MessageException):
"""This function requires a package that is not installed."""
class ToolkitUnavailableException(MessageException):
"""The requested toolkit is unavailable."""
# TODO: Allow toolkit to be specified and used in formatting/printing exception.
class LicenseError(ToolkitUnavailableException):
"""This function requires a license that cannot be found."""
class AntechamberNotFoundError(MessageException):
"""The antechamber executable was not found""" could be class MissingPackageError(ModuleNotFoundError): # This is actually the point of ModuleNotFoundError
"""This function requires a package that is not installed."""
class ToolkitUnavailableException(MissingPackageError):
"""The requested toolkit is unavailable."""
# TODO: Allow toolkit to be specified and used in formatting/printing exception.
class LicenseError(ToolkitUnavailableException):
"""This function requires a license that cannot be found."""
class AntechamberNotFoundError(ModuleNotFoundError):
"""The antechamber executable was not found""" and users who don't care about minutiae can catch them all with Edit: and I'm still not sure about this reasoning for so many different custom exceptions.
That is surely what the message raised by the error is for. So long as the |
Thanks for the feedback!
This has been in the codebase for ages (I think back when the PIs were the only developers) and I have always been confused by it as well. Things were different back in ~2017 ... maybe there's some Python 2 compatibility that I have never come across? Maybe it was part of a more elaborate idea that never got fully implemented. I don't know what it does that the built-in exceptions don't do, and therefore I'd like to ditch it. Your last code snippet (with I agree that each docstring, on average, does a poor job of communicating which exceptions can be raised. I'd like to say that we could go through everything and make a good effort to list out everything that could be raised by any function/method call, but that's probably a significant amount of work to wrangle up and a decent amount of work to keep things in sync over time. I'm even skeptical that it can be done well manually - I wonder if there are automated solutions for this?
I think the motivation here is similar to why we'd want to do custom exceptions to begin with; most users will reach for a somewhat broad exception (i.e. one that broadly captures molecule parsing errors) like you describe but some specific cases might require dealing with one or more of the more detailed exceptions. In cases like those, it's more useful to have a sub(-sub-sub...)classed exception that handles one extremely specific case, even if the contents of the message are similar, just like it's better to have a |
Per @mattwthompson and my discussion in today's developers coffee: As a next step, let's
|
As a smaller task, I think it would be nice to remove some of the
this isn't very useful, and could be replaced by a |
After #1021,
This will all presumably go into release v0.10.1, which does not currently have a timeline. The remaining work is more or less going through the above table and moving built-in exceptions into more specific ones. I'm ambivalent as to whether this should be done in one big swing or slowly over time. |
Is #986 on the radar of this PR or should I open a PR? It currently raises vanilla Key- and IndexErrors. |
I probably missed it when I made this survey however many months ago
I think so, yes |
If there's going to be custom errors, it probably makes more sense for these to (also) inherit from the Python core library errors. E.g. |
Based on my limited experience, I'm kinda against this. When I write I'd be interested in other thoughts on this, though. |
You can still do this with XYZError, but for people who want to
I personally would look at this from a user perspective. Keeping Toolkit-specific errors but subclassing generic ones still allows for approaches 1 and 2 above -- they can still just import whichever specific error they want to catch by commenting out the |
Thanks for the feedback!
Ah... That is an important limitation to my perspective.
Very helpful to know that this is the external developer workflow! I would look at something like this and be like "great! Users have so much control over the different things that go wrong", but what I'm getting from this is that they instead go "wow, this is a hassle, I'll just use So, I get the sense that multiple-inherited exceptions are something that Actual Users will enjoy (and I see @richardjgowers, who is a very important Actual User, is also in that camp).
I like that. I hadn't thought to classify built-in exceptions between "generic" and "specific", but you've got a good point -
👍 |
#1569 Helped this a little bit |
Is your feature request related to a problem? Please describe.
The toolkit, and our software in general, should raise descriptive, custom exceptions instead of re-using built-in exceptions.
Describe the solution you'd like
We should have our own exceptions that are fairly specific and provide detailed feedback to the user as to what went wrong and why, and possibly hints at how to resolve the error. For example, this behavior (currently in the code) checks all the boxes:
Today, I went through most of the codebase and recorded when we raise built-in exceptions to get a better picture of how much would be changed by implementing this (nearly) everywhere in the codebase. Some of these (maybe a little less than a half?) probably fit well with existing exceptions, but many will require new exceptions. I have yet to fill out the last column.
NotImplementedError
topology/topology.py
:ValueError
ValueError
ValueError
chemical_environment_matches
got an argument that can't be converted to SMARTSValueError
ValueError
Exception
get_bond_between
Exception
Exception
topology/molecule.py
:Exception
molecule_atom_index
ormolecule_particle_index
when it does not belong to any moleculesValueError
Exception
Exception
molecule_bond_index
when it does not belong to any moleculesValueError
FrozenMolecule.__init__()
ValueError
Exception
FrozenMolecule._add_bond
Exception
Exception
Exception
chemical_environment_matches
got an argument that can't be converted to SMARTSValueError
FrozenMolecule.{to|from}_iupac
Exception
Exception
from_file
needsfile_format
argument specifedException
to_file
couldn't find a toolkit to do its writing for itValueError
ImportError
from_qcschema
got something that's not JSON-encodableAttributeError
from_qcschema
was passed something without explicit hydrogen mapped SMILES or client otherwise failed to convert input to OFFMolKeyError
FrozenMolecule.remap
was given mapping with a different number of hydrogensValueError
get_bond_between
TypeError
ValueError
typing/engines/smirnoff/io.py
:ValueError
typing/engines/smirnoff/parameters.py
:TypeError
TypeError
TypeError
AttributeError
ParameterList.extend
with something not another instance of itTypeError
.add_parameter
TypeError
/ValueError
.add_parameter
ValueError
check_partial_bond_orders_from_molecules_duplicates
are isomorphicValueError
assign_partial_bond_orders_from_molecules
was told to use user bond orders, but not given anyValueError
k
ValueError
ElectrostaticsHandler
orToolkitAM1BCCHandler
found a particle that's not aTopologyAtom
orTopologyVirtualSite
ValueError
VirtualSiteHandler.add_parameter
ValueError
typing/engines/smirnoff/forcefield.py
:Exception
Exception
KeyError
IOError
RuntimeError
create_openmm_system
ValueError
KeyError
utils/utils.py
:get_data_file_path
failed to get anythingValueError
{a|de}tach_units
ValueError
ValueError
utils/toolkits.py
(many are copied code across toolkit wrappers):ValueError
ValueError
Exception
from_iupac
)ValueError
RuntimeError
Exception
assign_fractional_bond_orders
was given an OFFMol without conformersException
ValueError
Exception
ValueError
Exception
hydrogens_are_explicit
, but detect implicit hydrogensValueError
ValueError
ValueError
RuntimeError
ValueError
Step 2 here could be to do a similar survey on which exceptions are actually used, possibly considering how often and/or how similar any are to others, to inform what sort of inheritance structure we want.
Describe alternatives you've considered
Continuing with built-in exceptions is not ideal, long-term. We've already been slowly moving in this direction - most of our PRs the past few months are fairly aligned with the idea here - but slowly picking away at it won't provide the benefits of a more unified exception structure.
Additional context
This idea has been thrown around in a few places (#514 (comment) started it) and a few different contexts but I don't think there's a stub issue.
Some other things to consider
openforcefield/exceptions.py
) or, as it stands now, scattered across files closer to where they'd be raised, or something in between?The text was updated successfully, but these errors were encountered: