Skip to content

Commit

Permalink
fix unit tests for 2023.2.pre1 server and add License context manager (
Browse files Browse the repository at this point in the history
…#846)

* fix unit tests for 2023.2.pre1 server

* add license context manager

* Apply suggestions from code review

Co-authored-by: JennaPaikowsky <98607744+JennaPaikowsky@users.noreply.github.com>

* fixes

* _test suffix

* fix version

* doc string

* update dev requirements

* test

* fix

* fix unit test

* test post

---------

Co-authored-by: JennaPaikowsky <98607744+JennaPaikowsky@users.noreply.github.com>
  • Loading branch information
cbellot000 and JennaPaikowsky authored Mar 22, 2023
1 parent 1e0fd71 commit a24aae7
Show file tree
Hide file tree
Showing 7 changed files with 229 additions and 35 deletions.
12 changes: 6 additions & 6 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ on:
standalone_branch_suffix:
description: 'Suffix of the branch on standalone'
required: false
default: ''
default: '_test'


concurrency:
Expand Down Expand Up @@ -58,7 +58,7 @@ jobs:
python_versions: '["3.8"]'
wheel: true
wheelhouse: false
standalone_suffix: ${{ github.event.inputs.standalone_branch_suffix || '' }}
standalone_suffix: ${{ github.event.inputs.standalone_branch_suffix || '_test' }}
custom-requirements: requirements/requirements_dev.txt
custom-wheels: './dpf-standalone/v232/dist'
secrets: inherit
Expand All @@ -67,7 +67,7 @@ jobs:
name: "Build and Test on Docker"
uses: ./.github/workflows/test_docker.yml
with:
standalone_suffix: ${{ github.event.inputs.standalone_branch_suffix || '' }}
standalone_suffix: ${{ github.event.inputs.standalone_branch_suffix || '_test' }}
custom-requirements: requirements/requirements_dev.txt
custom-wheels: 'dpf-standalone/dist'
secrets: inherit
Expand All @@ -77,7 +77,7 @@ jobs:
uses: ./.github/workflows/docs.yml
with:
ANSYS_VERSION: "232"
standalone_suffix: ${{ github.event.inputs.standalone_branch_suffix || '' }}
standalone_suffix: ${{ github.event.inputs.standalone_branch_suffix || '_test' }}
custom-requirements: requirements/requirements_dev.txt
custom-wheels: './dpf-standalone/v232/dist'
event_name: ${{ github.event_name }}
Expand All @@ -100,7 +100,7 @@ jobs:
with:
ANSYS_VERSION: "232"
python_versions: '["3.8"]'
standalone_suffix: ${{ github.event.inputs.standalone_branch_suffix || '' }}
standalone_suffix: ${{ github.event.inputs.standalone_branch_suffix || '_test' }}
custom-requirements: requirements/requirements_dev.txt
custom-wheels: './dpf-standalone/v232/dist'
secrets: inherit
Expand Down Expand Up @@ -144,7 +144,7 @@ jobs:
uses: ./.github/workflows/pydpf-post.yml
with:
ANSYS_VERSION: "232"
standalone_suffix: ${{ github.event.inputs.standalone_branch_suffix || '' }}
standalone_suffix: ${{ github.event.inputs.standalone_branch_suffix || '_test' }}
custom-requirements: requirements/requirements_dev.txt
custom-wheels: './dpf-standalone/v232/dist'
secrets: inherit
6 changes: 3 additions & 3 deletions requirements/requirements_dev.txt
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
ansys-dpf-gate==0.3.1.dev0
ansys-dpf-gatebin==0.3.1.dev0
ansys-grpc-dpf==0.7.1.dev0
ansys-dpf-gate==0.3.2.dev0
ansys-dpf-gatebin==0.3.2.dev0
ansys-grpc-dpf==0.7.2.dev0
6 changes: 5 additions & 1 deletion src/ansys/dpf/core/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,11 @@
from ansys.dpf.core import path_utilities
from ansys.dpf.core import settings
from ansys.dpf.core.server_factory import ServerConfig, AvailableServerConfigs
from ansys.dpf.core.server_context import set_default_server_context, AvailableServerContexts
from ansys.dpf.core.server_context import (
set_default_server_context,
AvailableServerContexts,
LicenseContextManager
)
from ansys.dpf.core.unit_system import UnitSystem, unit_systems

# for matplotlib
Expand Down
15 changes: 15 additions & 0 deletions src/ansys/dpf/core/runtime_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -182,3 +182,18 @@ def num_threads(self):
@num_threads.setter
def num_threads(self, value):
self._data_tree.add(num_threads=int(value))

@property
def license_timeout_in_seconds(self):
"""Sets the default number of threads to use for all operators,
default is omp_get_num_threads.
Returns
-------
float
"""
return self._data_tree.get_as("license_timeout_in_seconds", types.double)

@license_timeout_in_seconds.setter
def license_timeout_in_seconds(self, value):
self._data_tree.add(license_timeout_in_seconds=float(value))
108 changes: 107 additions & 1 deletion src/ansys/dpf/core/server_context.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@
import os
import warnings
from enum import Enum
from ansys.dpf.core import dpf_operator
from ansys.dpf.core import errors


class LicensingContextType(Enum):
Expand All @@ -38,8 +40,112 @@ def same_licensing_context(first, second):
return True


class LicenseContextManager:
"""Can optionally be used to check out a license before using licensed DPF Operators.
Improves performance if you are using multiple Operators that require licensing.
It can also be used to force checkout before running a script when few
Ansys license increments are available.
The license is checked in when the the object is deleted.
Parameters
----------
increment_name: str, optional
License increment to check out. To improve script efficiency, this license increment
should be consistent with the increments required by the following Operators. If ``None``,
the first available increment of this
`list <https://dpf.docs.pyansys.com/version/dev/user_guide/getting_started_with_dpf_server.
html#ansys-licensing>`_
is checked out.
license_timeout_in_seconds: float, optional
If an increment is not available by the maximum time set here, check out fails. Default is:
:py:func:`ansys.dpf.core.runtime_config.RuntimeCoreConfig.license_timeout_in_seconds`
server : server.DPFServer, optional
Server with the channel connected to the remote or local instance. The
default is ``None``, in which case an attempt is made to use the global
server.
Examples
--------
Using a context manager
>>> from ansys.dpf import core as dpf
>>> dpf.set_default_server_context(dpf.AvailableServerContexts.premium)
>>> field = dpf.Field()
>>> field.append([0., 0., 0.], 1)
>>> op = dpf.operators.filter.field_high_pass()
>>> op.inputs.field(field)
>>> op.inputs.threshold(0.0)
>>> with dpf.LicenseContextManager() as lic:
... out = op.outputs.field()
Using an instance
>>> lic = dpf.LicenseContextManager()
>>> op.inputs.field(field)
>>> op.inputs.threshold(0.0)
>>> out = op.outputs.field()
>>> lic = None
Using a context manager and choosing license options
>>> op.inputs.field(field)
>>> op.inputs.threshold(0.0)
>>> out = op.outputs.field()
>>> op = dpf.operators.filter.field_high_pass()
>>> op.inputs.field(field)
>>> op.inputs.threshold(0.0)
>>> with dpf.LicenseContextManager(
... increment_name="preppost", license_timeout_in_seconds=1.) as lic:
... out = op.outputs.field()
Notes
-----
Available from 6.1 server version.
"""

def __init__(
self, increment_name: str = None, license_timeout_in_seconds: float = None, server=None
):
from ansys.dpf.core import server as server_module

self._server = server_module.get_or_create_server(server)
if not self._server.meet_version("6.1"):
raise errors.DpfVersionNotSupported("6.1")
self._license_checkout_operator = dpf_operator.Operator(
"license_checkout", server=self._server
)
if increment_name is not None:
self._license_checkout_operator.connect(0, increment_name)
if license_timeout_in_seconds is not None:
self._license_checkout_operator.connect(1, license_timeout_in_seconds)
self._license_checkout_operator.run()

def release_data(self):
"""Release the data."""
self._license_checkout_operator = None

def __enter__(self):
return self

def __exit__(self, type, value, tb):
if tb is None:
self.release_data()

def __del__(self):
self.release_data()
pass

@property
def status(self):
"""Returns a string with the list of checked out increments
Returns
-------
str
"""
status_operator = dpf_operator.Operator("license_status", server=self._server)
return status_operator.eval()


class ServerContext:
"""The context allows to choose which capabilities are available server side.
"""The context allows you to choose which capabilities are available server side.
xml_path argument won't be taken into account if using LicensingContextType.entry.
Parameters
Expand Down
6 changes: 6 additions & 0 deletions tests/test_data_tree.py
Original file line number Diff line number Diff line change
Expand Up @@ -298,6 +298,12 @@ def test_runtime_core_config(server_type):
assert num_threads == 4
core_config.num_threads = num_threads_init
assert core_config.num_threads == num_threads_init
timeout_init = core_config.license_timeout_in_seconds
core_config.license_timeout_in_seconds = 4.0
license_timeout_in_seconds = core_config.license_timeout_in_seconds
assert license_timeout_in_seconds == 4.0
core_config.license_timeout_in_seconds = timeout_init
assert core_config.license_timeout_in_seconds == timeout_init


@conftest.raises_for_servers_version_under("4.0")
Expand Down
111 changes: 87 additions & 24 deletions tests/test_service.py
Original file line number Diff line number Diff line change
Expand Up @@ -489,7 +489,7 @@ def test_context_environment_variable(reset_context_environment_variable):
@pytest.mark.order(1)
@pytest.mark.skipif(
running_docker
or os.environ.get("ANSYS_DPF_ACCEPT_LA", None) is None
or os.environ.get("ANSYS_DPF_ACCEPT_LA", "") is ""
or not conftest.SERVERS_VERSION_GREATER_THAN_OR_EQUAL_TO_6_0,
reason="Tests ANSYS_DPF_ACCEPT_LA",
)
Expand All @@ -509,7 +509,7 @@ def test_license_agr(set_context_back_to_premium):

@pytest.mark.order(2)
@pytest.mark.skipif(
os.environ.get("ANSYS_DPF_ACCEPT_LA", None) is None
os.environ.get("ANSYS_DPF_ACCEPT_LA", "") is ""
or not conftest.SERVERS_VERSION_GREATER_THAN_OR_EQUAL_TO_6_0,
reason="Tests ANSYS_DPF_ACCEPT_LA",
)
Expand Down Expand Up @@ -537,22 +537,33 @@ def test_apply_context(set_context_back_to_premium):
# in process server, otherwise premium operators will already be loaded.
dpf.core.server.shutdown_all_session_servers()
dpf.core.SERVER_CONFIGURATION = dpf.core.AvailableServerConfigs.InProcessServer

field = dpf.core.Field()
field.append([0.0], 1)
if conftest.SERVERS_VERSION_GREATER_THAN_OR_EQUAL_TO_6_0:
with pytest.raises(KeyError):
dpf.core.Operator("core::field::high_pass")
with pytest.raises(dpf.core.errors.DPFServerException):
if dpf.core.SERVER.os == "nt":
dpf.core.load_library("Ans.Dpf.Math.dll", "math_operators")
else:
dpf.core.load_library("libAns.Dpf.Math.so", "math_operators")
if conftest.SERVERS_VERSION_GREATER_THAN_OR_EQUAL_TO_6_1:
with pytest.raises(dpf.core.errors.DPFServerException):
op = dpf.core.Operator("core::field::high_pass")
op.connect(0, field)
op.connect(1, 0.0)
op.eval()
else:
with pytest.raises(KeyError):
dpf.core.Operator("core::field::high_pass")
with pytest.raises(dpf.core.errors.DPFServerException):
if dpf.core.SERVER.os == "nt":
dpf.core.load_library("Ans.Dpf.Math.dll", "math_operators")
else:
dpf.core.load_library("libAns.Dpf.Math.so", "math_operators")
assert dpf.core.SERVER.context == dpf.core.AvailableServerContexts.entry
else:
dpf.core.start_local_server()

dpf.core.set_default_server_context(dpf.core.AvailableServerContexts.premium)
assert dpf.core.SERVER.context == dpf.core.AvailableServerContexts.premium
dpf.core.Operator("core::field::high_pass")
op = dpf.core.Operator("core::field::high_pass")
op.connect(0, field)
op.connect(1, 0.0)
op.eval()
with pytest.raises(dpf.core.errors.DPFServerException):
dpf.core.set_default_server_context(dpf.core.AvailableServerContexts.entry)
with pytest.raises(dpf.core.errors.DPFServerException):
Expand All @@ -568,26 +579,43 @@ def test_apply_context(set_context_back_to_premium):
def test_apply_context_remote(remote_config_server_type, set_context_back_to_premium):
dpf.core.server.shutdown_all_session_servers()
dpf.core.SERVER_CONFIGURATION = remote_config_server_type
field = dpf.core.Field()
field.append([0.0], 1)
if conftest.SERVERS_VERSION_GREATER_THAN_OR_EQUAL_TO_6_0:
with pytest.raises(dpf.core.errors.DPFServerException):
dpf.core.Operator("core::field::high_pass")
with pytest.raises(dpf.core.errors.DPFServerException):
if dpf.core.SERVER.os == "nt":
dpf.core.load_library("Ans.Dpf.Math.dll", "math_operators")
else:
dpf.core.load_library("libAns.Dpf.Math.so", "math_operators")

assert dpf.core.SERVER.context == dpf.core.AvailableServerContexts.entry
if conftest.SERVERS_VERSION_GREATER_THAN_OR_EQUAL_TO_6_1:
with pytest.raises(dpf.core.errors.DPFServerException):
op = dpf.core.Operator("core::field::high_pass")
op.connect(0, field)
op.connect(1, 0.0)
op.eval()
else:
with pytest.raises(dpf.core.errors.DPFServerException):
op = dpf.core.Operator("core::field::high_pass")
with pytest.raises(dpf.core.errors.DPFServerException):
if dpf.core.SERVER.os == "nt":
dpf.core.load_library("Ans.Dpf.Math.dll", "math_operators")
else:
dpf.core.load_library("libAns.Dpf.Math.so", "math_operators")
assert dpf.core.SERVER.context == dpf.core.AvailableServerContexts.entry
else:
dpf.core.start_local_server()

dpf.core.SERVER.apply_context(dpf.core.AvailableServerContexts.premium)
dpf.core.Operator("core::field::high_pass")
op = dpf.core.Operator("core::field::high_pass")
op.connect(0, field)
op.connect(1, 0.0)
op.eval()
assert dpf.core.SERVER.context == dpf.core.AvailableServerContexts.premium

dpf.core.server.shutdown_all_session_servers()
with pytest.raises(dpf.core.errors.DPFServerException):
dpf.core.Operator("core::field::high_pass")
if conftest.SERVERS_VERSION_GREATER_THAN_OR_EQUAL_TO_6_1:
with pytest.raises(dpf.core.errors.DPFServerException):
op = dpf.core.Operator("core::field::high_pass")
op.connect(0, field)
op.connect(1, 0.0)
op.eval()
elif conftest.SERVERS_VERSION_GREATER_THAN_OR_EQUAL_TO_6_0:
with pytest.raises(dpf.core.errors.DPFServerException):
op = dpf.core.Operator("core::field::high_pass")
dpf.core.set_default_server_context(dpf.core.AvailableServerContexts.premium)
dpf.core.Operator("core::field::high_pass")
with pytest.raises(dpf.core.errors.DPFServerException):
Expand All @@ -612,5 +640,40 @@ def test_release_dpf(server_type):
dpf.core.Operator("expansion::modal_superposition", server=server_type)


@conftest.raises_for_servers_version_under("6.1")
def test_license_context_manager_as_context():
field = dpf.core.Field()
field.append([0.0, 0.0, 0.0], 1)
op = dpf.core.operators.filter.field_high_pass()
op.inputs.field(field)
op.inputs.threshold(0.0)
with dpf.core.LicenseContextManager() as lic:
out = op.outputs.field()
st = lic.status

assert len(st) != 0
new_st = lic.status
assert new_st == ""
lic = dpf.core.LicenseContextManager()
op.inputs.field(field)
op.inputs.threshold(0.0)
out = op.outputs.field()
new_st = lic.status
assert str(new_st) == str(st)
lic = None

op = dpf.core.operators.filter.field_high_pass()
op.inputs.field(field)
op.inputs.threshold(0.0)
with dpf.core.LicenseContextManager(
increment_name="ansys", license_timeout_in_seconds=1.0
) as lic:
out = op.outputs.field()
st = lic.status
assert "ansys" in st
st = lic.status
assert "ansys" not in st


if __name__ == "__main__":
test_load_api_with_awp_root()

0 comments on commit a24aae7

Please sign in to comment.