Skip to content

Commit ab3de2b

Browse files
committed
Start on new settings classes generator based on Jinja2
Old one was very hard to understand. New one works with `tools/generate-docs-nm-settings-docs-gir.py` from NetworkManager source code. However, there are several problems with that script that need upstream to fix it.
1 parent d2a4010 commit ab3de2b

File tree

1 file changed

+190
-0
lines changed

1 file changed

+190
-0
lines changed
Lines changed: 190 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,190 @@
1+
#!/usr/bin/env python
2+
# SPDX-License-Identifier: LGPL-2.1-or-later
3+
from __future__ import annotations
4+
from argparse import ArgumentParser
5+
from pathlib import Path
6+
from xml.etree.ElementTree import parse, Element
7+
8+
from typing import List, Optional
9+
from jinja2 import Environment
10+
import builtins
11+
import keyword
12+
13+
dbus_to_python_extra_typing_imports = {
14+
"as": ("List", ),
15+
"au": ("List", ),
16+
"a{ss}": ("Dict", ),
17+
"aa{sv}": ("List", "Tuple", "Any"),
18+
"aau": ("List", ),
19+
"aay": ("List", )
20+
}
21+
22+
dbus_to_python_type_map = {
23+
"b": "bool",
24+
"s": "str",
25+
"i": "int",
26+
"u": "int",
27+
"t": "int",
28+
"x": "int",
29+
"y": "int",
30+
"as": "List[str]",
31+
"au": "List[int]",
32+
"ay": "bytes",
33+
"a{ss}": "Dict[str, str]",
34+
"aa{sv}": "List[Tuple[str, Any]]",
35+
"aau": "List[List[int]]",
36+
"aay": "List[bytes]",
37+
# Legacy types:
38+
"a(ayuay)": "array of legacy IPv6 address struct",
39+
"a(ayuayu)": "array of legacy IPv6 route struct",
40+
}
41+
42+
dbus_name_type_map = {
43+
'array of array of uint32': 'aau',
44+
'array of byte array': 'aay',
45+
'array of legacy IPv6 address struct': 'a(ayuay)',
46+
'array of legacy IPv6 route struct': 'a(ayuayu)',
47+
'array of string': 'as',
48+
'array of uint32': 'au',
49+
'array of vardict': 'aa{sv}',
50+
"array of 'a{sv}'": 'aa{sv}', # wireguard.peers uses this, fix NM upstream
51+
'boolean': 'b',
52+
'byte': 'y',
53+
'byte array': 'ay',
54+
'dict of string to string': 'a{ss}',
55+
'int32': 'i',
56+
'int64': 'x',
57+
'string': 's',
58+
'uint32': 'u',
59+
'uint64': 't',
60+
}
61+
62+
python_name_replacements = {
63+
'type': 'connection_type',
64+
'id': 'pretty_id',
65+
}
66+
67+
68+
def must_replace_name(name: str) -> bool:
69+
return (keyword.iskeyword(name)
70+
or keyword.issoftkeyword(name)
71+
or hasattr(builtins, name))
72+
73+
74+
class NmSettingPropertyIntrospection:
75+
def __init__(self, name: str,
76+
description: str,
77+
name_upper: str,
78+
dbus_type: str,
79+
python_type: str,
80+
parent: NmSettingsIntrospection,
81+
default: Optional[str] = None,
82+
) -> None:
83+
self.name = name
84+
self.description = description
85+
self.name_upper = name_upper
86+
self.python_name = name_upper.lower()
87+
self.dbus_type = dbus_type
88+
self.python_type = python_type
89+
self.default = default
90+
91+
if must_replace_name(self.python_name):
92+
self.python_name = (f"{parent.name_upper.lower()}"
93+
f"_{self.python_name}")
94+
95+
extra_typing = dbus_to_python_extra_typing_imports.get(dbus_type)
96+
if extra_typing is not None:
97+
parent.typing_imports.update(extra_typing)
98+
99+
100+
class NmSettingsIntrospection:
101+
def __init__(self, name: str, description: str, name_upper: str,
102+
) -> None:
103+
self.name = name
104+
self.description = description
105+
self.name_upper = name_upper
106+
self.python_class_name = name.capitalize() + 'Settings'
107+
108+
self.typing_imports = {'Optional'}
109+
110+
self.properties: List[NmSettingPropertyIntrospection] = []
111+
112+
113+
def convert_property(node: Element,
114+
parent: NmSettingsIntrospection
115+
) -> NmSettingPropertyIntrospection:
116+
options = node.attrib
117+
118+
unconverted_type = options.pop('type')
119+
try:
120+
dbus_type = dbus_name_type_map[unconverted_type]
121+
except KeyError:
122+
dbus_type = dbus_name_type_map[unconverted_type.split('(')[1][:-1]]
123+
124+
options['dbus_type'] = dbus_type
125+
options['python_type'] = dbus_to_python_type_map[dbus_type]
126+
127+
return NmSettingPropertyIntrospection(**options, parent=parent)
128+
129+
130+
def generate_introspection(root: Element) -> List[NmSettingsIntrospection]:
131+
settings_introspection: List[NmSettingsIntrospection] = []
132+
for setting_node in root:
133+
setting = NmSettingsIntrospection(**setting_node.attrib)
134+
setting.properties.extend(
135+
(convert_property(x, setting) for x in setting_node)
136+
)
137+
138+
settings_introspection.append(setting)
139+
140+
return settings_introspection
141+
142+
143+
setttngs_template_str = """# SPDX-License-Identifier: LGPL-2.1-or-later
144+
# This file was generated by tools/generate-settings-dataclasses-jinja.py,
145+
# if possible, please make changes by also updating the script.
146+
from __future__ import annotations
147+
from dataclasses import dataclass, field
148+
from typing import {{ setting.typing_imports|sort|join(', ') }}
149+
from .base import NetworkManagerSettingsMixin
150+
151+
152+
@dataclass
153+
class {{ setting.python_class_name }}(NetworkManagerSettingsMixin):
154+
\"""{{ setting.description }}\"""
155+
{% for property in setting.properties %}
156+
{{ property.python_name }}: Optional[{{ property.python_type }}] = field(
157+
metadata={
158+
'dbus_name': '{{ property.name }}',
159+
'dbus_type': '{{ property.dbus_type }}',
160+
},
161+
default=None,
162+
){% endfor %}
163+
164+
"""
165+
166+
jinja_env = Environment()
167+
settings_template = jinja_env.from_string(setttngs_template_str)
168+
169+
170+
def main(settings_xml_path: Path) -> None:
171+
tree = parse(settings_xml_path)
172+
introspection = generate_introspection(tree.getroot())
173+
174+
settings_dir = Path('sdbus_async/networkmanager/settings/')
175+
for setting in introspection:
176+
setting_py_file = settings_dir / (setting.name_upper.lower() + '.py')
177+
with open(setting_py_file, mode='w') as f:
178+
f.write(settings_template.render(setting=setting))
179+
180+
181+
if __name__ == '__main__':
182+
arg_parser = ArgumentParser()
183+
arg_parser.add_argument(
184+
'nm_settings_xml',
185+
type=Path,
186+
default=Path('man/nm-settings-docs-dbus.xml'),
187+
)
188+
args = arg_parser.parse_args()
189+
190+
main(args.nm_settings_xml)

0 commit comments

Comments
 (0)