Update core code for plugins with type hints
Signed-off-by: Andriy Kurilin <andr.kurilin@gmail.com> Change-Id: I1531c90c753b3f08678e96583a225718743ff320
This commit is contained in:
@@ -295,33 +295,10 @@ disable_error_code = ["no-untyped-def"]
|
||||
module = "rally.common.opts"
|
||||
disable_error_code = ["no-untyped-def", "var-annotated"]
|
||||
|
||||
[[tool.mypy.overrides]]
|
||||
module = "rally.common.plugin.discover"
|
||||
disable_error_code = ["arg-type", "call-arg", "no-untyped-def", "union-attr"]
|
||||
|
||||
[[tool.mypy.overrides]]
|
||||
module = "rally.common.plugin.info"
|
||||
disable_error_code = ["attr-defined", "no-untyped-def"]
|
||||
|
||||
[[tool.mypy.overrides]]
|
||||
module = "rally.common.plugin.meta"
|
||||
disable_error_code = ["assignment", "attr-defined", "no-untyped-def", "var-annotated"]
|
||||
|
||||
[[tool.mypy.overrides]]
|
||||
module = "rally.common.plugin.plugin"
|
||||
disable_error_code = ["no-untyped-def", "str-format"]
|
||||
|
||||
|
||||
[[tool.mypy.overrides]]
|
||||
module = "rally.common.streaming_algorithms"
|
||||
disable_error_code = ["no-untyped-def"]
|
||||
|
||||
[[tool.mypy.overrides]]
|
||||
module = "rally.common.validation"
|
||||
disable_error_code = ["attr-defined", "no-untyped-def"]
|
||||
|
||||
|
||||
|
||||
[[tool.mypy.overrides]]
|
||||
module = "rally.env.env_mgr"
|
||||
disable_error_code = ["attr-defined", "index", "no-untyped-def", "var-annotated"]
|
||||
|
||||
@@ -13,6 +13,8 @@
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import importlib
|
||||
import importlib.metadata
|
||||
import importlib.util
|
||||
@@ -24,10 +26,17 @@ import typing as t
|
||||
import rally
|
||||
from rally.common import logging
|
||||
|
||||
if t.TYPE_CHECKING: # pragma: no cover
|
||||
import types
|
||||
|
||||
P = t.TypeVar("P")
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def itersubclasses(cls, seen=None):
|
||||
def itersubclasses(
|
||||
cls: type[P], seen: set[type[P]] | None = None
|
||||
) -> t.Generator[type[P], None, None]:
|
||||
"""Generator over all subclasses of a given class in depth first order.
|
||||
|
||||
NOTE: Use 'seen' to exclude cls which was reduplicated found, because
|
||||
@@ -38,16 +47,15 @@ def itersubclasses(cls, seen=None):
|
||||
try:
|
||||
subs = cls.__subclasses__()
|
||||
except TypeError: # fails only when cls is type
|
||||
subs = cls.__subclasses__(cls)
|
||||
subs = cls.__subclasses__(cls) # type: ignore[call-arg]
|
||||
for sub in subs:
|
||||
if sub not in seen:
|
||||
seen.add(sub)
|
||||
yield sub
|
||||
for sub in itersubclasses(sub, seen):
|
||||
yield sub
|
||||
yield from itersubclasses(sub, seen)
|
||||
|
||||
|
||||
def import_modules_from_package(package):
|
||||
def import_modules_from_package(package: str) -> None:
|
||||
"""Import modules from package and append into sys.modules
|
||||
|
||||
:param package: Full package name. For example: rally.plugins.openstack
|
||||
@@ -64,16 +72,18 @@ def import_modules_from_package(package):
|
||||
sys.modules[module_name] = importlib.import_module(module_name)
|
||||
|
||||
|
||||
def iter_entry_points(): # pragma: no cover
|
||||
def iter_entry_points() -> t.Any: # pragma: no cover
|
||||
try:
|
||||
# Python 3.10+
|
||||
return importlib.metadata.entry_points(group="rally_plugins")
|
||||
return importlib.metadata.entry_points(
|
||||
group="rally_plugins"
|
||||
) # type: ignore[call-arg]
|
||||
except TypeError:
|
||||
# Python 3.8-3.9
|
||||
return importlib.metadata.entry_points().get("rally_plugins", [])
|
||||
|
||||
|
||||
def find_packages_by_entry_point():
|
||||
def find_packages_by_entry_point() -> list[dict[str, t.Any]]:
|
||||
"""Find all packages with rally_plugins entry-point"""
|
||||
packages = {}
|
||||
|
||||
@@ -97,7 +107,9 @@ def find_packages_by_entry_point():
|
||||
return list(packages.values())
|
||||
|
||||
|
||||
def import_modules_by_entry_point(_packages: t.Union[list, None] = None):
|
||||
def import_modules_by_entry_point(
|
||||
_packages: list[dict[str, t.Any]] | None = None
|
||||
) -> list[dict[str, t.Any]]:
|
||||
"""Import plugins by entry-point 'rally_plugins'."""
|
||||
if _packages is not None:
|
||||
loaded_packages = _packages
|
||||
@@ -132,10 +144,10 @@ def import_modules_by_entry_point(_packages: t.Union[list, None] = None):
|
||||
return loaded_packages
|
||||
|
||||
|
||||
_loaded_modules = []
|
||||
_loaded_modules: list[types.ModuleType] = []
|
||||
|
||||
|
||||
def load_plugins(dir_or_file, depth=0):
|
||||
def load_plugins(dir_or_file: str, depth: int = 0) -> None:
|
||||
if os.path.isdir(dir_or_file):
|
||||
directory = dir_or_file
|
||||
LOG.info("Loading plugins from directories %s/*" %
|
||||
@@ -158,6 +170,8 @@ def load_plugins(dir_or_file, depth=0):
|
||||
try:
|
||||
spec = importlib.util.spec_from_file_location(
|
||||
module_name, plugin_file)
|
||||
if spec is None or spec.loader is None:
|
||||
raise ImportError(f"Could not load spec for {plugin_file}")
|
||||
module = importlib.util.module_from_spec(spec)
|
||||
spec.loader.exec_module(module)
|
||||
|
||||
|
||||
@@ -13,8 +13,14 @@
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import re
|
||||
import sys
|
||||
import typing as t
|
||||
|
||||
if t.TYPE_CHECKING:
|
||||
from rally.common.plugin import plugin
|
||||
|
||||
PARAM_OR_RETURNS_REGEX = re.compile(":(?:param|returns)")
|
||||
RETURNS_REGEX = re.compile(":returns: (?P<doc>.*)", re.S)
|
||||
@@ -22,7 +28,7 @@ PARAM_REGEX = re.compile(r":param (?P<name>[\*\w]+): (?P<doc>.*?)"
|
||||
r"(?:(?=:param)|(?=:return)|(?=:raises)|\Z)", re.S)
|
||||
|
||||
|
||||
def trim(docstring):
|
||||
def trim(docstring: str) -> str:
|
||||
"""trim function from PEP-257"""
|
||||
if not docstring:
|
||||
return ""
|
||||
@@ -56,11 +62,25 @@ def trim(docstring):
|
||||
return "\n".join(trimmed)
|
||||
|
||||
|
||||
def reindent(string):
|
||||
def reindent(string: str) -> str:
|
||||
return "\n".join(line.strip() for line in string.strip().split("\n"))
|
||||
|
||||
|
||||
def parse_docstring(docstring):
|
||||
class _ParamInfo(t.TypedDict):
|
||||
"""Type for parameter information in docstring parsing."""
|
||||
name: str
|
||||
doc: str
|
||||
|
||||
|
||||
class _DocstringInfo(t.TypedDict):
|
||||
"""Type for parsed docstring information."""
|
||||
short_description: str
|
||||
long_description: str
|
||||
params: list[_ParamInfo]
|
||||
returns: str
|
||||
|
||||
|
||||
def parse_docstring(docstring: str | None) -> _DocstringInfo:
|
||||
"""Parse the docstring into its components.
|
||||
|
||||
:returns: a dictionary of form
|
||||
@@ -73,7 +93,7 @@ def parse_docstring(docstring):
|
||||
"""
|
||||
|
||||
short_description = long_description = returns = ""
|
||||
params = []
|
||||
params: list[_ParamInfo] = []
|
||||
|
||||
if docstring:
|
||||
docstring = trim(docstring.lstrip("\n"))
|
||||
@@ -94,7 +114,7 @@ def parse_docstring(docstring):
|
||||
|
||||
if params_returns_desc:
|
||||
params = [
|
||||
{"name": name, "doc": trim(doc)}
|
||||
_ParamInfo(name=name, doc=trim(doc))
|
||||
for name, doc in PARAM_REGEX.findall(params_returns_desc)
|
||||
]
|
||||
|
||||
@@ -110,10 +130,22 @@ def parse_docstring(docstring):
|
||||
}
|
||||
|
||||
|
||||
class InfoMixin(object):
|
||||
class _PluginInfo(t.TypedDict):
|
||||
"""Type for plugin information returned by get_info method."""
|
||||
name: str
|
||||
platform: str
|
||||
module: str
|
||||
title: str
|
||||
description: str
|
||||
parameters: list[_ParamInfo]
|
||||
schema: str | None
|
||||
returns: str
|
||||
|
||||
|
||||
class InfoMixin:
|
||||
|
||||
@classmethod
|
||||
def _get_doc(cls):
|
||||
def _get_doc(cls) -> str | None:
|
||||
"""Return documentary of class
|
||||
|
||||
By default it returns docstring of class, but it can be overridden
|
||||
@@ -122,7 +154,9 @@ class InfoMixin(object):
|
||||
return cls.__doc__
|
||||
|
||||
@classmethod
|
||||
def get_info(cls):
|
||||
def get_info( # type: ignore[misc]
|
||||
cls: type[plugin.Plugin]
|
||||
) -> _PluginInfo:
|
||||
doc = parse_docstring(cls._get_doc())
|
||||
|
||||
return {
|
||||
|
||||
@@ -13,10 +13,13 @@
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import copy
|
||||
import typing as t
|
||||
|
||||
|
||||
class MetaMixin(object):
|
||||
class MetaMixin:
|
||||
"""Safe way to store meta information related to class object.
|
||||
|
||||
Allows to store information in class object instead of the instance.
|
||||
@@ -61,20 +64,21 @@ class MetaMixin(object):
|
||||
>>> assert B._meta_get("a") == 20
|
||||
"""
|
||||
|
||||
_default_meta = (None, {})
|
||||
_default_meta: tuple[type[MetaMixin] | None, dict[str, t.Any]] = (None, {})
|
||||
_meta: dict[str, t.Any] # Dynamically created by _meta_init()
|
||||
|
||||
@classmethod
|
||||
def _meta_init(cls):
|
||||
def _meta_init(cls) -> None:
|
||||
"""Initialize meta for this class."""
|
||||
cls._meta = copy.deepcopy(cls._default_meta[1])
|
||||
|
||||
@classmethod
|
||||
def _meta_clear(cls):
|
||||
def _meta_clear(cls) -> None:
|
||||
cls._meta.clear() # NOTE(boris-42): make sure that meta is deleted
|
||||
delattr(cls, "_meta")
|
||||
|
||||
@classmethod
|
||||
def _meta_is_inited(cls, raise_exc=True):
|
||||
def _meta_is_inited(cls, raise_exc: bool = True) -> bool:
|
||||
"""Check if meta is initialized.
|
||||
|
||||
It means that this class has own cls._meta object (not pointer
|
||||
@@ -89,25 +93,25 @@ class MetaMixin(object):
|
||||
return True
|
||||
|
||||
@classmethod
|
||||
def _meta_get(cls, key, default=None):
|
||||
def _meta_get(cls, key: str, default: t.Any = None) -> t.Any:
|
||||
"""Get value corresponding to key in meta data."""
|
||||
cls._meta_is_inited()
|
||||
return cls._meta.get(key, default)
|
||||
|
||||
@classmethod
|
||||
def _meta_set(cls, key, value):
|
||||
def _meta_set(cls, key: str, value: t.Any) -> None:
|
||||
"""Set value for key in meta."""
|
||||
cls._meta_is_inited()
|
||||
cls._meta[key] = value
|
||||
|
||||
@classmethod
|
||||
def _meta_setdefault(cls, key, value):
|
||||
def _meta_setdefault(cls, key: str, value: t.Any) -> None:
|
||||
"""Set default value for key in meta."""
|
||||
cls._meta_is_inited()
|
||||
cls._meta.setdefault(key, value)
|
||||
|
||||
@classmethod
|
||||
def _default_meta_init(cls, inherit=True):
|
||||
def _default_meta_init(cls, inherit: bool = True) -> None:
|
||||
"""Initialize default meta.
|
||||
|
||||
Default Meta is used to change the behavior of _meta_init() method
|
||||
@@ -121,7 +125,7 @@ class MetaMixin(object):
|
||||
cls._default_meta = (cls, {})
|
||||
|
||||
@classmethod
|
||||
def _default_meta_set(cls, key, value):
|
||||
def _default_meta_set(cls, key: str, value: t.Any) -> None:
|
||||
if cls is not cls._default_meta[0]:
|
||||
raise ReferenceError(
|
||||
"Trying to update default meta from children class.")
|
||||
@@ -129,11 +133,11 @@ class MetaMixin(object):
|
||||
cls._default_meta[1][key] = value
|
||||
|
||||
@classmethod
|
||||
def _default_meta_get(cls, key, default=None):
|
||||
def _default_meta_get(cls, key: str, default: t.Any = None) -> t.Any:
|
||||
return cls._default_meta[1].get(key, default)
|
||||
|
||||
@classmethod
|
||||
def _default_meta_setdefault(cls, key, value):
|
||||
def _default_meta_setdefault(cls, key: str, value: t.Any) -> None:
|
||||
if cls is not cls._default_meta[0]:
|
||||
raise ReferenceError(
|
||||
"Trying to update default meta from children class.")
|
||||
|
||||
@@ -13,7 +13,10 @@
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import sys
|
||||
import typing as t
|
||||
|
||||
from rally.common.plugin import discover
|
||||
from rally.common.plugin import info
|
||||
@@ -21,7 +24,17 @@ from rally.common.plugin import meta
|
||||
from rally import exceptions
|
||||
|
||||
|
||||
def base():
|
||||
if t.TYPE_CHECKING: # pragma: no cover
|
||||
P = t.TypeVar("P", bound="Plugin")
|
||||
|
||||
|
||||
class DeprecationInfo(t.TypedDict):
|
||||
"""Structure for plugin deprecation information."""
|
||||
reason: str
|
||||
rally_version: str
|
||||
|
||||
|
||||
def base() -> t.Callable[[type[P]], type[P]]:
|
||||
"""Mark Plugin as a base.
|
||||
|
||||
Base Plugins are used to have better organization of plugins providing
|
||||
@@ -34,7 +47,7 @@ def base():
|
||||
|
||||
Plugin bases by default initialize _default_meta
|
||||
"""
|
||||
def wrapper(cls):
|
||||
def wrapper(cls: type[P]) -> type[P]:
|
||||
if not issubclass(cls, Plugin):
|
||||
raise exceptions.RallyException(
|
||||
"Plugin's Base can be only a subclass of Plugin class.")
|
||||
@@ -52,7 +65,9 @@ def base():
|
||||
return wrapper
|
||||
|
||||
|
||||
def configure(name, platform="default", hidden=False):
|
||||
def configure(
|
||||
name: str, platform: str = "default", hidden: bool = False
|
||||
) -> t.Callable[[type[P]], type[P]]:
|
||||
"""Use this decorator to configure plugin's attributes.
|
||||
|
||||
Plugin is not discoverable until configure() is performed.
|
||||
@@ -63,14 +78,14 @@ def configure(name, platform="default", hidden=False):
|
||||
loaded only explicitly
|
||||
"""
|
||||
|
||||
def decorator(plugin):
|
||||
def decorator(plugin: type[P]) -> type[P]:
|
||||
plugin_id = "%s.%s" % (plugin.__module__, plugin.__name__)
|
||||
if not name:
|
||||
raise ValueError(
|
||||
"The name of the plugin %s cannot be empty." % plugin_id)
|
||||
f"The name of the plugin {plugin_id} cannot be empty.")
|
||||
if "@" in name:
|
||||
raise ValueError(
|
||||
"The name of the plugin cannot contain @ symbol" % plugin_id)
|
||||
f"The name of the plugin {plugin_id} cannot contain @ symbol")
|
||||
|
||||
plugin._meta_init()
|
||||
try:
|
||||
@@ -93,7 +108,7 @@ def configure(name, platform="default", hidden=False):
|
||||
return decorator
|
||||
|
||||
|
||||
def default_meta(inherit=True):
|
||||
def default_meta(inherit: bool = True) -> t.Callable[[type[P]], type[P]]:
|
||||
"""Initialize default meta for particular plugin.
|
||||
|
||||
Default Meta is inherited by all children comparing to Meta which is unique
|
||||
@@ -102,24 +117,26 @@ def default_meta(inherit=True):
|
||||
:param inherit: Whatever to copy parents default meta
|
||||
"""
|
||||
|
||||
def decorator(plugin):
|
||||
def decorator(plugin: type[P]) -> type[P]:
|
||||
plugin._default_meta_init(inherit)
|
||||
return plugin
|
||||
|
||||
return decorator
|
||||
|
||||
|
||||
def deprecated(reason, rally_version):
|
||||
def deprecated(
|
||||
reason: str, rally_version: str
|
||||
) -> t.Callable[[type[P]], type[P]]:
|
||||
"""Mark plugin as deprecated.
|
||||
|
||||
:param reason: Message that describes reason of plugin deprecation
|
||||
:param rally_version: Deprecated since this version of Rally
|
||||
"""
|
||||
def decorator(plugin):
|
||||
plugin._meta_set("deprecated", {
|
||||
"reason": reason,
|
||||
"rally_version": rally_version
|
||||
})
|
||||
def decorator(plugin: type[P]) -> type[P]:
|
||||
plugin._meta_set("deprecated", DeprecationInfo(
|
||||
reason=reason,
|
||||
rally_version=rally_version
|
||||
))
|
||||
return plugin
|
||||
|
||||
return decorator
|
||||
@@ -128,17 +145,24 @@ def deprecated(reason, rally_version):
|
||||
class Plugin(meta.MetaMixin, info.InfoMixin):
|
||||
"""Base class for all Plugins in Rally."""
|
||||
|
||||
base_ref: t.ClassVar[type[Plugin]] # Dynamically set by @base() decorator
|
||||
|
||||
@classmethod
|
||||
def unregister(cls):
|
||||
def unregister(cls) -> None:
|
||||
"""Removes all plugin meta information and makes it undiscoverable."""
|
||||
cls._meta_clear()
|
||||
|
||||
@classmethod
|
||||
def _get_base(cls):
|
||||
def _get_base(cls) -> type[Plugin]:
|
||||
return getattr(cls, "base_ref", Plugin)
|
||||
|
||||
@classmethod
|
||||
def get(cls, name, platform=None, allow_hidden=False):
|
||||
def get(
|
||||
cls: type[P],
|
||||
name: str,
|
||||
platform: str | None = None,
|
||||
allow_hidden: bool = False
|
||||
) -> type[P]:
|
||||
"""Return plugin by its name for specified platform.
|
||||
|
||||
:param name: Plugin's name or fullname
|
||||
@@ -172,7 +196,12 @@ class Plugin(meta.MetaMixin, info.InfoMixin):
|
||||
plugins=", ".join(p.get_fullname() for p in results))
|
||||
|
||||
@classmethod
|
||||
def get_all(cls, platform=None, allow_hidden=False, name=None):
|
||||
def get_all(
|
||||
cls: type[P],
|
||||
platform: str | None = None,
|
||||
allow_hidden: bool = False,
|
||||
name: str | None = None,
|
||||
) -> list[type[P]]:
|
||||
"""Return all subclass plugins of plugin.
|
||||
|
||||
All plugins that are not configured will be ignored.
|
||||
@@ -198,26 +227,26 @@ class Plugin(meta.MetaMixin, info.InfoMixin):
|
||||
return plugins
|
||||
|
||||
@classmethod
|
||||
def get_name(cls):
|
||||
def get_name(cls) -> str:
|
||||
"""Return plugin's name."""
|
||||
return cls._meta_get("name")
|
||||
|
||||
@classmethod
|
||||
def get_platform(cls):
|
||||
def get_platform(cls) -> str:
|
||||
""""Return plugin's platform name."""
|
||||
return cls._meta_get("platform")
|
||||
|
||||
@classmethod
|
||||
def get_fullname(cls):
|
||||
def get_fullname(cls) -> str:
|
||||
"""Returns plugins's full name."""
|
||||
return "%s@%s" % (cls.get_name(), cls.get_platform() or "")
|
||||
|
||||
@classmethod
|
||||
def is_hidden(cls):
|
||||
def is_hidden(cls) -> bool:
|
||||
"""Returns whatever plugin is hidden or not."""
|
||||
return cls._meta_get("hidden", False)
|
||||
|
||||
@classmethod
|
||||
def is_deprecated(cls):
|
||||
def is_deprecated(cls) -> DeprecationInfo | t.Literal[False]:
|
||||
"""Returns deprecation details if plugin is deprecated."""
|
||||
return cls._meta_get("deprecated", False)
|
||||
|
||||
@@ -13,18 +13,28 @@
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import abc
|
||||
import traceback
|
||||
import typing as t
|
||||
|
||||
from rally.common import logging
|
||||
from rally.common.plugin import plugin
|
||||
from rally import exceptions
|
||||
|
||||
if t.TYPE_CHECKING: # pragma: no cover
|
||||
P = t.TypeVar("P", bound=plugin.Plugin)
|
||||
V = t.TypeVar("V", bound="Validator")
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def configure(name, platform="default"):
|
||||
def wrapper(cls):
|
||||
def configure(
|
||||
name: str, platform: str = "default"
|
||||
) -> t.Callable[[type[V]], type[V]]:
|
||||
"""Configure validator plugin with name and platform."""
|
||||
def wrapper(cls: type[V]) -> type[V]:
|
||||
return plugin.configure(name=name, platform=platform)(cls)
|
||||
|
||||
return wrapper
|
||||
@@ -34,11 +44,17 @@ def configure(name, platform="default"):
|
||||
class Validator(plugin.Plugin, metaclass=abc.ABCMeta):
|
||||
"""A base class for all validators."""
|
||||
|
||||
def __init__(self):
|
||||
def __init__(self) -> None:
|
||||
pass
|
||||
|
||||
@abc.abstractmethod
|
||||
def validate(self, context, config, plugin_cls, plugin_cfg):
|
||||
def validate(
|
||||
self,
|
||||
context: dict[str, t.Any],
|
||||
config: dict[str, t.Any] | None,
|
||||
plugin_cls: type[plugin.Plugin],
|
||||
plugin_cfg: dict[str, t.Any] | None
|
||||
) -> None:
|
||||
"""Method that validates something.
|
||||
|
||||
:param context: a validation context
|
||||
@@ -50,11 +66,11 @@ class Validator(plugin.Plugin, metaclass=abc.ABCMeta):
|
||||
"""
|
||||
|
||||
@staticmethod
|
||||
def fail(msg):
|
||||
def fail(msg: str) -> t.NoReturn:
|
||||
raise ValidationError(msg)
|
||||
|
||||
@classmethod
|
||||
def _get_doc(cls):
|
||||
def _get_doc(cls) -> str:
|
||||
doc = ""
|
||||
if cls.__doc__ is not None:
|
||||
doc = cls.__doc__
|
||||
@@ -68,7 +84,7 @@ class Validator(plugin.Plugin, metaclass=abc.ABCMeta):
|
||||
@configure(name="required_platform")
|
||||
class RequiredPlatformValidator(Validator):
|
||||
|
||||
def __init__(self, platform, **kwargs):
|
||||
def __init__(self, platform: str, **kwargs: t.Any) -> None:
|
||||
"""Validates specification of specified platform for the workload.
|
||||
|
||||
:param platform: name of the platform
|
||||
@@ -77,7 +93,13 @@ class RequiredPlatformValidator(Validator):
|
||||
self.platform = platform
|
||||
self._kwargs = kwargs
|
||||
|
||||
def validate(self, context, config, plugin_cls, plugin_cfg):
|
||||
def validate(
|
||||
self,
|
||||
context: dict[str, t.Any],
|
||||
config: dict[str, t.Any] | None,
|
||||
plugin_cls: type[plugin.Plugin],
|
||||
plugin_cfg: dict[str, t.Any] | None
|
||||
) -> None:
|
||||
try:
|
||||
pvalidator_cls = RequiredPlatformValidator.get(
|
||||
"required_platform",
|
||||
@@ -103,12 +125,12 @@ class RequiredPlatformValidator(Validator):
|
||||
"You should specify admin=True or users=True or both "
|
||||
"for validating openstack platform.")
|
||||
|
||||
context = context["platforms"].get(self.platform, {})
|
||||
platform_context = context["platforms"].get(self.platform, {})
|
||||
|
||||
if admin and context.get("admin") is None:
|
||||
if admin and platform_context.get("admin") is None:
|
||||
self.fail("No admin credential for %s" % self.platform)
|
||||
if users and len(context.get("users", ())) == 0:
|
||||
if context.get("admin") is None:
|
||||
if users and len(platform_context.get("users", ())) == 0:
|
||||
if platform_context.get("admin") is None:
|
||||
self.fail("No user credentials for %s" % self.platform)
|
||||
else:
|
||||
# NOTE(andreykurilin): It is a case when the plugin
|
||||
@@ -125,7 +147,7 @@ class RequiredPlatformValidator(Validator):
|
||||
plugin_cfg=plugin_cfg)
|
||||
|
||||
|
||||
def add(name, **kwargs):
|
||||
def add(name: str, **kwargs: t.Any) -> t.Callable[[type[P]], type[P]]:
|
||||
"""Add validator to the plugin class meta.
|
||||
|
||||
Add validator name and arguments to validators list stored in the
|
||||
@@ -137,7 +159,7 @@ def add(name, **kwargs):
|
||||
instance
|
||||
"""
|
||||
|
||||
def wrapper(plugin):
|
||||
def wrapper(plugin: type[P]) -> type[P]:
|
||||
if issubclass(plugin, RequiredPlatformValidator):
|
||||
raise exceptions.RallyException(
|
||||
"Cannot add a validator to RequiredPlatformValidator")
|
||||
@@ -153,7 +175,7 @@ def add(name, **kwargs):
|
||||
return wrapper
|
||||
|
||||
|
||||
def add_default(name, **kwargs):
|
||||
def add_default(name: str, **kwargs: t.Any) -> t.Callable[[type[P]], type[P]]:
|
||||
"""Add validator to the plugin class default meta.
|
||||
|
||||
Validator is added to all subclasses by default
|
||||
@@ -162,7 +184,7 @@ def add_default(name, **kwargs):
|
||||
:param kwargs: dict, validator plugin arguments
|
||||
"""
|
||||
|
||||
def wrapper(plugin):
|
||||
def wrapper(plugin: type[P]) -> type[P]:
|
||||
plugin._default_meta_setdefault("validators", [])
|
||||
plugin._default_meta_get("validators").append((name, (), kwargs,))
|
||||
return plugin
|
||||
@@ -172,22 +194,32 @@ def add_default(name, **kwargs):
|
||||
# this class doesn't inherit from rally.exceptions.RallyException, since
|
||||
# ValidationError should be used only for inner purpose.
|
||||
class ValidationError(Exception):
|
||||
def __init__(self, message):
|
||||
def __init__(self, message: str) -> None:
|
||||
super(ValidationError, self).__init__(message)
|
||||
self.message = message
|
||||
|
||||
|
||||
class ValidatablePluginMixin(object):
|
||||
_ValidatorInfo = tuple[type[Validator], tuple[t.Any, ...], dict[str, t.Any]]
|
||||
|
||||
|
||||
class ValidatablePluginMixin:
|
||||
|
||||
@staticmethod
|
||||
def _load_validators(plugin):
|
||||
def _load_validators(plugin: type[plugin.Plugin]) -> list[_ValidatorInfo]:
|
||||
validators = plugin._meta_get("validators", default=[])
|
||||
return [(Validator.get(name), args, kwargs)
|
||||
for name, args, kwargs in validators]
|
||||
|
||||
@classmethod
|
||||
def validate(cls, name, context, config, plugin_cfg,
|
||||
allow_hidden=False, vtype=None):
|
||||
def validate(
|
||||
cls,
|
||||
name: str,
|
||||
context: dict[str, t.Any],
|
||||
config: dict[str, t.Any] | None,
|
||||
plugin_cfg: dict[str, t.Any] | None,
|
||||
allow_hidden: bool = False,
|
||||
vtype: str | list[str] | tuple[str, ...] | None = None
|
||||
) -> list[str]:
|
||||
"""Execute all validators stored in meta of plugin.
|
||||
|
||||
Iterate during all validators stored in the meta of Validator
|
||||
@@ -205,7 +237,8 @@ class ValidatablePluginMixin(object):
|
||||
:returns: list of ValidationResult(is_valid=False) instances
|
||||
"""
|
||||
try:
|
||||
plugin = cls.get(name, allow_hidden=allow_hidden)
|
||||
plugin_cls = t.cast(plugin.Plugin, cls).get(
|
||||
name, allow_hidden=allow_hidden)
|
||||
except exceptions.PluginNotFound as e:
|
||||
return [e.format_message()]
|
||||
|
||||
@@ -224,38 +257,40 @@ class ValidatablePluginMixin(object):
|
||||
syntax = "syntax" in vtype
|
||||
platform = "platform" in vtype
|
||||
|
||||
syntax_validators = []
|
||||
platform_validators = []
|
||||
regular_validators = []
|
||||
syntax_validators: list[_ValidatorInfo] = []
|
||||
platform_validators: list[_ValidatorInfo] = []
|
||||
regular_validators: list[_ValidatorInfo] = []
|
||||
|
||||
plugin_validators = cls._load_validators(plugin)
|
||||
for validator, args, kwargs in plugin_validators:
|
||||
if issubclass(validator, RequiredPlatformValidator):
|
||||
plugin_validators = cls._load_validators(plugin_cls)
|
||||
for validator_cls, args, kwargs in plugin_validators:
|
||||
if issubclass(validator_cls, RequiredPlatformValidator):
|
||||
if platform:
|
||||
platform_validators.append((validator, args, kwargs))
|
||||
platform_validators.append((validator_cls, args, kwargs))
|
||||
else:
|
||||
validators_of_validators = cls._load_validators(validator)
|
||||
validators_of_validators = cls._load_validators(validator_cls)
|
||||
if validators_of_validators:
|
||||
if semantic:
|
||||
regular_validators.append((validator, args, kwargs))
|
||||
regular_validators.append(
|
||||
(validator_cls, args, kwargs))
|
||||
if platform:
|
||||
# Load platform validators from each validator
|
||||
platform_validators.extend(validators_of_validators)
|
||||
else:
|
||||
if syntax:
|
||||
syntax_validators.append((validator, args, kwargs))
|
||||
syntax_validators.append((validator_cls, args, kwargs))
|
||||
|
||||
results = []
|
||||
results: list[str] = []
|
||||
for validators in (syntax_validators, platform_validators,
|
||||
regular_validators):
|
||||
for validator_cls, args, kwargs in validators:
|
||||
validator = validator_cls(*args, **kwargs)
|
||||
result = None
|
||||
result: str | None = None
|
||||
try:
|
||||
validator.validate(context=context,
|
||||
config=config,
|
||||
plugin_cls=plugin,
|
||||
plugin_cfg=plugin_cfg)
|
||||
validator.validate(
|
||||
context=context,
|
||||
config=config,
|
||||
plugin_cls=plugin_cls,
|
||||
plugin_cfg=plugin_cfg)
|
||||
except ValidationError as e:
|
||||
result = e.message
|
||||
except Exception:
|
||||
@@ -263,13 +298,9 @@ class ValidatablePluginMixin(object):
|
||||
result = traceback.format_exc()
|
||||
if result:
|
||||
results.append(
|
||||
"%(base)s plugin '%(pname)s' doesn't pass %(vname)s "
|
||||
"validation. Details: %(error)s" % {
|
||||
"base": cls.__name__,
|
||||
"pname": name,
|
||||
"vname": validator_cls.get_fullname(),
|
||||
"error": result
|
||||
}
|
||||
f"{cls.__name__} plugin '{name}' doesn't pass "
|
||||
f"{validator_cls.get_fullname()} validation. "
|
||||
f"Details: {result}"
|
||||
)
|
||||
if results:
|
||||
break
|
||||
|
||||
@@ -54,7 +54,9 @@ def find_exception(response: requests.Response) -> RallyException:
|
||||
global _exception_map
|
||||
if _exception_map is None:
|
||||
_exception_map = dict(
|
||||
(e.error_code, e) for e in discover.itersubclasses(RallyException))
|
||||
(e.error_code, e) for e in discover.itersubclasses(RallyException)
|
||||
if hasattr(e, "error_code")
|
||||
)
|
||||
exc_class = _exception_map.get(response.status_code, RallyException)
|
||||
|
||||
error_data = response.json()["error"]
|
||||
|
||||
@@ -110,7 +110,9 @@ def _process_workload(workload, workload_cfg, pos):
|
||||
additive_output_charts[i].add_iteration(additive["data"])
|
||||
except IndexError:
|
||||
chart_cls = plugin.Plugin.get(additive["chart_plugin"])
|
||||
chart = chart_cls(
|
||||
# FIXME(andreykurilin): we need to be more specific about
|
||||
# plugin class
|
||||
chart = chart_cls( # type: ignore[call-arg]
|
||||
workload, title=additive["title"],
|
||||
description=additive.get("description", ""),
|
||||
label=additive.get("label", ""),
|
||||
|
||||
Reference in New Issue
Block a user