Add typing
Change-Id: I75d8e9603ae12aa89fe465ce438dd79fa475e48e Signed-off-by: Stephen Finucane <stephenfin@redhat.com>
This commit is contained in:
@@ -13,7 +13,7 @@ repos:
|
||||
- id: check-yaml
|
||||
files: .*\.(yaml|yml)$
|
||||
- repo: https://github.com/astral-sh/ruff-pre-commit
|
||||
rev: v0.14.7
|
||||
rev: v0.14.8
|
||||
hooks:
|
||||
- id: ruff-check
|
||||
args: ['--fix', '--unsafe-fixes']
|
||||
|
||||
@@ -19,11 +19,13 @@ import signal
|
||||
import socket
|
||||
import sys
|
||||
import threading
|
||||
from types import FrameType
|
||||
from typing import Any
|
||||
from wsgiref.simple_server import make_server
|
||||
from wsgiref.simple_server import WSGIRequestHandler
|
||||
|
||||
from oslo_config import cfg
|
||||
from oslo_log import log as logging
|
||||
from oslo_config import cfg # type: ignore
|
||||
from oslo_log import log as logging # type: ignore
|
||||
from prometheus_client import make_wsgi_app
|
||||
|
||||
from oslo_metrics import message_router
|
||||
@@ -63,7 +65,7 @@ logging.setup(CONF, 'oslo-metrics')
|
||||
|
||||
|
||||
class MetricsListener:
|
||||
def __init__(self, socket_path):
|
||||
def __init__(self, socket_path: str) -> None:
|
||||
self.socket_path = socket_path
|
||||
self.socket = socket.socket(socket.AF_UNIX, socket.SOCK_DGRAM)
|
||||
self.unlink(socket_path)
|
||||
@@ -72,14 +74,14 @@ class MetricsListener:
|
||||
self.start = True
|
||||
self.router = message_router.MessageRouter()
|
||||
|
||||
def unlink(self, socket_path):
|
||||
def unlink(self, socket_path: str) -> None:
|
||||
try:
|
||||
os.unlink(socket_path)
|
||||
except OSError:
|
||||
if os.path.exists(socket_path):
|
||||
raise
|
||||
|
||||
def serve(self):
|
||||
def serve(self) -> None:
|
||||
while self.start:
|
||||
readable, writable, exceptional = select.select(
|
||||
[self.socket], [], [], 1
|
||||
@@ -95,7 +97,7 @@ class MetricsListener:
|
||||
except TimeoutError:
|
||||
pass
|
||||
|
||||
def stop(self):
|
||||
def stop(self) -> None:
|
||||
self.socket.close()
|
||||
self.start = False
|
||||
|
||||
@@ -103,20 +105,24 @@ class MetricsListener:
|
||||
class _SilentHandler(WSGIRequestHandler):
|
||||
"""WSGI handler that does not log requests."""
|
||||
|
||||
def log_message(self, format, *args):
|
||||
def log_message(self, format: str, *args: Any) -> None:
|
||||
"""Log nothing."""
|
||||
|
||||
|
||||
httpd = None
|
||||
|
||||
|
||||
def handle_sigterm(_signum, _frame):
|
||||
def handle_sigterm(_signum: int, _frame: FrameType | None) -> None:
|
||||
if httpd is None:
|
||||
# this should never happen
|
||||
raise RuntimeError('httpd is uninitialized')
|
||||
|
||||
LOG.debug("Caught sigterm")
|
||||
shutdown_thread = threading.Thread(target=httpd.shutdown)
|
||||
shutdown_thread.start()
|
||||
|
||||
|
||||
def main():
|
||||
def main() -> None:
|
||||
cfg.CONF(sys.argv[1:])
|
||||
socket_path = cfg.CONF.oslo_metrics.metrics_socket_file
|
||||
m = MetricsListener(socket_path)
|
||||
@@ -129,18 +135,21 @@ def main():
|
||||
mt.start()
|
||||
|
||||
app = make_wsgi_app()
|
||||
|
||||
global httpd
|
||||
if cfg.CONF.oslo_metrics.wsgi_silent_server:
|
||||
httpd = make_server(
|
||||
'',
|
||||
CONF.oslo_metrics.prometheus_port,
|
||||
app,
|
||||
handler_class=_SilentHandler,
|
||||
)
|
||||
else:
|
||||
httpd = make_server('', CONF.oslo_metrics.prometheus_port, app)
|
||||
|
||||
signal.signal(signal.SIGTERM, handle_sigterm)
|
||||
|
||||
try:
|
||||
global httpd
|
||||
if cfg.CONF.oslo_metrics.wsgi_silent_server:
|
||||
httpd = make_server(
|
||||
'',
|
||||
CONF.oslo_metrics.prometheus_port,
|
||||
app,
|
||||
handler_class=_SilentHandler,
|
||||
)
|
||||
else:
|
||||
httpd = make_server('', CONF.oslo_metrics.prometheus_port, app)
|
||||
signal.signal(signal.SIGTERM, handle_sigterm)
|
||||
httpd.serve_forever()
|
||||
except KeyboardInterrupt:
|
||||
pass
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
from oslo_log import log as logging
|
||||
from oslo_log import log as logging # type: ignore
|
||||
from oslo_utils import importutils
|
||||
|
||||
from oslo_metrics import message_type
|
||||
@@ -25,7 +25,7 @@ MODULE_LISTS = ["oslo_metrics.metrics.oslo_messaging"]
|
||||
|
||||
|
||||
class MessageRouter:
|
||||
def __init__(self):
|
||||
def __init__(self) -> None:
|
||||
self.modules = {}
|
||||
for m_str in MODULE_LISTS:
|
||||
mod = importutils.try_import(m_str, False)
|
||||
@@ -33,14 +33,14 @@ class MessageRouter:
|
||||
LOG.error("Failed to load module %s", m_str)
|
||||
self.modules[m_str.split('.')[-1]] = mod
|
||||
|
||||
def process(self, raw_string):
|
||||
def process(self, raw_string: bytes) -> None:
|
||||
try:
|
||||
metric = message_type.Metric.from_json(raw_string.decode())
|
||||
self.dispatch(metric)
|
||||
except Exception as e:
|
||||
LOG.error("Failed to parse: %s", e)
|
||||
|
||||
def dispatch(self, metric):
|
||||
def dispatch(self, metric: message_type.Metric) -> None:
|
||||
if metric.module not in self.modules:
|
||||
LOG.error("Failed to lookup modules by %s", metric.module)
|
||||
return
|
||||
|
||||
@@ -14,22 +14,28 @@
|
||||
# under the License.
|
||||
|
||||
import json
|
||||
from typing import Any, TypedDict
|
||||
|
||||
|
||||
class UnSupportedMetricActionError(Exception):
|
||||
def __init__(self, message=None):
|
||||
def __init__(self, message: str | None = None) -> None:
|
||||
self.message = message
|
||||
|
||||
|
||||
class MetricValidationError(Exception):
|
||||
def __init__(self, message=None):
|
||||
def __init__(self, message: str | None = None) -> None:
|
||||
self.message = message
|
||||
|
||||
|
||||
class _MetricActionDict(TypedDict, total=False):
|
||||
action: str
|
||||
value: str
|
||||
|
||||
|
||||
class MetricAction:
|
||||
actions = ['inc', 'observe']
|
||||
|
||||
def __init__(self, action, value):
|
||||
def __init__(self, action: str, value: str) -> None:
|
||||
if action not in self.actions:
|
||||
raise UnSupportedMetricActionError(
|
||||
f"{action} action is not supported"
|
||||
@@ -38,7 +44,7 @@ class MetricAction:
|
||||
self.value = value
|
||||
|
||||
@classmethod
|
||||
def validate(cls, metric_action_dict):
|
||||
def validate(cls, metric_action_dict: _MetricActionDict) -> None:
|
||||
if "value" not in metric_action_dict:
|
||||
raise MetricValidationError("action need 'value' field")
|
||||
if "action" not in metric_action_dict:
|
||||
@@ -49,18 +55,22 @@ class MetricAction:
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def from_dict(cls, metric_action_dict):
|
||||
def from_dict(
|
||||
cls, metric_action_dict: _MetricActionDict
|
||||
) -> 'MetricAction':
|
||||
return cls(metric_action_dict["action"], metric_action_dict["value"])
|
||||
|
||||
|
||||
class Metric:
|
||||
def __init__(self, module, name, action, **labels):
|
||||
def __init__(
|
||||
self, module: str, name: str, action: MetricAction, **labels: str
|
||||
) -> None:
|
||||
self.module = module
|
||||
self.name = name
|
||||
self.action = action
|
||||
self.labels = labels
|
||||
|
||||
def to_json(self):
|
||||
def to_json(self) -> str:
|
||||
raw = {
|
||||
"module": self.module,
|
||||
"name": self.name,
|
||||
@@ -73,7 +83,7 @@ class Metric:
|
||||
return json.dumps(raw)
|
||||
|
||||
@classmethod
|
||||
def from_json(cls, encoded):
|
||||
def from_json(cls, encoded: str) -> 'Metric':
|
||||
metric_dict = json.loads(encoded)
|
||||
cls._validate(metric_dict)
|
||||
return Metric(
|
||||
@@ -84,7 +94,7 @@ class Metric:
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def _validate(cls, metric_dict):
|
||||
def _validate(cls, metric_dict: dict[str, Any]) -> None:
|
||||
if "module" not in metric_dict:
|
||||
raise MetricValidationError("module should be specified")
|
||||
|
||||
|
||||
0
oslo_metrics/py.typed
Normal file
0
oslo_metrics/py.typed
Normal file
@@ -13,13 +13,14 @@
|
||||
"""
|
||||
test_message_process
|
||||
--------------------
|
||||
|
||||
Check that messages are processed correctly
|
||||
"""
|
||||
|
||||
from unittest import mock
|
||||
|
||||
from oslo_metrics import message_router
|
||||
from oslotest import base
|
||||
from oslotest import base # type: ignore
|
||||
import prometheus_client
|
||||
|
||||
|
||||
|
||||
@@ -12,13 +12,16 @@
|
||||
|
||||
"""
|
||||
test_message_validation
|
||||
--------------------
|
||||
-----------------------
|
||||
|
||||
Check that messages validation is working properly
|
||||
"""
|
||||
|
||||
import json
|
||||
from typing import Any
|
||||
|
||||
from oslo_metrics import message_type
|
||||
from oslotest import base
|
||||
from oslotest import base # type: ignore
|
||||
|
||||
|
||||
class TestMetricValidation(base.BaseTestCase):
|
||||
@@ -29,11 +32,14 @@ class TestMetricValidation(base.BaseTestCase):
|
||||
try:
|
||||
func(*args, **kwargs)
|
||||
self.assertFail()
|
||||
except Exception as e:
|
||||
except (
|
||||
message_type.MetricValidationError,
|
||||
message_type.UnSupportedMetricActionError,
|
||||
) as e:
|
||||
self.assertEqual(message, e.message)
|
||||
|
||||
def test_message_validation(self):
|
||||
metric = {}
|
||||
metric: dict[str, Any] = {}
|
||||
message = "module should be specified"
|
||||
self.assertRaisesWithMessage(
|
||||
message, message_type.Metric.from_json, json.dumps(metric)
|
||||
|
||||
@@ -37,6 +37,20 @@ Repository = "https://opendev.org/openstack/oslo.metrics"
|
||||
[tool.setuptools]
|
||||
packages = ["oslo_metrics"]
|
||||
|
||||
[tool.mypy]
|
||||
python_version = "3.10"
|
||||
show_column_numbers = true
|
||||
show_error_context = true
|
||||
strict = true
|
||||
exclude = '(?x)(doc | examples | releasenotes)'
|
||||
|
||||
[[tool.mypy.overrides]]
|
||||
module = ["oslo_metrics.tests.*"]
|
||||
disallow_untyped_calls = false
|
||||
disallow_untyped_defs = false
|
||||
disallow_subclassing_any = false
|
||||
disallow_any_generics = false
|
||||
|
||||
[tool.ruff]
|
||||
line-length = 79
|
||||
|
||||
|
||||
16
tox.ini
16
tox.ini
@@ -7,17 +7,30 @@ allowlist_externals =
|
||||
find
|
||||
deps =
|
||||
-c{env:TOX_CONSTRAINTS_FILE:https://releases.openstack.org/constraints/upper/master}
|
||||
-r{toxinidir}/requirements.txt
|
||||
-r{toxinidir}/test-requirements.txt
|
||||
commands =
|
||||
find . -type f -name "*.pyc" -delete
|
||||
stestr run --slowest {posargs}
|
||||
|
||||
[testenv:pep8]
|
||||
skip_install = true
|
||||
description =
|
||||
Run style checks.
|
||||
deps =
|
||||
pre-commit
|
||||
{[testenv:mypy]deps}
|
||||
commands =
|
||||
pre-commit run -a
|
||||
{[testenv:mypy]commands}
|
||||
|
||||
[testenv:mypy]
|
||||
description =
|
||||
Run type checks.
|
||||
deps =
|
||||
{[testenv]deps}
|
||||
mypy
|
||||
commands =
|
||||
mypy --cache-dir="{envdir}/mypy_cache" {posargs:oslo_metrics}
|
||||
|
||||
[testenv:venv]
|
||||
commands = {posargs}
|
||||
@@ -49,6 +62,7 @@ enable-extensions = H904
|
||||
|
||||
[hacking]
|
||||
import_exceptions =
|
||||
typing
|
||||
|
||||
[testenv:releasenotes]
|
||||
allowlist_externals =
|
||||
|
||||
Reference in New Issue
Block a user