updating docs to pull descriptions from structured configs

config_dataclasses
Andrew Mirsky 2025-07-11 22:24:27 -04:00
rodzic 1ec555ca23
commit 1be3a7c848
Nie znaleziono w bazie danych klucza dla tego podpisu
ID klucza GPG: A98E67635CDF2C39
38 zmienionych plików z 286 dodań i 124 usunięć

Wyświetl plik

@ -3,7 +3,7 @@ from dataclasses import dataclass, field, fields, replace, asdict
from enum import Enum, StrEnum
import logging
from pathlib import Path
from typing import TYPE_CHECKING, Any
from typing import TYPE_CHECKING, Any, LiteralString, Literal
from amqtt.mqtt.constants import QOS_0
@ -34,7 +34,7 @@ class ListenerType(StrEnum):
class Dictable:
def __getitem__(self, key):
return getattr(self, key)
return self.get(key)
def get(self, name, default=None):
if hasattr(self, name):
@ -43,34 +43,6 @@ class Dictable:
return default
raise ValueError(f"'{name}' is not defined")
def copy(self):
return replace(self)
def update(self, other):
if not isinstance(other, self.__class__):
raise TypeError(f'must update with another {self.__class__.__name__}')
for f in fields(self):
if isinstance(getattr(self, f.name), Dictable):
setattr(self, f.name, getattr(self, f.name).update(getattr(other, f.name)))
if getattr(self, f.name) == f.default:
setattr(self, f.name, other[f.name])
# if not isinstance(other, self.__class__):
# raise TypeError(f'must update with another {self.__class__.__name__}')
#
# valids = { k:v for k,v in asdict(self).items() if v is not None }
# return replace(other, **valids)
def __ior__(self, other):
self.update(other)
return self
# if not isinstance(other, self.__class__):
# raise TypeError(f'must update with another {self.__class__.__name__}')
# for f in fields(self):
# if getattr(self, f.name) == f.default:
# setattr(self, f.name, other[f.name])
# return self
def __contains__(self, name):
return getattr(self, name, None) is not None
@ -80,10 +52,13 @@ class Dictable:
@dataclass
class ListenerConfig(Dictable):
class ListenerConfig:
type: ListenerType = ListenerType.TCP
"""listener type: 'tcp' or 'ws'"""
bind: str | None = "0.0.0.0:1883"
"""address and port for the listener to bind to"""
max_connections: int = 0
""""""
ssl: bool = False
cafile: str | Path | None = None
capath: str | Path | None = None
@ -96,6 +71,15 @@ class ListenerConfig(Dictable):
if isinstance(getattr(self, fn), str):
setattr(self, fn, Path(getattr(self, fn)))
def apply(self, other):
"""Apply the field from 'other', if 'self' field is default."""
if not isinstance(other, ListenerConfig):
msg = f'cannot apply {self.__class__.__name__} to {other.__class__.__name__}'
raise TypeError(msg)
for f in fields(self):
if getattr(self, f.name) == f.default:
setattr(self, f.name, other[f.name])
def default_listeners():
return {
'default': ListenerConfig()
@ -112,12 +96,20 @@ def default_broker_plugins():
@dataclass
class BrokerConfig(Dictable):
listeners: dict[str, ListenerConfig] = field(default_factory=dict)
"""Structured configuration for a broker. Can be passed directly to `amqtt.broker.Broker` or created from a dictionary.
"""
listeners: dict[Literal['default'] | str, ListenerConfig] = field(default_factory=default_listeners)
"""Network of listeners used by the services."""
sys_interval: int | None = None
"""Deprecated field to configure the `BrokerSysPlugin`"""
timeout_disconnect_delay: int | None = 0
plugins: dict | list | None = None
"""Client disconnect timeout without a keep-alive."""
auth: dict[str, Any] | None = None
"""Deprecated field used to config EntryPoint-loaded plugins."""
topic_check: dict[str, Any] | None = None
"""Deprecated field used to config EntryPoint-loaded plugins."""
plugins: dict | list | None = None
"""A list of strings representing the modules and class name of `BasePlugin`, `BaseAuthPlugin` and `BaseTopicPlugins`."""
def __post__init__(self) -> None:
if self.sys_interval is not None:
@ -130,7 +122,9 @@ class BrokerConfig(Dictable):
default_listener = self.listeners['default']
for listener_name, listener in self.listeners.items():
listener.update(default_listener)
if listener_name == 'default':
continue
listener.apply(default_listener)
if isinstance(self.plugins, list):
_plugins = {}

Wyświetl plik

@ -1,8 +0,0 @@
{% extends "base.html" %}
{% block outdated %}
You're not viewing the latest version.
<a href="{{ '../' ~ base_url }}">
<strong>Click here to go to latest.</strong>
</a>
{% endblock %}

Wyświetl plik

@ -3,6 +3,22 @@
This configuration structure is valid as a python dictionary passed to the `amqtt.broker.Broker` class's `__init__` method or
as a yaml formatted file passed to the `amqtt` script.
::: amqtt.contexts.BrokerConfig
options:
heading_level: 3
extra:
class_style: "simple"
::: amqtt.contexts.ListenerConfig
#### Manual docs
### `listeners` *(list[dict[str, Any]])*
Defines the network listeners used by the service. Items defined in the `default` listener will be

Wyświetl plik

@ -0,0 +1,13 @@
{% extends "_base/class.html.jinja" %}
{% block signature scoped %}
{% if config.extra.class_style != 'simple' %}
{{ super() }}
{% endif %}
{% endblock signature %}
{% block bases scoped %}
{% if config.extra.class_style != 'simple' %}
{{ super() }}
{% endif %}
{% endblock bases %}

Wyświetl plik

@ -0,0 +1,7 @@
{% extends "_base/attribute.html.jinja" %}
{% block logs scoped %}
<p style="color:red">attribute.html.jinja</p>
{% endblock logs %}

Wyświetl plik

@ -0,0 +1,7 @@
{% extends "_base/backlinks.html.jinja" %}
{% block logs scoped %}
<p style="color:red">backlinks.html.jinja</p>
{% endblock logs %}

Wyświetl plik

@ -0,0 +1,7 @@
{% extends "_base/children.html.jinja" %}
{% block logs scoped %}
<p style="color:red">children.html.jinja</p>
{% endblock logs %}

Wyświetl plik

@ -0,0 +1,7 @@
{% extends "_base/class.html.jinja" %}
{% block logs scoped %}
<p style="color:red">class.html.jinja</p>
{% endblock logs %}

Wyświetl plik

@ -0,0 +1,7 @@
{% extends "_base/docstring.html.jinja" %}
{% block logs scoped %}
<p style="color:red">docstring.html.jinja</p>
{% endblock logs %}

Wyświetl plik

@ -0,0 +1,7 @@
{% extends "_base/docstring/admonition.html.jinja" %}
{% block logs scoped %}
<p style="color:red">docstring/admonition.html.jinja</p>
{% endblock logs %}

Wyświetl plik

@ -0,0 +1,7 @@
{% extends "_base/docstring/attributes.html.jinja" %}
{% block logs scoped %}
<p style="color:red">docstring/attributes.html.jinja</p>
{% endblock logs %}

Wyświetl plik

@ -0,0 +1,7 @@
{% extends "_base/docstring/classes.html.jinja" %}
{% block logs scoped %}
<p style="color:red">docstring/classes.html.jinja</p>
{% endblock logs %}

Wyświetl plik

@ -0,0 +1,7 @@
{% extends "_base/docstring/examples.html.jinja" %}
{% block logs scoped %}
<p style="color:red">docstring/examples.html.jinja</p>
{% endblock logs %}

Wyświetl plik

@ -0,0 +1,7 @@
{% extends "_base/docstring/functions.html.jinja" %}
{% block logs scoped %}
<p style="color:red">docstring/functions.html.jinja</p>
{% endblock logs %}

Wyświetl plik

@ -0,0 +1,7 @@
{% extends "_base/docstring/modules.html.jinja" %}
{% block logs scoped %}
<p style="color:red">docstring/modules.html.jinja</p>
{% endblock logs %}

Wyświetl plik

@ -0,0 +1,7 @@
{% extends "_base/docstring/other_parameters.html.jinja" %}
{% block logs scoped %}
<p style="color:red">docstring/other_parameters.html.jinja</p>
{% endblock logs %}

Wyświetl plik

@ -0,0 +1,7 @@
{% extends "_base/docstring/parameters.html.jinja" %}
{% block logs scoped %}
<p style="color:red">docstring/parameters.html.jinja</p>
{% endblock logs %}

Wyświetl plik

@ -0,0 +1,7 @@
{% extends "_base/docstring/raises.html.jinja" %}
{% block logs scoped %}
<p style="color:red">docstring/raises.html.jinja</p>
{% endblock logs %}

Wyświetl plik

@ -0,0 +1,7 @@
{% extends "_base/docstring/receives.html.jinja" %}
{% block logs scoped %}
<p style="color:red">docstring/receives.html.jinja</p>
{% endblock logs %}

Wyświetl plik

@ -0,0 +1,7 @@
{% extends "_base/docstring/returns.html.jinja" %}
{% block logs scoped %}
<p style="color:red">docstring/returns.html.jinja</p>
{% endblock logs %}

Wyświetl plik

@ -0,0 +1,7 @@
{% extends "_base/docstring/warns.html.jinja" %}
{% block logs scoped %}
<p style="color:red">docstring/warns.html.jinja</p>
{% endblock logs %}

Wyświetl plik

@ -0,0 +1,7 @@
{% extends "_base/docstring/yields.html.jinja" %}
{% block logs scoped %}
<p style="color:red">docstring/yields.html.jinja</p>
{% endblock logs %}

Wyświetl plik

@ -0,0 +1,8 @@
{% extends "_base/expression.html.jinja" %}
{% block logs scoped %}
<< expression.html.jinja >>
{% endblock logs %}

Wyświetl plik

@ -0,0 +1,7 @@
{% extends "_base/function.html.jinja" %}
{% block logs scoped %}
<p style="color:red">function.html.jinja</p>
{% endblock logs %}

Wyświetl plik

@ -0,0 +1,7 @@
{% extends "_base/labels.html.jinja" %}
{% block logs scoped %}
<p style="color:red">labels.html.jinja</p>
{% endblock logs %}

Wyświetl plik

@ -0,0 +1,7 @@
{% extends "_base/language.html.jinja" %}
{% block logs scoped %}
<p style="color:red">language.html.jinja</p>
{% endblock logs %}

Wyświetl plik

@ -0,0 +1,7 @@
{% extends "_base/languages/en.html.jinja" %}
{% block logs scoped %}
<p style="color:red">languages/en.html.jinja</p>
{% endblock logs %}

Wyświetl plik

@ -0,0 +1,7 @@
{% extends "_base/languages/ja.html.jinja" %}
{% block logs scoped %}
<p style="color:red">languages/ja.html.jinja</p>
{% endblock logs %}

Wyświetl plik

@ -0,0 +1,7 @@
{% extends "_base/languages/zh.html.jinja" %}
{% block logs scoped %}
<p style="color:red">languages/zh.html.jinja</p>
{% endblock logs %}

Wyświetl plik

@ -0,0 +1,7 @@
{% extends "_base/module.html.jinja" %}
{% block logs scoped %}
<p style="color:red">module.html.jinja</p>
{% endblock logs %}

Wyświetl plik

@ -0,0 +1,7 @@
{% extends "_base/signature.html.jinja" %}
{% block logs scoped %}
<< signature.html.jinja >>
{% endblock logs %}

Wyświetl plik

@ -0,0 +1,7 @@
{% extends "_base/summary.html.jinja" %}
{% block logs scoped %}
<p style="color:red">summary.html.jinja</p>
{% endblock logs %}

Wyświetl plik

@ -0,0 +1,7 @@
{% extends "_base/summary/attributes.html.jinja" %}
{% block logs scoped %}
<p style="color:red">summary/attributes.html.jinja</p>
{% endblock logs %}

Wyświetl plik

@ -0,0 +1,7 @@
{% extends "_base/summary/classes.html.jinja" %}
{% block logs scoped %}
<p style="color:red">summary/classes.html.jinja</p>
{% endblock logs %}

Wyświetl plik

@ -0,0 +1,7 @@
{% extends "_base/summary/functions.html.jinja" %}
{% block logs scoped %}
<p style="color:red">summary/functions.html.jinja</p>
{% endblock logs %}

Wyświetl plik

@ -0,0 +1,7 @@
{% extends "_base/summary/modules.html.jinja" %}
{% block logs scoped %}
<p style="color:red">summary/modules.html.jinja</p>
{% endblock logs %}

Wyświetl plik

@ -127,10 +127,12 @@ plugins:
- coverage
- mkdocs-typer2
- mkdocstrings:
custom_templates: 'docs/templates'
handlers:
python:
paths: [amqtt]
options:
annotation_path: "full"
docstring_options:
ignore_init_summary: true
docstring_section_style: list
@ -148,6 +150,8 @@ plugins:
show_symbol_type_toc: true
signature_crossrefs: true
summary: true
extensions:
- git-revision-date-localized:
enabled: !ENV [DEPLOY, false]
enable_creation_date: true

Wyświetl plik

@ -10,88 +10,6 @@ from amqtt.contexts import BrokerConfig, ListenerConfig, Dictable
logger = logging.getLogger(__name__)
@dataclass
class NestedData(Dictable):
nested_one: int | None = None
nested_two: str | None = None
nested_three: str | None = "empty option"
@dataclass
class MainData(Dictable):
main_one: str | None = None
main_two: str | None = 2
main_three: dict[str, NestedData] | None = field(default_factory=dict)
data_a = {
'main_one': 'value main one',
'main_three': {
'nested_key_one': {
'nested_one': 101,
'nested_two': 'value nested two'
}
}
}
data_b = {
'option_one': 'value ten'
}
data_c = {
'main_two': 'value main two',
'main_three': {
'nested_key_one': {
'nested_two': 'other nested two'
},
'nested_key_two': {
'nested_one': 202,
'nested_three': 'other nested three'
}
}
}
def test_mismatched_data():
with pytest.raises(UnexpectedDataError):
_ = from_dict(data_class=MainData, data=data_b, config=Config(cast=[StrEnum], strict=True))
def test_nested_data():
main_a = from_dict(data_class=MainData, data=data_a, config=Config(cast=[StrEnum], strict=True))
main_c = from_dict(data_class=MainData, data=data_c, config=Config(cast=[StrEnum], strict=True))
main_a.update(main_c)
assert isinstance(main_c, MainData)
assert isinstance(main_c, MainData)
assert isinstance(main_c.main_three, dict)
assert 'nested_key_one' in main_c.main_three
assert isinstance(main_c.main_three['nested_key_one'], NestedData)
assert 'nested_key_two' in main_c.main_three
assert main_c.main_three['nested_key_two'].nested_two == 'value nested two'
assert main_a.main_one == 'value main one'
assert main_a.main_two == 'value main two'
assert main_a.main_three['nested_key_one'].nested_two == 'other nested two'
assert main_a.main_three['nested_key_one'] == 'value twelve'
assert main_a.main_three['nested_key_one'] == 'value three'
data = {
"listeners": {
"default": {
"bind": "0.0.0.0:1883",
"max_connections": 50
},
"secure": {
"bind": "0.0.0.0:8883",
"cafile": "ca.key"
}
}
}
def _test_broker_config():
# Parse with dacite