diff --git a/.github/labels.yml b/.github/labels.yml index 0a5c4e3592..e746716168 100644 --- a/.github/labels.yml +++ b/.github/labels.yml @@ -74,6 +74,10 @@ description: Issues fixed in Ansys Mechanical version 271 color: FFDD33 +- name: fixed-in-next-release + description: Issues fixed in the upcoming Ansys Mechanical release + color: FFEE66 + - name: 24R2 description: Bug observed in PyMechanical with Mechanical version 24R2 color: 804000 diff --git a/doc/changelog.d/1564.added.md b/doc/changelog.d/1564.added.md new file mode 100644 index 0000000000..91b584000a --- /dev/null +++ b/doc/changelog.d/1564.added.md @@ -0,0 +1 @@ +Add reuse flag to override gallery flag diff --git a/doc/source/conf.py b/doc/source/conf.py index 2665ac0d4b..86aecd0742 100644 --- a/doc/source/conf.py +++ b/doc/source/conf.py @@ -22,7 +22,8 @@ import ansys.mechanical.core as pymechanical from ansys.mechanical.core.embedding.initializer import SUPPORTED_MECHANICAL_EMBEDDING_VERSIONS -# necessary when building the sphinx gallery +# Documentation gallery: enables Sphinx-Gallery and embedded ``App`` instance +# reuse across gallery scripts. Per-constructor opt-out: ``App(..., reuse_instance=True)``. pymechanical.BUILDING_GALLERY = True # Ensure that offscreen rendering is used for docs generation diff --git a/doc/source/user_guide/embedding/globals.rst b/doc/source/user_guide/embedding/globals.rst index 6d425defd0..2bc4f9bbcc 100644 --- a/doc/source/user_guide/embedding/globals.rst +++ b/doc/source/user_guide/embedding/globals.rst @@ -43,3 +43,4 @@ not for the ``globals`` argument of the ``App`` class: app = App() app.update_globals(globals(), False) + diff --git a/src/ansys/mechanical/core/__init__.py b/src/ansys/mechanical/core/__init__.py index c3ceb080e7..dfd6dfae30 100644 --- a/src/ansys/mechanical/core/__init__.py +++ b/src/ansys/mechanical/core/__init__.py @@ -76,7 +76,19 @@ from ansys.mechanical.core.pool import LocalMechanicalPool BUILDING_GALLERY = False -"""Whether or not to build gallery examples.""" +"""Control documentation gallery behavior and embedded-app instance sharing. + +When ``True``: + +- Sphinx enables the example gallery during documentation builds (see ``doc/source/conf.py``). +- For the embedded :class:`~ansys.mechanical.core.embedding.app.App`, multiple + ``App()`` constructions reuse one underlying Mechanical instance so examples can + run without exhausting resources. + +To opt out of embedded instance sharing for a single ``App`` while leaving this +flag unchanged (for example when ``BUILDING_GALLERY`` is set globally), pass +``reuse_instance=True`` to the constructor. +""" __all__ = [ "__version__", diff --git a/src/ansys/mechanical/core/embedding/app.py b/src/ansys/mechanical/core/embedding/app.py index 613ed3c8b8..a9fe44f321 100644 --- a/src/ansys/mechanical/core/embedding/app.py +++ b/src/ansys/mechanical/core/embedding/app.py @@ -193,6 +193,13 @@ class App: feature_flags : list, optional List of feature flag names to enable. Default is []. Available flags include: 'ThermalShells', 'MultistageHarmonic', 'CPython'. + reuse_instance : bool, optional + When ``True``, gallery instance sharing is skipped for this constructor, + so initialization follows the same path as when the module-level + ``BUILDING_GALLERY`` flag is ``False``. Use this to opt out of gallery + sharing without assigning ``pymechanical.BUILDING_GALLERY = False`` (for + example when the flag is set globally for documentation builds). + Default is ``False``. Examples -------- @@ -236,12 +243,16 @@ def __init__( self, db_file: str | None = None, private_appdata: bool = False, + *, + reuse_instance: bool = False, **kwargs: typing.Any, ) -> None: """Construct an instance of the mechanical Application.""" global INSTANCES from ansys.mechanical.core import BUILDING_GALLERY + use_gallery_sharing = BUILDING_GALLERY and not reuse_instance + self._enable_logging = kwargs.get("enable_logging", True) if self._enable_logging: self._log = LOG @@ -270,7 +281,7 @@ def __init__( # If the building gallery flag is set, we need to share the instance # This can apply to running the `make -C doc html` command - if BUILDING_GALLERY: + if use_gallery_sharing: if len(INSTANCES) != 0: # Get the first instance of the app instance: App = INSTANCES[0] @@ -668,7 +679,8 @@ def _share(self, other) -> None: """Shares the state of self with other. Other is another instance of App. - This is used when the BUILDING_GALLERY flag is on. + This is used when the BUILDING_GALLERY flag is on and the constructor + was not called with ``reuse_instance=True``. In that mode, multiple instance of App are used, but they all point to the same underlying application object. Because of that, special care needs to be diff --git a/tests/embedding/test_app.py b/tests/embedding/test_app.py index fdd121ae5d..37bfefe6b7 100644 --- a/tests/embedding/test_app.py +++ b/tests/embedding/test_app.py @@ -325,7 +325,8 @@ def test_building_gallery(pytestconfig, run_subprocess, rootdir): """Test for building gallery check. When building the gallery, each example file creates another instance of the app. - When the BUILDING_GALLERY flag is enabled, only one instance is kept. + When the BUILDING_GALLERY flag is enabled, only one instance is kept (unless a + constructor uses ``reuse_instance=True`` to opt out of sharing). This is to test the bug fixed in https://github.com/ansys/pymechanical/pull/784 and will fail on PyMechanical version 0.11.0 """ @@ -350,6 +351,16 @@ def test_building_gallery(pytestconfig, run_subprocess, rootdir): assert "Multiple App launched with building gallery flag on" in stdout +@pytest.mark.embedding_scripts +def test_reuse_instance_bypasses_gallery_sharing(pytestconfig, run_subprocess, rootdir): + """When BUILDING_GALLERY is True, ``reuse_instance=True`` skips the sharing path.""" + version = pytestconfig.getoption("ansys_version") + script = Path(rootdir) / "tests" / "scripts" / "reuse_instance_test.py" + _process, stdout, stderr = run_subprocess([sys.executable, str(script), version]) + stdout = stdout.decode() + assert "reuse_instance bypassed gallery sharing as expected" in stdout + + @pytest.mark.embedding def test_shims_import_material(embedded_app, assets): """Test deprecation warning for shims import material.""" diff --git a/tests/embedding/test_globals.py b/tests/embedding/test_globals.py index 0e9535cfb4..52a19fe5ef 100644 --- a/tests/embedding/test_globals.py +++ b/tests/embedding/test_globals.py @@ -111,6 +111,7 @@ def test_globals_kwarg_building_gallery(run_subprocess, pytestconfig, rootdir): Test ViewOrientationType exists and messages are printed when BUILDING_GALLERY is True and globals are updated during the app initialization. + (Gallery instance sharing can be skipped per ``App`` via ``reuse_instance=True``.) """ version = pytestconfig.getoption("ansys_version") embedded_py = Path(rootdir) / "tests" / "scripts" / "run_embedded_app.py" diff --git a/tests/scripts/reuse_instance_test.py b/tests/scripts/reuse_instance_test.py new file mode 100644 index 0000000000..de19ba96f5 --- /dev/null +++ b/tests/scripts/reuse_instance_test.py @@ -0,0 +1,43 @@ +# Copyright (C) 2022 - 2026 ANSYS, Inc. and/or its affiliates. +# SPDX-License-Identifier: MIT +# +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. + +"""Exercise App(reuse_instance=...) when BUILDING_GALLERY is True.""" + +import sys + +import ansys.mechanical.core as pymechanical + + +if __name__ == "__main__": + version = int(sys.argv[1]) + pymechanical.BUILDING_GALLERY = True + + _ = pymechanical.App(version=version) + try: + pymechanical.App(version=version, reuse_instance=True) + except RuntimeError as exc: + if "Cannot have more than one embedded mechanical instance" in str(exc): + print("reuse_instance bypassed gallery sharing as expected") + else: + raise + else: + raise AssertionError("Expected RuntimeError for second App with reuse_instance=True")