From 9dfed28aed7caf34d03fde8343b30cf61eaf3313 Mon Sep 17 00:00:00 2001 From: Anastasia Kuporosova Date: Tue, 26 Sep 2023 21:38:53 +0200 Subject: [PATCH] [PyOV] Make openvino properties behave as python property object (#20007) * properties as property * working prototype * another attempt * fix for test * cosmetic changes * generate properties * test upste * add comments * update submodules * apply comments --- .../src/openvino/properties/__init__.py | 22 +----- .../src/openvino/properties/_properties.py | 55 ++++++++++++++ .../openvino/properties/device/__init__.py | 14 +--- .../src/openvino/properties/hint/__init__.py | 13 +--- .../properties/intel_auto/__init__.py | 7 +- .../openvino/properties/intel_cpu/__init__.py | 6 +- .../openvino/properties/intel_gpu/__init__.py | 9 +-- .../properties/intel_gpu/hint/__init__.py | 7 +- .../src/openvino/properties/log/__init__.py | 4 +- .../openvino/properties/streams/__init__.py | 8 +- .../tests/test_runtime/test_properties.py | 74 ++++++++++--------- 11 files changed, 124 insertions(+), 95 deletions(-) create mode 100644 src/bindings/python/src/openvino/properties/_properties.py diff --git a/src/bindings/python/src/openvino/properties/__init__.py b/src/bindings/python/src/openvino/properties/__init__.py index a9fa5a0dac4..b611cf3a085 100644 --- a/src/bindings/python/src/openvino/properties/__init__.py +++ b/src/bindings/python/src/openvino/properties/__init__.py @@ -6,25 +6,9 @@ from openvino._pyopenvino.properties import Affinity # Properties -from openvino._pyopenvino.properties import enable_profiling -from openvino._pyopenvino.properties import cache_dir -from openvino._pyopenvino.properties import auto_batch_timeout -from openvino._pyopenvino.properties import num_streams -from openvino._pyopenvino.properties import inference_num_threads -from openvino._pyopenvino.properties import compilation_num_threads -from openvino._pyopenvino.properties import affinity -from openvino._pyopenvino.properties import force_tbb_terminate -from openvino._pyopenvino.properties import enable_mmap -from openvino._pyopenvino.properties import supported_properties -from openvino._pyopenvino.properties import available_devices -from openvino._pyopenvino.properties import model_name -from openvino._pyopenvino.properties import optimal_number_of_infer_requests -from openvino._pyopenvino.properties import range_for_streams -from openvino._pyopenvino.properties import optimal_batch_size -from openvino._pyopenvino.properties import max_batch_size -from openvino._pyopenvino.properties import range_for_async_infer_requests -from openvino._pyopenvino.properties import execution_devices -from openvino._pyopenvino.properties import loaded_from_cache +import openvino._pyopenvino.properties as __properties +from openvino.properties._properties import __make_properties +__make_properties(__properties, __name__) # Submodules from openvino.runtime.properties import hint diff --git a/src/bindings/python/src/openvino/properties/_properties.py b/src/bindings/python/src/openvino/properties/_properties.py new file mode 100644 index 00000000000..dfabe6a7b41 --- /dev/null +++ b/src/bindings/python/src/openvino/properties/_properties.py @@ -0,0 +1,55 @@ +# -*- coding: utf-8 -*- +# Copyright (C) 2018-2023 Intel Corporation +# SPDX-License-Identifier: Apache-2.0 + +import sys +from types import BuiltinFunctionType, ModuleType +from typing import Callable, Any, Union + + +class Property(str): + """This class allows to make a string object callable. Call returns underlying string's data.""" + def __new__(cls, prop: Callable[..., Any]): # type: ignore + instance = super().__new__(cls, prop()) + instance.prop = prop + return instance + + def __call__(self, *args: Any) -> Callable[..., Any]: + if args is not None: + return self.prop(*args) + return self.prop() + + +def __append_property_to_module(func: Callable[..., Any], target_module_name: str) -> None: + """Modifies the target module's __getattr__ method to expose a python property wrapper by the function's name. + + :param func: the function which will be transformed to behave as python property. + :param target_module_name: the name of the module to which properties are added. + """ + module = sys.modules[target_module_name] + + def base_getattr(name: str) -> None: + raise AttributeError( + f"Module '{module.__name__}' doesn't have the attribute with name '{name}'.") + + getattr_old = getattr(module, "__getattr__", base_getattr) + + def getattr_new(name: str) -> Union[Callable[..., Any], Any]: + if func.__name__ == name: + return Property(func) + else: + return getattr_old(name) + + module.__getattr__ = getattr_new # type: ignore + + +def __make_properties(source_module_of_properties: ModuleType, target_module_name: str) -> None: + """Makes python properties in target module from functions found in the source module. + + :param source_module_of_properties: the source module from which functions should be taken. + :param target_module_name: the name of the module to which properties are added. + """ + for attr in dir(source_module_of_properties): + func = getattr(source_module_of_properties, attr) + if isinstance(func, BuiltinFunctionType): + __append_property_to_module(func, target_module_name) diff --git a/src/bindings/python/src/openvino/properties/device/__init__.py b/src/bindings/python/src/openvino/properties/device/__init__.py index 5729dd10123..3fd42834197 100644 --- a/src/bindings/python/src/openvino/properties/device/__init__.py +++ b/src/bindings/python/src/openvino/properties/device/__init__.py @@ -6,17 +6,9 @@ from openvino._pyopenvino.properties.device import Type # Properties -from openvino._pyopenvino.properties.device import priorities -from openvino._pyopenvino.properties.device import id -from openvino._pyopenvino.properties.device import full_name -from openvino._pyopenvino.properties.device import architecture -from openvino._pyopenvino.properties.device import type -from openvino._pyopenvino.properties.device import gops -from openvino._pyopenvino.properties.device import thermal -from openvino._pyopenvino.properties.device import capabilities -from openvino._pyopenvino.properties.device import uuid -from openvino._pyopenvino.properties.device import luid -from openvino._pyopenvino.properties.device import properties +import openvino._pyopenvino.properties.device as __device +from openvino.properties._properties import __make_properties +__make_properties(__device, __name__) # Classes from openvino._pyopenvino.properties.device import Capability diff --git a/src/bindings/python/src/openvino/properties/hint/__init__.py b/src/bindings/python/src/openvino/properties/hint/__init__.py index 9b4fdd0d752..b218ee7b2a3 100644 --- a/src/bindings/python/src/openvino/properties/hint/__init__.py +++ b/src/bindings/python/src/openvino/properties/hint/__init__.py @@ -9,13 +9,6 @@ from openvino._pyopenvino.properties.hint import ExecutionMode from openvino.runtime.properties.hint.overloads import PerformanceMode # Properties -from openvino._pyopenvino.properties.hint import inference_precision -from openvino._pyopenvino.properties.hint import model_priority -from openvino._pyopenvino.properties.hint import performance_mode -from openvino._pyopenvino.properties.hint import enable_cpu_pinning -from openvino._pyopenvino.properties.hint import scheduling_core_type -from openvino._pyopenvino.properties.hint import enable_hyper_threading -from openvino._pyopenvino.properties.hint import execution_mode -from openvino._pyopenvino.properties.hint import num_requests -from openvino._pyopenvino.properties.hint import model -from openvino._pyopenvino.properties.hint import allow_auto_batching +import openvino._pyopenvino.properties.hint as __hint +from openvino.properties._properties import __make_properties +__make_properties(__hint, __name__) diff --git a/src/bindings/python/src/openvino/properties/intel_auto/__init__.py b/src/bindings/python/src/openvino/properties/intel_auto/__init__.py index a9a7e450794..2d8a52ac109 100644 --- a/src/bindings/python/src/openvino/properties/intel_auto/__init__.py +++ b/src/bindings/python/src/openvino/properties/intel_auto/__init__.py @@ -2,6 +2,7 @@ # Copyright (C) 2018-2023 Intel Corporation # SPDX-License-Identifier: Apache-2.0 -from openvino._pyopenvino.properties.intel_auto import device_bind_buffer -from openvino._pyopenvino.properties.intel_auto import enable_startup_fallback -from openvino._pyopenvino.properties.intel_auto import enable_runtime_fallback +# Properties +import openvino._pyopenvino.properties.intel_auto as __intel_auto +from openvino.properties._properties import __make_properties +__make_properties(__intel_auto, __name__) diff --git a/src/bindings/python/src/openvino/properties/intel_cpu/__init__.py b/src/bindings/python/src/openvino/properties/intel_cpu/__init__.py index 2b947dc960e..7b13195261e 100644 --- a/src/bindings/python/src/openvino/properties/intel_cpu/__init__.py +++ b/src/bindings/python/src/openvino/properties/intel_cpu/__init__.py @@ -2,5 +2,7 @@ # Copyright (C) 2018-2023 Intel Corporation # SPDX-License-Identifier: Apache-2.0 -from openvino._pyopenvino.properties.intel_cpu import denormals_optimization -from openvino._pyopenvino.properties.intel_cpu import sparse_weights_decompression_rate +# Properties +import openvino._pyopenvino.properties.intel_cpu as __intel_cpu +from openvino.properties._properties import __make_properties +__make_properties(__intel_cpu, __name__) diff --git a/src/bindings/python/src/openvino/properties/intel_gpu/__init__.py b/src/bindings/python/src/openvino/properties/intel_gpu/__init__.py index 0cfa6dde23d..6cb43927241 100644 --- a/src/bindings/python/src/openvino/properties/intel_gpu/__init__.py +++ b/src/bindings/python/src/openvino/properties/intel_gpu/__init__.py @@ -3,12 +3,9 @@ # SPDX-License-Identifier: Apache-2.0 # Properties -from openvino._pyopenvino.properties.intel_gpu import device_total_mem_size -from openvino._pyopenvino.properties.intel_gpu import uarch_version -from openvino._pyopenvino.properties.intel_gpu import execution_units_count -from openvino._pyopenvino.properties.intel_gpu import memory_statistics -from openvino._pyopenvino.properties.intel_gpu import enable_loop_unrolling -from openvino._pyopenvino.properties.intel_gpu import disable_winograd_convolution +import openvino._pyopenvino.properties.intel_gpu as __intel_gpu +from openvino.properties._properties import __make_properties +__make_properties(__intel_gpu, __name__) # Classes from openvino._pyopenvino.properties.intel_gpu import MemoryType diff --git a/src/bindings/python/src/openvino/properties/intel_gpu/hint/__init__.py b/src/bindings/python/src/openvino/properties/intel_gpu/hint/__init__.py index 78b18bdf477..af54a90c6e6 100644 --- a/src/bindings/python/src/openvino/properties/intel_gpu/hint/__init__.py +++ b/src/bindings/python/src/openvino/properties/intel_gpu/hint/__init__.py @@ -3,10 +3,9 @@ # SPDX-License-Identifier: Apache-2.0 # Properties -from openvino._pyopenvino.properties.intel_gpu.hint import queue_throttle -from openvino._pyopenvino.properties.intel_gpu.hint import queue_priority -from openvino._pyopenvino.properties.intel_gpu.hint import host_task_priority -from openvino._pyopenvino.properties.intel_gpu.hint import available_device_mem +import openvino._pyopenvino.properties.intel_gpu.hint as __hint +from openvino.properties._properties import __make_properties +__make_properties(__hint, __name__) # Classes from openvino._pyopenvino.properties.intel_gpu.hint import ThrottleLevel diff --git a/src/bindings/python/src/openvino/properties/log/__init__.py b/src/bindings/python/src/openvino/properties/log/__init__.py index bf29a7a70e7..9295f5b11fa 100644 --- a/src/bindings/python/src/openvino/properties/log/__init__.py +++ b/src/bindings/python/src/openvino/properties/log/__init__.py @@ -6,4 +6,6 @@ from openvino._pyopenvino.properties.log import Level # Properties -from openvino._pyopenvino.properties.log import level +import openvino._pyopenvino.properties.log as __log +from openvino.properties._properties import __make_properties +__make_properties(__log, __name__) diff --git a/src/bindings/python/src/openvino/properties/streams/__init__.py b/src/bindings/python/src/openvino/properties/streams/__init__.py index e1ce0b19044..457d6c88f70 100644 --- a/src/bindings/python/src/openvino/properties/streams/__init__.py +++ b/src/bindings/python/src/openvino/properties/streams/__init__.py @@ -2,8 +2,10 @@ # Copyright (C) 2018-2023 Intel Corporation # SPDX-License-Identifier: Apache-2.0 -# Properties -from openvino._pyopenvino.properties.streams import num - # Classes from openvino._pyopenvino.properties.streams import Num + +# Properties +import openvino._pyopenvino.properties.streams as __streams +from openvino.properties._properties import __make_properties +__make_properties(__streams, __name__) diff --git a/src/bindings/python/tests/test_runtime/test_properties.py b/src/bindings/python/tests/test_runtime/test_properties.py index e11afaf5a84..cb70887fb19 100644 --- a/src/bindings/python/tests/test_runtime/test_properties.py +++ b/src/bindings/python/tests/test_runtime/test_properties.py @@ -1,11 +1,12 @@ # -*- coding: utf-8 -*- -# Copyright (C) 2022 Intel Corporation +# Copyright (C) 2018-2023 Intel Corporation # SPDX-License-Identifier: Apache-2.0 import pytest import numpy as np import os +import openvino as ov import openvino.properties as props import openvino.properties.hint as hints import openvino.properties.intel_cpu as intel_cpu @@ -28,7 +29,7 @@ def test_properties_ro_base(): def test_properties_rw_base(): - assert props.cache_dir() == "CACHE_DIR" + assert ov.properties.cache_dir == "CACHE_DIR" assert props.cache_dir("./test_dir") == ("CACHE_DIR", OVAny("./test_dir")) with pytest.raises(TypeError) as e: @@ -181,6 +182,7 @@ def test_conflicting_enum(proxy_enums, expected_values): def test_properties_ro(ov_property_ro, expected_value): # Test if property is correctly registered assert ov_property_ro() == expected_value + assert ov_property_ro == expected_value ### @@ -369,6 +371,7 @@ def test_properties_ro(ov_property_ro, expected_value): def test_properties_rw(ov_property_rw, expected_value, test_values): # Test if property is correctly registered assert ov_property_rw() == expected_value + assert ov_property_rw == expected_value # Test if property process values correctly for values in test_values: @@ -381,7 +384,7 @@ def test_properties_rw(ov_property_rw, expected_value, test_values): # Special cases ### def test_properties_device_priorities(): - assert device.priorities() == "MULTI_DEVICE_PRIORITIES" + assert device.priorities == "MULTI_DEVICE_PRIORITIES" assert device.priorities("CPU,GPU") == ("MULTI_DEVICE_PRIORITIES", OVAny("CPU,GPU,")) assert device.priorities("CPU", "GPU") == ("MULTI_DEVICE_PRIORITIES", OVAny("CPU,GPU,")) @@ -401,7 +404,7 @@ def test_properties_device_properties(): def check(value1, value2): assert device.properties(value1) == ("DEVICE_PROPERTIES", OVAny(value2)) - check({"CPU": {streams.num(): 2}}, + check({"CPU": {streams.num: 2}}, {"CPU": {"NUM_STREAMS": 2}}) check({"CPU": make_dict(streams.num(2))}, {"CPU": {"NUM_STREAMS": streams.Num(2)}}) @@ -457,7 +460,7 @@ def test_properties_hint_model(): model = generate_add_model() - assert hints.model() == "MODEL_PTR" + assert hints.model == "MODEL_PTR" property_tuple = hints.model(model) assert property_tuple[0] == "MODEL_PTR" @@ -468,7 +471,7 @@ def test_single_property_setting(device): core.set_property(device, streams.num(streams.Num.AUTO)) - assert streams.Num.AUTO.to_integer() == -1 + assert props.streams.Num.AUTO.to_integer() == -1 assert type(core.get_property(device, streams.num())) == int @@ -494,28 +497,28 @@ def test_single_property_setting(device): ), # Pure dict { - props.enable_profiling(): True, - props.cache_dir(): "./", - props.inference_num_threads(): 9, - props.affinity(): props.Affinity.NONE, - hints.inference_precision(): Type.f32, - hints.performance_mode(): hints.PerformanceMode.LATENCY, - hints.enable_cpu_pinning(): True, - hints.scheduling_core_type(): hints.SchedulingCoreType.PCORE_ONLY, - hints.enable_hyper_threading(): True, - hints.num_requests(): 12, - streams.num(): 5, + props.enable_profiling: True, + props.cache_dir: "./", + props.inference_num_threads: 9, + props.affinity: props.Affinity.NONE, + hints.inference_precision: Type.f32, + hints.performance_mode: hints.PerformanceMode.LATENCY, + hints.enable_cpu_pinning: True, + hints.scheduling_core_type: hints.SchedulingCoreType.PCORE_ONLY, + hints.enable_hyper_threading: True, + hints.num_requests: 12, + streams.num: 5, }, # Mixed dict { - props.enable_profiling(): True, + props.enable_profiling: True, "CACHE_DIR": "./", - props.inference_num_threads(): 9, - props.affinity(): "NONE", + props.inference_num_threads: 9, + props.affinity: "NONE", "INFERENCE_PRECISION_HINT": Type.f32, - hints.performance_mode(): hints.PerformanceMode.LATENCY, - hints.scheduling_core_type(): hints.SchedulingCoreType.PCORE_ONLY, - hints.num_requests(): 12, + hints.performance_mode: hints.PerformanceMode.LATENCY, + hints.scheduling_core_type: hints.SchedulingCoreType.PCORE_ONLY, + hints.num_requests: 12, "NUM_STREAMS": streams.Num(5), "ENABLE_MMAP": "NO", }, @@ -526,21 +529,20 @@ def test_core_cpu_properties(properties_to_set): if "Intel" not in core.get_property("CPU", "FULL_DEVICE_NAME"): pytest.skip("This test runs only on openvino intel cpu plugin") - core.set_property(properties_to_set) # RW properties - assert core.get_property("CPU", props.enable_profiling()) is True - assert core.get_property("CPU", props.cache_dir()) == "./" - assert core.get_property("CPU", props.inference_num_threads()) == 9 - assert core.get_property("CPU", props.affinity()) == props.Affinity.NONE - assert core.get_property("CPU", streams.num()) == 5 + assert core.get_property("CPU", props.enable_profiling) is True + assert core.get_property("CPU", props.cache_dir) == "./" + assert core.get_property("CPU", props.inference_num_threads) == 9 + assert core.get_property("CPU", props.affinity) == props.Affinity.NONE + assert core.get_property("CPU", streams.num) == 5 # RO properties - assert type(core.get_property("CPU", props.supported_properties())) == dict - assert type(core.get_property("CPU", props.available_devices())) == list - assert type(core.get_property("CPU", props.optimal_number_of_infer_requests())) == int - assert type(core.get_property("CPU", props.range_for_streams())) == tuple - assert type(core.get_property("CPU", props.range_for_async_infer_requests())) == tuple - assert type(core.get_property("CPU", device.full_name())) == str - assert type(core.get_property("CPU", device.capabilities())) == list + assert type(core.get_property("CPU", props.supported_properties)) == dict + assert type(core.get_property("CPU", props.available_devices)) == list + assert type(core.get_property("CPU", props.optimal_number_of_infer_requests)) == int + assert type(core.get_property("CPU", props.range_for_streams)) == tuple + assert type(core.get_property("CPU", props.range_for_async_infer_requests)) == tuple + assert type(core.get_property("CPU", device.full_name)) == str + assert type(core.get_property("CPU", device.capabilities)) == list