-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy path_customizations.py
More file actions
304 lines (221 loc) · 8.81 KB
/
_customizations.py
File metadata and controls
304 lines (221 loc) · 8.81 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
# coding: utf-8
"""Runtime customizations for generated WHMCS models."""
from __future__ import annotations
import warnings
from collections.abc import Mapping
import re
from types import NoneType, UnionType
from typing import Annotated, Any, Optional, Union, get_args, get_origin
warnings.filterwarnings(
"ignore",
message='Field name "register" in "TldPricingInfo" shadows an attribute in parent "BaseModel"',
)
_INT_PATTERN = re.compile(r"^[+-]?\d+$")
def _unwrap_annotation(annotation: Any) -> Any:
origin = get_origin(annotation)
if origin is Annotated:
return _unwrap_annotation(get_args(annotation)[0])
if origin in (Union, UnionType):
args = [arg for arg in get_args(annotation) if arg is not NoneType]
if len(args) == 1:
return _unwrap_annotation(args[0])
return annotation
def _normalize_value(annotation: Any, value: Any) -> Any:
if value is None:
return None
annotation = _unwrap_annotation(annotation)
origin = get_origin(annotation)
if isinstance(annotation, type) and hasattr(annotation, "model_fields"):
if isinstance(value, Mapping):
return _normalize_model_payload(annotation, value)
return value
if origin is list:
item_annotation = get_args(annotation)[0] if get_args(annotation) else Any
if isinstance(value, list):
return [_normalize_value(item_annotation, item) for item in value]
return value
if origin is dict:
value_args = get_args(annotation)
item_annotation = value_args[1] if len(value_args) > 1 else Any
if isinstance(value, Mapping):
return {
key: _normalize_value(item_annotation, item)
for key, item in value.items()
}
return value
if annotation is int and isinstance(value, str):
stripped = value.strip()
if stripped == "":
return None
if _INT_PATTERN.fullmatch(stripped):
return int(stripped)
return value
def _normalize_model_payload(model_cls: Any, payload: Mapping[str, Any]) -> dict[str, Any]:
normalized = dict(payload)
field_map: dict[str, Any] = {}
for field_name, field in model_cls.model_fields.items():
field_map[field_name] = field
field_alias = getattr(field, "alias", None)
if field_alias:
field_map[field_alias] = field
for key, value in list(normalized.items()):
field = field_map.get(key)
if field is None:
continue
normalized[key] = _normalize_value(field.annotation, value)
return normalized
def _patch_from_dict(model_cls: Any) -> None:
def _from_dict(cls, obj):
if obj is None:
return None
if not isinstance(obj, Mapping):
return cls.model_validate(obj)
return cls.model_validate(_normalize_model_payload(cls, obj))
model_cls.from_dict = classmethod(_from_dict)
def _should_patch_model(model_cls: Any) -> bool:
if not isinstance(model_cls, type) or not hasattr(model_cls, "model_fields"):
return False
model_fields = getattr(model_cls, "model_fields", {})
if "actual_instance" in model_fields:
return False
if "additional_properties" in model_fields:
return False
return True
def _coerce_bool(value: Any) -> Any:
if value in (None, ""):
return value
if isinstance(value, bool):
return value
if isinstance(value, (int, float)):
return bool(value)
if isinstance(value, str):
lowered = value.strip().lower()
if lowered in {"1", "true", "yes", "on"}:
return True
if lowered in {"0", "false", "no", "off"}:
return False
return value
def _normalize_domain_row(raw_domain: Mapping[str, Any]) -> dict[str, Any]:
normalized = dict(raw_domain)
for key in ("id", "userid", "orderid", "regperiod", "promoid"):
value = normalized.get(key)
if isinstance(value, str) and value.isdigit():
normalized[key] = int(value)
for key in ("firstpaymentamount", "recurringamount"):
value = normalized.get(key)
if isinstance(value, str) and value != "":
normalized[key] = float(value)
for key in ("dnsmanagement", "emailforwarding", "idprotection", "donotrenew"):
normalized[key] = _coerce_bool(normalized.get(key))
if normalized.get("expirydate") == "0000-00-00":
normalized["expirydate"] = None
return normalized
def _normalize_domains(raw_domains: Any) -> Optional[list[Any]]:
if raw_domains is None:
return None
if raw_domains == "":
return []
if isinstance(raw_domains, Mapping):
raw_domains = raw_domains.get("domain")
if raw_domains is None:
return []
if isinstance(raw_domains, list):
# pylint: disable=import-outside-toplevel
from whmcs_client.models.domain_info import DomainInfo
normalized_domains = []
for item in raw_domains:
if isinstance(item, Mapping):
normalized_domains.append(DomainInfo.from_dict(_normalize_domain_row(item)))
else:
normalized_domains.append(item)
return normalized_domains
return raw_domains
def _get_clients_domains_response_from_dict(cls, obj):
if obj is None:
return None
if not isinstance(obj, dict):
return cls.model_validate(obj)
normalized = _normalize_model_payload(cls, obj)
return cls.model_construct(
result=normalized.get("result"),
message=normalized.get("message"),
clientid=normalized.get("clientid"),
domainid=normalized.get("domainid"),
totalresults=normalized.get("totalresults"),
startnumber=normalized.get("startnumber"),
numreturned=normalized.get("numreturned"),
domains=_normalize_domains(obj.get("domains")),
)
def _get_clients_domains_response_to_dict(self):
output = {}
if self.result is not None:
output["result"] = self.result
if self.message is not None:
output["message"] = self.message
if self.clientid is not None:
output["clientid"] = self.clientid
if self.domainid is not None:
output["domainid"] = self.domainid
if self.totalresults is not None:
output["totalresults"] = self.totalresults
if self.startnumber is not None:
output["startnumber"] = self.startnumber
if self.numreturned is not None:
output["numreturned"] = self.numreturned
domains = self.domains
if domains is not None:
if isinstance(domains, list):
output["domains"] = [
item.to_dict() if hasattr(item, "to_dict") and callable(item.to_dict) else item
for item in domains
]
elif hasattr(domains, "to_dict") and callable(domains.to_dict):
output["domains"] = domains.to_dict()
else:
output["domains"] = domains
return output
def _coerce_string_list(value: Any) -> Any:
if value is None:
return None
if value == "":
return []
if isinstance(value, list):
return value
if isinstance(value, str):
parts = [item.strip() for item in value.split(",") if item.strip()]
return parts if parts else []
return [value]
def _add_order_response_from_dict(cls, obj):
if obj is None:
return None
if not isinstance(obj, dict):
return cls.model_validate(obj)
normalized = _normalize_model_payload(cls, obj)
orderid = normalized.get("orderid")
if isinstance(orderid, int):
orderid = str(orderid)
invoiceid = normalized.get("invoiceid")
if isinstance(invoiceid, int):
invoiceid = str(invoiceid)
return cls.model_construct(
result=normalized.get("result"),
message=normalized.get("message"),
orderid=orderid,
productids=_coerce_string_list(obj.get("productids")),
addonids=_coerce_string_list(obj.get("addonids")),
domainids=_coerce_string_list(obj.get("domainids")),
invoiceid=invoiceid,
)
def apply_customizations():
"""Apply runtime customizations to generated models."""
# pylint: disable=import-outside-toplevel
import whmcs_client.models as models_package
from whmcs_client.models.add_order_response import AddOrderResponse
from whmcs_client.models.get_clients_domains_response import GetClientsDomainsResponse
for model_name in dir(models_package):
model_cls = getattr(models_package, model_name)
if _should_patch_model(model_cls):
_patch_from_dict(model_cls)
GetClientsDomainsResponse.from_dict = classmethod(_get_clients_domains_response_from_dict)
GetClientsDomainsResponse.to_dict = _get_clients_domains_response_to_dict
AddOrderResponse.from_dict = classmethod(_add_order_response_from_dict)