diff --git a/doc/OnlineDocs/developer_reference/deprecation.rst b/doc/OnlineDocs/developer_reference/deprecation.rst index 886309880a5..7fc5ec2b0ff 100644 --- a/doc/OnlineDocs/developer_reference/deprecation.rst +++ b/doc/OnlineDocs/developer_reference/deprecation.rst @@ -11,22 +11,33 @@ Deprecation We offer a set of tools to help with deprecation in ``pyomo.common.deprecation``. -By policy, when deprecating or moving an existing capability, -one of the following functions should be imported. In use, -the ``version`` option should be set to current development -version. This can be found by running ``pyomo --version`` on -your local fork/branch. +By policy, when deprecating or moving an existing capability, one of the +following utilities should be leveraged. Each has a required +``version`` argument that should be set to current development version (e.g., +``"6.6.2.dev0"``). This version will be updated to the next actual +release as part of the Pyomo release process. The current development version +can be found by running ``pyomo --version`` on your local fork/branch. -.. autoclass:: pyomo.common.deprecation.deprecated +.. currentmodule:: pyomo.common.deprecation + +.. autosummary:: + + deprecated + deprecation_warning + relocated_module + relocated_module_attribute + RenamedClass + +.. autodecorator:: pyomo.common.deprecation.deprecated :noindex: -.. autoclass:: pyomo.common.deprecation.deprecation_warning +.. autofunction:: pyomo.common.deprecation.deprecation_warning :noindex: -.. autoclass:: pyomo.common.deprecation.relocated_module +.. autofunction:: pyomo.common.deprecation.relocated_module :noindex: -.. autoclass:: pyomo.common.deprecation.relocated_module_attribute +.. autofunction:: pyomo.common.deprecation.relocated_module_attribute :noindex: .. autoclass:: pyomo.common.deprecation.RenamedClass diff --git a/doc/OnlineDocs/library_reference/common/errors.rst b/doc/OnlineDocs/library_reference/common/errors.rst new file mode 100644 index 00000000000..7b2bd01fe32 --- /dev/null +++ b/doc/OnlineDocs/library_reference/common/errors.rst @@ -0,0 +1,6 @@ +pyomo.common.errors +=================== + +.. automodule:: pyomo.common.errors + :members: + :member-order: bysource diff --git a/doc/OnlineDocs/library_reference/common/index.rst b/doc/OnlineDocs/library_reference/common/index.rst index 9b7836512ec..c9c99008250 100644 --- a/doc/OnlineDocs/library_reference/common/index.rst +++ b/doc/OnlineDocs/library_reference/common/index.rst @@ -11,6 +11,7 @@ or rely on any other parts of Pyomo. config.rst dependencies.rst deprecation.rst + errors.rst fileutils.rst formatting.rst tempfiles.rst diff --git a/pyomo/common/dependencies.py b/pyomo/common/dependencies.py index 9ee7b291966..266074201b0 100644 --- a/pyomo/common/dependencies.py +++ b/pyomo/common/dependencies.py @@ -17,22 +17,19 @@ import warnings from .deprecation import deprecated, deprecation_warning, in_testing_environment +from .errors import DeferredImportError from . import numeric_types SUPPRESS_DEPENDENCY_WARNINGS = False -class DeferredImportError(ImportError): - pass - - class ModuleUnavailable(object): - """Mock object that raises a DeferredImportError upon attribute access + """Mock object that raises :py:class:`.DeferredImportError` upon attribute access This object is returned by :py:func:`attempt_import()` in lieu of the module in the case that the module import fails. Any attempts - to access attributes on this object will raise a DeferredImportError + to access attributes on this object will raise a :py:class:`.DeferredImportError` exception. Parameters @@ -134,7 +131,7 @@ class DeferredImportModule(object): ``defer_check=True``. Any attempts to access attributes on this object will trigger the actual module import and return either the appropriate module attribute or else if the module import fails, - raise a DeferredImportError exception. + raise a :py:class:`.DeferredImportError` exception. """ @@ -184,6 +181,61 @@ def mro(self): def UnavailableClass(unavailable_module): + """Function to generate an "unavailable" base class + + This function returns a custom class that wraps the + :py:class:`ModuleUnavailable` instance returned by + :py:func:`attempt_import` when the target module is not available. + Any attempt to instantiate this class (or a class derived from it) + or access a class attribute will raise the + :py:class:`.DeferredImportError` from the wrapped + :py:class:`ModuleUnavailable` object. + + Parameters + ---------- + unavailable_module: ModuleUnavailable + The :py:class:`ModuleUnavailable` instance (from + :py:func:`attempt_import`) to use to generate the + :py:class:`.DeferredImportError`. + + Example + ------- + + Declaring a class that inherits from an optional dependency: + + .. doctest:: + + >>> from pyomo.common.dependencies import attempt_import, UnavailableClass + >>> bogus, bogus_available = attempt_import('bogus_unavailable_class') + >>> class MyPlugin(bogus.plugin if bogus_available else UnavailableClass(bogus)): + ... pass + + Attempting to instantiate the derived class generates an exception + when the module is unavailable: + + .. doctest:: + + >>> MyPlugin() + Traceback (most recent call last): + ... + pyomo.common.dependencies.DeferredImportError: The class 'MyPlugin' cannot be + created because a needed optional dependency was not found (import raised + ModuleNotFoundError: No module named 'bogus_unavailable_class') + + As does attempting to access class attributes on the derived class: + + .. doctest:: + + >>> MyPlugin.create_instance() + Traceback (most recent call last): + ... + pyomo.common.dependencies.DeferredImportError: The class attribute + 'MyPlugin.create_instance' is not available because a needed optional + dependency was not found (import raised ModuleNotFoundError: No module + named 'bogus_unavailable_class') + + """ + class UnavailableMeta(type): def __getattr__(cls, name): raise DeferredImportError( @@ -224,11 +276,11 @@ class DeferredImportIndicator(_DeferredImportIndicatorBase): This object serves as a placeholder for the Boolean indicator if a deferred module import was successful. Casting this instance to - bool will cause the import to be attempted. The actual import logic - is here and not in the DeferredImportModule to reduce the number of - attributes on the DeferredImportModule. + `bool` will cause the import to be attempted. The actual import logic + is here and not in the :py:class:`DeferredImportModule` to reduce the number of + attributes on the :py:class:`DeferredImportModule`. - ``DeferredImportIndicator`` supports limited logical expressions + :py:class:`DeferredImportIndicator` supports limited logical expressions using the ``&`` (and) and ``|`` (or) binary operators. Creating these expressions does not trigger the import of the corresponding :py:class:`DeferredImportModule` instances, although casting the @@ -473,8 +525,8 @@ def attempt_import( defer_check: bool, optional If True (the default), then the attempted import is deferred until the first use of either the module or the availability - flag. The method will return instances of DeferredImportModule - and DeferredImportIndicator. + flag. The method will return instances of :py:class:`DeferredImportModule` + and :py:class:`DeferredImportIndicator`. deferred_submodules: Iterable[str], optional If provided, an iterable of submodule names within this module @@ -622,7 +674,7 @@ def _perform_import( def declare_deferred_modules_as_importable(globals_dict): - """Make all DeferredImportModules in ``globals_dict`` importable + """Make all :py:class:`DeferredImportModules` in ``globals_dict`` importable This function will go throughout the specified ``globals_dict`` dictionary and add any instances of :py:class:`DeferredImportModule` diff --git a/pyomo/common/deprecation.py b/pyomo/common/deprecation.py index 14fb97cccff..2e39083770d 100644 --- a/pyomo/common/deprecation.py +++ b/pyomo/common/deprecation.py @@ -389,10 +389,6 @@ def relocated_module_attribute( object from the new location (on request), as well as emitting the deprecation warning. - It contains backports of the __getattr__ functionality for earlier - versions of Python (although the implementation for 3.5+ is more - efficient that the implementation for 2.7+) - Parameters ---------- local: str diff --git a/pyomo/common/errors.py b/pyomo/common/errors.py index 55cd2ce0723..744dbf76f59 100644 --- a/pyomo/common/errors.py +++ b/pyomo/common/errors.py @@ -14,6 +14,36 @@ def format_exception(msg, prolog=None, epilog=None, exception=None, width=76): + """Generate a formatted exception message + + This returns a formatted exception message, line wrapped for display + on the console and with optional prolog and epilog messages. + + Parameters + ---------- + msg: str + The raw exception message + + prolog: str, optional + A message to output before the exception message, ``msg``. If + this message is long enough to line wrap, the ``msg`` will be + indented a level below the ``prolog`` message. + + epilog: str, optional + A message to output after the exception message, ``msg``. If + provided, the ``msg`` will be indented a level below the + ``prolog`` / ``epilog`` messages. + + exception: Exception, optional + The raw exception being raised (used to improve initial line wrapping). + + width: int, optional + The line length to wrap the exception message to. + + Returns + ------- + str + """ fields = [] if epilog: @@ -92,14 +122,24 @@ class ApplicationError(Exception): class PyomoException(Exception): """ - Exception class for other pyomo exceptions to inherit from, - allowing pyomo exceptions to be caught in a general way + Exception class for other Pyomo exceptions to inherit from, + allowing Pyomo exceptions to be caught in a general way (e.g., in other applications that use Pyomo). """ pass +class DeferredImportError(ImportError): + """This exception is raised when something attempts to access a module + that was imported by :py:func:`.attempt_import`, but the module + import failed. + + """ + + pass + + class DeveloperError(PyomoException, NotImplementedError): """ Exception class used to throw errors that result from Pyomo @@ -116,9 +156,11 @@ def __str__(self): ) -class InvalidValueError(PyomoException, ValueError): +class InfeasibleConstraintException(PyomoException): """ - Exception class used for for value errors in compiled model representations + Exception class used by Pyomo transformations to indicate + that an infeasible constraint has been identified (e.g. in + the course of range reduction). """ pass @@ -132,26 +174,9 @@ class IntervalException(PyomoException, ValueError): pass -class InfeasibleConstraintException(PyomoException): - """ - Exception class used by Pyomo transformations to indicate - that an infeasible constraint has been identified (e.g. in - the course of range reduction). +class InvalidValueError(PyomoException, ValueError): """ - - pass - - -class NondifferentiableError(PyomoException, ValueError): - """A Pyomo-specific ValueError raised for non-differentiable expressions""" - - pass - - -class TempfileContextError(PyomoException, IndexError): - """A Pyomo-specific IndexError raised when attempting to use the - TempfileManager when it does not have a currently active context. - + Exception class used for value errors in compiled model representations """ pass @@ -177,3 +202,18 @@ def __str__(self): "pull requests are always welcome!", exception=self, ) + + +class NondifferentiableError(PyomoException, ValueError): + """A Pyomo-specific ValueError raised for non-differentiable expressions""" + + pass + + +class TempfileContextError(PyomoException, IndexError): + """A Pyomo-specific IndexError raised when attempting to use the + TempfileManager when it does not have a currently active context. + + """ + + pass