Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/aiodns/compat.py: 59%
Shortcuts on this page
r m x toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
Shortcuts on this page
r m x toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
1"""
2Compatibility layer for pycares 5.x API.
4This module provides result types compatible with pycares 4.x API
5to maintain backward compatibility with existing code.
6"""
8from __future__ import annotations
10from dataclasses import dataclass
11from typing import Union, cast
13import pycares
15from . import error
17_SINGLE_RESULT_QTYPES = frozenset(
18 {
19 pycares.QUERY_TYPE_CNAME,
20 pycares.QUERY_TYPE_SOA,
21 pycares.QUERY_TYPE_PTR,
22 }
23)
26def _maybe_str(data: bytes) -> str | bytes:
27 """Decode bytes as ASCII, return bytes if decode fails (pycares 4.x)."""
28 try:
29 return data.decode('ascii')
30 except UnicodeDecodeError:
31 return data
34@dataclass(frozen=True, slots=True)
35class AresQueryAResult:
36 """A record result (compatible with pycares 4.x ares_query_a_result)."""
38 host: str
39 ttl: int
42@dataclass(frozen=True, slots=True)
43class AresQueryAAAAResult:
44 """AAAA record result (pycares 4.x compat)."""
46 host: str
47 ttl: int
50@dataclass(frozen=True, slots=True)
51class AresQueryCNAMEResult:
52 """CNAME record result (pycares 4.x compat)."""
54 cname: str
55 ttl: int
58@dataclass(frozen=True, slots=True)
59class AresQueryMXResult:
60 """MX record result (pycares 4.x compat)."""
62 host: str
63 priority: int
64 ttl: int
67@dataclass(frozen=True, slots=True)
68class AresQueryNSResult:
69 """NS record result (pycares 4.x compat)."""
71 host: str
72 ttl: int
75@dataclass(frozen=True, slots=True)
76class AresQueryTXTResult:
77 """TXT record result (pycares 4.x compat)."""
79 text: str | bytes # str if ASCII, bytes otherwise (pycares 4.x behavior)
80 ttl: int
83@dataclass(frozen=True, slots=True)
84class AresQuerySOAResult:
85 """SOA record result (pycares 4.x compat)."""
87 nsname: str
88 hostmaster: str
89 serial: int
90 refresh: int
91 retry: int
92 expires: int
93 minttl: int
94 ttl: int
97@dataclass(frozen=True, slots=True)
98class AresQuerySRVResult:
99 """SRV record result (pycares 4.x compat)."""
101 host: str
102 port: int
103 priority: int
104 weight: int
105 ttl: int
108@dataclass(frozen=True, slots=True)
109class AresQueryNAPTRResult:
110 """NAPTR record result (pycares 4.x compat)."""
112 order: int
113 preference: int
114 flags: str
115 service: str
116 regex: str
117 replacement: str
118 ttl: int
121@dataclass(frozen=True, slots=True)
122class AresQueryCAAResult:
123 """CAA record result (pycares 4.x compat)."""
125 critical: int
126 property: str
127 value: str
128 ttl: int
131@dataclass(frozen=True, slots=True)
132class AresQueryPTRResult:
133 """PTR record result (pycares 4.x compat)."""
135 name: str
136 ttl: int
137 aliases: list[str]
140@dataclass(frozen=True, slots=True)
141class AresHostResult:
142 """Host result (compatible with pycares 4.x ares_host_result)."""
144 name: str
145 aliases: list[str]
146 addresses: list[str]
149# Type alias for a single converted record
150ConvertedRecord = Union[
151 AresQueryAResult,
152 AresQueryAAAAResult,
153 AresQueryCNAMEResult,
154 AresQueryMXResult,
155 AresQueryNSResult,
156 AresQueryTXTResult,
157 AresQuerySOAResult,
158 AresQuerySRVResult,
159 AresQueryNAPTRResult,
160 AresQueryCAAResult,
161 AresQueryPTRResult,
162 pycares.DNSRecord, # Unknown types returned as-is
163]
165# Type alias for query results
166QueryResult = Union[
167 list[AresQueryAResult],
168 list[AresQueryAAAAResult],
169 AresQueryCNAMEResult,
170 list[AresQueryMXResult],
171 list[AresQueryNSResult],
172 list[AresQueryTXTResult],
173 AresQuerySOAResult,
174 list[AresQuerySRVResult],
175 list[AresQueryNAPTRResult],
176 list[AresQueryCAAResult],
177 AresQueryPTRResult,
178 list[ConvertedRecord], # For ANY query type
179]
182def _convert_record(record: pycares.DNSRecord) -> ConvertedRecord:
183 """Convert a single DNS record to pycares 4.x compatible format."""
184 ttl = record.ttl
185 record_type = record.type
187 if record_type == pycares.QUERY_TYPE_A:
188 a_data = cast(pycares.ARecordData, record.data)
189 return AresQueryAResult(host=a_data.addr, ttl=ttl)
190 if record_type == pycares.QUERY_TYPE_AAAA:
191 aaaa_data = cast(pycares.AAAARecordData, record.data)
192 return AresQueryAAAAResult(host=aaaa_data.addr, ttl=ttl)
193 if record_type == pycares.QUERY_TYPE_CNAME:
194 cname_data = cast(pycares.CNAMERecordData, record.data)
195 return AresQueryCNAMEResult(cname=cname_data.cname, ttl=ttl)
196 if record_type == pycares.QUERY_TYPE_MX:
197 mx_data = cast(pycares.MXRecordData, record.data)
198 return AresQueryMXResult(
199 host=mx_data.exchange, priority=mx_data.priority, ttl=ttl
200 )
201 if record_type == pycares.QUERY_TYPE_NS:
202 ns_data = cast(pycares.NSRecordData, record.data)
203 return AresQueryNSResult(host=ns_data.nsdname, ttl=ttl)
204 if record_type == pycares.QUERY_TYPE_TXT:
205 txt_data = cast(pycares.TXTRecordData, record.data)
206 return AresQueryTXTResult(text=_maybe_str(txt_data.data), ttl=ttl)
207 if record_type == pycares.QUERY_TYPE_SOA:
208 soa_data = cast(pycares.SOARecordData, record.data)
209 return AresQuerySOAResult(
210 nsname=soa_data.mname,
211 hostmaster=soa_data.rname,
212 serial=soa_data.serial,
213 refresh=soa_data.refresh,
214 retry=soa_data.retry,
215 expires=soa_data.expire,
216 minttl=soa_data.minimum,
217 ttl=ttl,
218 )
219 if record_type == pycares.QUERY_TYPE_SRV:
220 srv_data = cast(pycares.SRVRecordData, record.data)
221 return AresQuerySRVResult(
222 host=srv_data.target,
223 port=srv_data.port,
224 priority=srv_data.priority,
225 weight=srv_data.weight,
226 ttl=ttl,
227 )
228 if record_type == pycares.QUERY_TYPE_NAPTR:
229 naptr_data = cast(pycares.NAPTRRecordData, record.data)
230 return AresQueryNAPTRResult(
231 order=naptr_data.order,
232 preference=naptr_data.preference,
233 flags=naptr_data.flags,
234 service=naptr_data.service,
235 regex=naptr_data.regexp,
236 replacement=naptr_data.replacement,
237 ttl=ttl,
238 )
239 if record_type == pycares.QUERY_TYPE_CAA:
240 caa_data = cast(pycares.CAARecordData, record.data)
241 return AresQueryCAAResult(
242 critical=caa_data.critical,
243 property=caa_data.tag,
244 value=caa_data.value,
245 ttl=ttl,
246 )
247 if record_type == pycares.QUERY_TYPE_PTR:
248 ptr_data = cast(pycares.PTRRecordData, record.data)
249 return AresQueryPTRResult(name=ptr_data.dname, ttl=ttl, aliases=[])
250 # Return raw record for unknown types
251 return record
254def convert_result(dns_result: pycares.DNSResult, qtype: int) -> QueryResult:
255 """Convert pycares 5.x DNSResult to pycares 4.x compatible format."""
256 # For ANY - convert all records and return mixed list
257 if qtype == pycares.QUERY_TYPE_ANY:
258 return [_convert_record(record) for record in dns_result.answer]
260 results: list[ConvertedRecord] = []
262 for record in dns_result.answer:
263 record_type = record.type
265 # Filter by query type since answer can contain other types
266 # (e.g., CNAME records when querying for A/AAAA)
267 if record_type != qtype:
268 continue
270 converted = _convert_record(record)
272 # CNAME, SOA, and PTR return single result, not list
273 if record_type in _SINGLE_RESULT_QTYPES:
274 return cast(QueryResult, converted)
276 results.append(converted)
278 # NOERROR/NODATA: c-ares delivered ARES_SUCCESS but the answer has no
279 # records of the queried type. pycares 4.x raised ARES_ENODATA here;
280 # without this branch single-result qtypes (CNAME/SOA/PTR) would
281 # resolve to [] and crash callers reading .name/.cname/.nsname.
282 if not results:
283 raise error.DNSError(
284 pycares.errno.ARES_ENODATA,
285 pycares.errno.strerror(pycares.errno.ARES_ENODATA),
286 )
288 return results