Skip to content
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

Spring cleaning for dependencies, Pt. 2: The Tree-Chopping #1133

Merged
merged 56 commits into from
May 26, 2023
Merged
Show file tree
Hide file tree
Changes from 55 commits
Commits
Show all changes
56 commits
Select commit Hold shift + click to select a range
04ccdb1
Remove dependencies without any Ctrl-F-able imports
lbianchi-lbl Mar 10, 2023
4440b8e
Try removing potentially unneeded version constraints
lbianchi-lbl Mar 10, 2023
d209e51
Use list instead of unsupported set for columns in DataFrame constructor
lbianchi-lbl Mar 10, 2023
15954e0
Move sympy to optional dependencies
lbianchi-lbl Mar 14, 2023
382e6d9
Remove backport package that has been obsolete since Python 3.3
lbianchi-lbl Mar 14, 2023
e9672a0
Split optional dependencies in categories
lbianchi-lbl Mar 15, 2023
b360707
Format with Black
lbianchi-lbl Mar 16, 2023
af9aea8
Add pint to required dependencies
lbianchi-lbl Mar 16, 2023
4c4f943
Try Pyomo implementation of topical optional dependencies
lbianchi-lbl Mar 16, 2023
043644e
Split optional dependencies in categories
lbianchi-lbl Mar 15, 2023
b7ab623
Format with Black
lbianchi-lbl Mar 16, 2023
81fae89
Add pint to required dependencies
lbianchi-lbl Mar 16, 2023
17fa84e
Try Pyomo implementation of topical optional dependencies
lbianchi-lbl Mar 16, 2023
1176954
Add pytest.importorskip() directives to DMF test modules
lbianchi-lbl Apr 6, 2023
bee7f4a
Move colorama import to function scope
lbianchi-lbl Apr 6, 2023
6d4026d
Track updates to Prescient requirement
lbianchi-lbl Apr 6, 2023
311e926
Try handling optional imports at the conftest.py level
lbianchi-lbl Apr 14, 2023
866cf75
Revert "Add pytest.importorskip() directives to DMF test modules"
lbianchi-lbl Apr 6, 2023
7157827
Merge remote-tracking branch 'lbianchi-lbl/issue-705-2' into issue-705-2
fndari Apr 28, 2023
55e360a
Fix accidental tuplification
lbianchi-lbl May 3, 2023
db29fb0
Remove too generic 'optional' target
lbianchi-lbl May 3, 2023
fe160b4
Use appropriate version of Pyomo
lbianchi-lbl May 3, 2023
1aad67e
Add version constraint for ipython from #1179
lbianchi-lbl May 3, 2023
c342821
Remove unneeded global variable
lbianchi-lbl May 3, 2023
5682ee3
Merge branch 'main' into issue-705-2
lbianchi-lbl May 3, 2023
f42638f
Update dev requirements to track changes in extras_require targets
lbianchi-lbl May 4, 2023
e55df40
Improve pytest plugins to skip tests if registered modules are not found
lbianchi-lbl May 4, 2023
be1c50b
Run Black
lbianchi-lbl May 4, 2023
9e909cc
Fix typo
lbianchi-lbl May 4, 2023
3501e9f
Update site-packages CI job to track changes to extras_require targets
lbianchi-lbl May 4, 2023
b9ddc23
Resolve Pylint failures
lbianchi-lbl May 4, 2023
e44e431
Modify pytest INI options to support plugins in idaes/conftest.py
lbianchi-lbl May 4, 2023
fad3ad5
Run Black
lbianchi-lbl May 4, 2023
3c03566
Fix bug causing error running idaes get-extensions in env without pytest
lbianchi-lbl May 4, 2023
753a947
Trigger CI
lbianchi-lbl May 4, 2023
3f74a5f
Tweak plugin hooks to support pytest runs outside of source dir
lbianchi-lbl May 4, 2023
7555a0f
Add handling for yaml import as pyyaml is now an optional dependency
lbianchi-lbl May 4, 2023
00a4e77
Add constraint for urllib3 in dev dependencies
lbianchi-lbl May 4, 2023
9a90416
Handle import of requests for test_model_server.py
lbianchi-lbl May 9, 2023
a3ac23f
Remove examples extras_require target
lbianchi-lbl May 10, 2023
5d319c8
Merge and rename target to omlt
lbianchi-lbl May 10, 2023
0b5cb05
Reshuffle meta-targets for more differentiation
lbianchi-lbl May 10, 2023
4f54cc4
Isolate DMF reference in idaes.core.util.convergence.convergence_base
lbianchi-lbl May 10, 2023
c75f793
Format with Black
lbianchi-lbl May 10, 2023
f72a200
Add Pylint directive for import guard
lbianchi-lbl May 11, 2023
e738689
Revert "Add constraint for urllib3 in dev dependencies"
lbianchi-lbl May 4, 2023
c982e40
Remove Python version constraint on coolprop
lbianchi-lbl May 12, 2023
caff30d
Remove catch-all extras_require targets
lbianchi-lbl May 19, 2023
1db7264
Merge branch 'main' into issue-705-2
lbianchi-lbl May 19, 2023
bebd429
Update Sphinx docs for pip installation
lbianchi-lbl May 19, 2023
b4075db
Merge branch 'main' into issue-705-2
May 23, 2023
d9e6ccd
Replace outdated extras_require targets in CI workflow
lbianchi-lbl May 23, 2023
c6463e2
Merge remote-tracking branch 'lbianchi-lbl/issue-705-2' into issue-705-2
lbianchi-lbl May 23, 2023
921ff1e
Add how-to guide for installing optional dependencies
lbianchi-lbl May 23, 2023
f37a2a6
Add ref anchors
lbianchi-lbl May 23, 2023
a70c11e
Merge branch 'main' into issue-705-2
lbianchi-lbl May 26, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 6 additions & 3 deletions .github/workflows/integration.yml
Original file line number Diff line number Diff line change
Expand Up @@ -214,7 +214,8 @@ jobs:
matrix:
install-mode:
- pip-default
- pip-optional
- pip-complete
- pip-all
- conda-like
os:
- linux
Expand All @@ -223,8 +224,10 @@ jobs:
runner-image: ubuntu-20.04
- install-mode: pip-default
pip-install-target: '.'
- install-mode: pip-optional
pip-install-target: .[optional]
- install-mode: pip-complete
pip-install-target: .[ui,dmf,grid]
- install-mode: pip-all
pip-install-target: .[ui,dmf,grid,omlt,coolprop]
- install-mode: conda-like
pip-install-target: '.'
dependencies-to-uninstall: omlt
Expand Down
12 changes: 9 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,11 +33,17 @@ Now, in that "idaes-pse" environment, install the IDAES Toolkit using either `pi

```bash
# install latest stable release
pip install idaes_pse
pip install idaes-pse
# install latest stable release with one set of optional dependencies, e.g. `ui` for the user interface
pip install "idaes-pse[ui]"
# install latest stable release with multiple sets of optional dependencies
pip install "idaes-pse[ui,dmf,omlt,grid,coolprop]"
# install latest version from the main branch of this repository
pip install 'idaes-pse[prerelease] @ https://github.com/IDAES/idaes-pse/archive/main.zip'
pip install "idaes-pse @ git+https://github.com/IDAES/idaes-pse@main"
# install from the `mybranch` branch of the fork belonging to `myuser`
pip install "idaes-pse @ git+https://github.com/myuser/idaes-pse@mybranch"
```
.

You can check the version installed with the command:

```bash
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
.. _omlt:

OMLT: Optimization and Machine Learning Toolkit
===============================================

Expand Down
1 change: 1 addition & 0 deletions docs/how_to_guides/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,5 @@ How-To-Guides
vis/index
workflow/index
data_management_framework/index
opt_dependencies
versioned_idaes_install
52 changes: 52 additions & 0 deletions docs/how_to_guides/opt_dependencies.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
Installing optional dependencies
================================

Depending on the installation method (Conda, pip) and the IDAES version, not all of IDAES's dependencies will be installed by default.

Installing these *optional dependencies* might require additional steps, described below, in addition to the default installation steps described elsewhere in the documentation.

For pip installations
^^^^^^^^^^^^^^^^^^^^^

.. important:: Users who installed IDAES by running ``pip install idaes-pse`` should follow these steps.

When installing IDAES using pip, optional dependencies can be installed by specifying one or more "``extras_require`` targets":

.. code-block:: bash

# install the `ui` target
pip install "idaes-pse[ui]"

# install the `ui` and `dmf` targets
pip install "idaes-pse[ui,dmf]"

.. important:: The ``pip install`` argument should be wrapped in double quotes (``"``) when it contains square brackets. Otherwise, it might cause an error to be reported by the shell/command interface used to invoke the command.

Available optional dependencies targets
---------------------------------------

As of IDAES 2.1, the following ``extras_require`` targets are available:

* ``ui``: for the :ref:`IDAES Flowsheet Visualizer <IFV>`
* ``dmf``: for the :ref:`Data Management Framework <dmf-overview>`
* ``grid``: for the :ref:`IDAES Grid integration <idaes-grid>`
* ``omlt``: for the :ref:`OMLT integration <omlt>`
* ``coolprop``: for the :py:mod:`idaes.models.properties.modular_properties.coolprop` property package

Specifying optional dependencies when installing a prerelease version of IDAES
------------------------------------------------------------------------------

.. code-block:: bash

pip install "idaes-pse[ui,dmf] @ git+https://github.com/IDAES/idaes-pse@main"

For Conda installations
^^^^^^^^^^^^^^^^^^^^^^^

.. important:: These steps apply to users who installed IDAES by running ``conda install <...>``. They **do not apply** if IDAES was installed using pip.

As of IDAES 2.1, the IDAES Conda package available already includes several dependencies that are optional for pip installations.
This difference is due to a variety of reasons, including the fact that not all optional dependencies are available as Conda packages (e.g. OMLT); or that Conda does not have an equivalent of ``extras_require`` targets to specify optional dependencies at install-time.
The consequence of this is that some optional dependencies might be unavailable if IDAES was installed using Conda.

Therefore, we encourage users who encounter issues after a Conda installation of IDAES to **try installing IDAES again using pip instead** and see if the issues are resolved.
10 changes: 5 additions & 5 deletions docs/how_to_guides/versioned_idaes_install.rst
Original file line number Diff line number Diff line change
Expand Up @@ -37,16 +37,16 @@ Installing IDAES
----------------
.. _updating_install:

* To get a previous `IDAES release <https://github.com/IDAES/idaes-pse/releases>`_, for example v1.13 ::
* To get a previous `IDAES release <https://github.com/IDAES/idaes-pse/releases>`_, for example 2.0 ::

pip install idaes-pse==1.13
pip install idaes-pse==2.0

* To get the latest version from the GitHub main branch ::
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One thing I do not see here is instructions on how to get the optional dependencies; this is one of our user facing sets of install instructions so we probably need to cover it here (or point to the README for more details and to avoid the possibility of conflicting instrucitons).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good point. I agree that the optional dependencies instructions should be included in the user-facing installation material. I think I'll copy what's in the README to the Sphinx docs, and we can revisit later if the repetition becomes a problem.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.


pip install 'idaes-pse[prerelease] @ https://github.com/IDAES/idaes-pse/archive/main.zip'
pip install "idaes-pse @ git+https://github.com/IDAES/idaes-pse@main"

* To get a specific fork or branch, for example myfork (of idaes-pse) and mybranch ::
* To get a specific fork or branch, for example for branch ``mybranch`` of the fork belonging to ``myuser`` ::

pip install 'idaes-pse[prerelease] @ https://github.com/myfork/idaes-pse/archive/mybranch.zip'
pip install "idaes-pse @ git+https://github.com/myuser/idaes-pse@mybranch"

* **For IDAES Contributors**: follow the :ref:`advanced user installation<tutorials/advanced_install/index:Advanced User Installation>`.
2 changes: 2 additions & 0 deletions docs/reference_guides/apps/grid_integration/index.rst
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
.. _idaes-grid:

.. grid_integration documentation master file, created by
sphinx-quickstart on Wed Dec 15 17:14:39 2021.
You can adapt this file completely to your liking, but it should at least
Expand Down
1 change: 0 additions & 1 deletion docs/tutorials/getting_started/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,6 @@ OS Specific Instructions
windows
mac_osx
binaries
opt_dependencies

.. list-table::
:header-rows: 1
Expand Down
3 changes: 0 additions & 3 deletions docs/tutorials/getting_started/opt_dependencies.rst

This file was deleted.

9 changes: 6 additions & 3 deletions idaes/commands/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,10 +23,13 @@
from idaes.commands.base import command_base as cb

# import all the commands
for loader, module_name, is_pkg in pkgutil.walk_packages(__path__):
if module_name == "base":
for loader, dotted_module_name, is_pkg in pkgutil.walk_packages(__path__):
module_name_parts = dotted_module_name.split(".")
module_name = module_name_parts[-1]
is_test_module = module_name.startswith("test_")
if dotted_module_name == "base":
pass
elif module_name.startswith("test"):
elif is_test_module:
pass
else:
loader.find_module(module_name).load_module(module_name)
Expand Down
108 changes: 108 additions & 0 deletions idaes/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,13 @@
# pylint: disable=missing-module-docstring
# pylint: disable=missing-function-docstring

import importlib.abc
import importlib.machinery
import sys
from typing import Dict
from typing import Iterable
from typing import List

import pytest

####
Expand Down Expand Up @@ -133,4 +139,106 @@ def pytest_configure(config):
setattr(config.option, "markexpr", "performance")


ModuleName = str


class ImportorskipLoader(importlib.abc.Loader):
"""
A wrapper class around a concrete Loader instance. If a ModuleNotFoundError is raised
during module execution and the module name matches one of the registered modules,
it is replaced with a module-level call to :func:`pyest.skip()`.
"""

def __init__(
self, wrapped: importlib.abc.Loader, skip_if_not_found: Iterable[ModuleName]
):
self._wrapped = wrapped
self.skip_if_not_found = list(skip_if_not_found)

def module_repr(self, module) -> str:
return self._wrapped.module_repr(module)

def create_module(self, spec):
return self._wrapped.create_module(spec)

def exec_module(self, module):
try:
return self._wrapped.exec_module(module)
except ModuleNotFoundError as e:
if e.name in self.skip_if_not_found:
pytest.skip(allow_module_level=True)
raise e


class ImportorskipFinder(importlib.abc.MetaPathFinder):
"""
Custom Finder class to modify import behavior for registered modules.

If inserted in sys.meta_path before the default finders, it will cause
a custom Loader to be used for registered modules.
"""

def __init__(self, registry: Dict[ModuleName, List[ModuleName]]):
self._registry = registry

def find_spec(self, *args, **kwargs):
spec = importlib.machinery.PathFinder.find_spec(*args, **kwargs)
if spec is None:
return
registered_for_skipping = self._registry.get(spec.name, None)
if registered_for_skipping:
spec.loader = ImportorskipLoader(spec.loader, registered_for_skipping)
return spec


class Importorskipper:
"""
A pytest plugin that allows automatically skipping test modules if they attempt to import
modules distributed in optional dependencies.

The functionality is similar to pytest.importorskip(), but using a centralized registry
instead of having to call importorskip() in each test module for each possibly missing module.
"""

def __init__(self, registry: Dict[ModuleName, List[ModuleName]]):
self._registry = dict(registry)
self._finder = ImportorskipFinder(self._registry)

def pytest_configure(self):
sys.meta_path.insert(0, self._finder)

def pytest_sessionfinish(self):
sys.meta_path.remove(self._finder)

def pytest_report_collectionfinish(self) -> List[str]:
preamble = [
"The following modules are registered in the importorskipper plugin",
" and will cause tests to be skipped if any of the registered modules is not found: ",
]
lines = []
for importing_mod, mods in self._registry.items():
lines.append(f"- {importing_mod}:\t{mods}")
if lines:
lines = preamble + lines
return lines


def pytest_addhooks(pluginmanager: pytest.PytestPluginManager):
skipper_plugin = Importorskipper(
{
"idaes.core.dmf": ["traitlets", "tinydb"],
# idaes.tests.test_import must be specified instead of idaes.core.dmf.util
# since colorama is not imported at the module level in idaes.core.dmf.util,
# but it is imported at the module level in idaes.tests.test_import
# when instantiating ColorTerm()
"idaes.tests.test_import": ["colorama"],
"idaes.core.surrogate.keras_surrogate": ["omlt"],
"idaes.core.ui.fsvis.tests.test_fsvis": ["requests"],
"idaes.core.ui.fsvis.tests.test_model_server": ["requests"],
}
)

pluginmanager.register(skipper_plugin, name="importorskipper")


####
4 changes: 2 additions & 2 deletions idaes/core/dmf/util.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,6 @@
from typing import Union
import yaml

# third-party
import colorama

__author__ = "Dan Gunter"

Expand Down Expand Up @@ -268,6 +266,8 @@ def __getattr__(self, a):
def __init__(self, enabled=True):
self._width = None
if enabled:
import colorama # pylint: disable=import-outside-toplevel

colorama.init(autoreset=True)
# Colorama colors and styles
F = self.Fore = colorama.Fore
Expand Down
3 changes: 2 additions & 1 deletion idaes/core/ui/fsvis/tests/test_model_server.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@
import pytest
from pyomo.environ import ConcreteModel

import requests

# pkg
from idaes.core.ui.fsvis import model_server, errors, persist
from idaes.core import FlowsheetBlock
Expand Down Expand Up @@ -86,7 +88,6 @@ def flash_model():

@pytest.mark.component
def test_flowsheet_server_run(flash_model):
import requests

srv = model_server.FlowsheetServer()
srv.start()
Expand Down
11 changes: 9 additions & 2 deletions idaes/core/util/convergence/convergence_base.py
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,6 @@ class from ConvergenceEvaluation, and implement three methods:

# idaes
import idaes.core.util.convergence.mpi_utils as mpiu
from idaes.core.dmf import resource
import idaes.logger as idaeslog
from idaes.core.solvers import get_solver

Expand Down Expand Up @@ -934,7 +933,15 @@ def to_dict(self):
def to_json(self, fp):
json.dump(self.to_dict(), fp, indent=4)

def to_dmf(self, dmf):
def to_dmf(self, dmf) -> None:
try:
# pylint: disable-next=import-outside-toplevel
from idaes.core.dmf import resource
except ImportError as err:
_log.error(
"Stats.to_dmf() failed because DMF is not available: %s", str(err)
)
return None
# PYLINT-TODO-FIX fix error due to undefined variable "stats"
rsrc = resource.Resource(
value={
Expand Down
2 changes: 1 addition & 1 deletion idaes/tests/test_headers.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,8 @@

# third-party
import pytest
import yaml

yaml = pytest.importorskip("yaml", reason="`yaml` package is not available")

addheader_add = pytest.importorskip(
"addheader.add", reason="`addheader` package is not available"
Expand Down
4 changes: 2 additions & 2 deletions pytest.ini
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
[pytest]
addopts = -W ignore
addopts = --pyargs idaes
--durations=100
testpaths = idaes
-W ignore
log_file = pytest.log
log_file_date_format = %Y-%m-%dT%H:%M:%S
log_file_format = %(asctime)s %(levelname)-7s <%(filename)s:%(lineno)d> %(message)s
Expand Down
6 changes: 3 additions & 3 deletions requirements-dev.txt
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,6 @@ jupyter_contrib_nbextensions
snowballstemmer==1.2.1
addheader>=0.2.2

# this will install IDAES in editable mode using the dependencies defined under the `prerelease` tag of `extras_require` in `setup.py`
# to customize this (e.g. to install a local clone of the Pyomo git repository), install IDAES without the `prerelease` tag and then install the dependencies separately
--editable .[prerelease,optional]
# this will install IDAES in editable mode using the dependencies defined under the `extras_require` tags defined in `setup.py`
--editable .[ui,dmf,grid,omlt,coolprop]
# to customize this (e.g. to install a local clone of the Pyomo git repository), add the desired alternate requirements below
Loading