pycares 4.5.0__cp313-cp313-win32.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
pycares/__init__.py ADDED
@@ -0,0 +1,855 @@
1
+
2
+ from ._cares import ffi as _ffi, lib as _lib
3
+ import _cffi_backend # hint for bundler tools
4
+
5
+ if _lib.ARES_SUCCESS != _lib.ares_library_init(_lib.ARES_LIB_INIT_ALL):
6
+ raise RuntimeError('Could not initialize c-ares')
7
+
8
+ from . import errno
9
+ from .utils import ascii_bytes, maybe_str, parse_name
10
+ from ._version import __version__
11
+
12
+ import collections.abc
13
+ import socket
14
+ import math
15
+ import functools
16
+ import sys
17
+
18
+
19
+ exported_pycares_symbols = [
20
+ # Flag values
21
+ 'ARES_FLAG_USEVC',
22
+ 'ARES_FLAG_PRIMARY',
23
+ 'ARES_FLAG_IGNTC',
24
+ 'ARES_FLAG_NORECURSE',
25
+ 'ARES_FLAG_STAYOPEN',
26
+ 'ARES_FLAG_NOSEARCH',
27
+ 'ARES_FLAG_NOALIASES',
28
+ 'ARES_FLAG_NOCHECKRESP',
29
+
30
+ # Nameinfo flag values
31
+ 'ARES_NI_NOFQDN',
32
+ 'ARES_NI_NUMERICHOST',
33
+ 'ARES_NI_NAMEREQD',
34
+ 'ARES_NI_NUMERICSERV',
35
+ 'ARES_NI_DGRAM',
36
+ 'ARES_NI_TCP',
37
+ 'ARES_NI_UDP',
38
+ 'ARES_NI_SCTP',
39
+ 'ARES_NI_DCCP',
40
+ 'ARES_NI_NUMERICSCOPE',
41
+ 'ARES_NI_LOOKUPHOST',
42
+ 'ARES_NI_LOOKUPSERVICE',
43
+ 'ARES_NI_IDN',
44
+ 'ARES_NI_IDN_ALLOW_UNASSIGNED',
45
+ 'ARES_NI_IDN_USE_STD3_ASCII_RULES',
46
+
47
+ # Bad socket
48
+ 'ARES_SOCKET_BAD',
49
+ ]
50
+
51
+ for symbol in exported_pycares_symbols:
52
+ globals()[symbol] = getattr(_lib, symbol)
53
+
54
+
55
+ exported_pycares_symbols_map = {
56
+ # Query types
57
+ "QUERY_TYPE_A" : "T_A",
58
+ "QUERY_TYPE_AAAA" : "T_AAAA",
59
+ "QUERY_TYPE_ANY" : "T_ANY",
60
+ "QUERY_TYPE_CAA" : "T_CAA",
61
+ "QUERY_TYPE_CNAME" : "T_CNAME",
62
+ "QUERY_TYPE_MX" : "T_MX",
63
+ "QUERY_TYPE_NAPTR" : "T_NAPTR",
64
+ "QUERY_TYPE_NS" : "T_NS",
65
+ "QUERY_TYPE_PTR" : "T_PTR",
66
+ "QUERY_TYPE_SOA" : "T_SOA",
67
+ "QUERY_TYPE_SRV" : "T_SRV",
68
+ "QUERY_TYPE_TXT" : "T_TXT",
69
+
70
+ # Query classes
71
+ "QUERY_CLASS_IN" : "C_IN",
72
+ "QUERY_CLASS_CHAOS": "C_CHAOS",
73
+ "QUERY_CLASS_HS" : "C_HS",
74
+ "QUERY_CLASS_NONE" :"C_NONE",
75
+ "QUERY_CLASS_ANY" : "C_ANY",
76
+ }
77
+
78
+ for k, v in exported_pycares_symbols_map.items():
79
+ globals()[k] = getattr(_lib, v)
80
+
81
+
82
+ globals()['ARES_VERSION'] = maybe_str(_ffi.string(_lib.ares_version(_ffi.NULL)))
83
+
84
+
85
+ PYCARES_ADDRTTL_SIZE = 256
86
+
87
+
88
+ class AresError(Exception):
89
+ pass
90
+
91
+
92
+ # callback helpers
93
+
94
+ _global_set = set()
95
+
96
+ @_ffi.def_extern()
97
+ def _sock_state_cb(data, socket_fd, readable, writable):
98
+ sock_state_cb = _ffi.from_handle(data)
99
+ sock_state_cb(socket_fd, readable, writable)
100
+
101
+ @_ffi.def_extern()
102
+ def _host_cb(arg, status, timeouts, hostent):
103
+ callback = _ffi.from_handle(arg)
104
+ _global_set.discard(arg)
105
+
106
+ if status != _lib.ARES_SUCCESS:
107
+ result = None
108
+ else:
109
+ result = ares_host_result(hostent)
110
+ status = None
111
+
112
+ callback(result, status)
113
+
114
+ @_ffi.def_extern()
115
+ def _nameinfo_cb(arg, status, timeouts, node, service):
116
+ callback = _ffi.from_handle(arg)
117
+ _global_set.discard(arg)
118
+
119
+ if status != _lib.ARES_SUCCESS:
120
+ result = None
121
+ else:
122
+ result = ares_nameinfo_result(node, service)
123
+ status = None
124
+
125
+ callback(result, status)
126
+
127
+ @_ffi.def_extern()
128
+ def _query_cb(arg, status, timeouts, abuf, alen):
129
+ callback, query_type = _ffi.from_handle(arg)
130
+ _global_set.discard(arg)
131
+
132
+ if status == _lib.ARES_SUCCESS:
133
+ if query_type == _lib.T_ANY:
134
+ result = []
135
+ for qtype in (_lib.T_A, _lib.T_AAAA, _lib.T_CAA, _lib.T_CNAME, _lib.T_MX, _lib.T_NAPTR, _lib.T_NS, _lib.T_PTR, _lib.T_SOA, _lib.T_SRV, _lib.T_TXT):
136
+ r, status = parse_result(qtype, abuf, alen)
137
+ if status not in (None, _lib.ARES_ENODATA, _lib.ARES_EBADRESP):
138
+ result = None
139
+ break
140
+ if r is not None:
141
+ if isinstance(r, collections.abc.Iterable):
142
+ result.extend(r)
143
+ else:
144
+ result.append(r)
145
+ else:
146
+ status = None
147
+ else:
148
+ result, status = parse_result(query_type, abuf, alen)
149
+ else:
150
+ result = None
151
+
152
+ callback(result, status)
153
+
154
+ @_ffi.def_extern()
155
+ def _addrinfo_cb(arg, status, timeouts, res):
156
+ callback = _ffi.from_handle(arg)
157
+ _global_set.discard(arg)
158
+
159
+ if status != _lib.ARES_SUCCESS:
160
+ result = None
161
+ else:
162
+ result = ares_addrinfo_result(res)
163
+ status = None
164
+
165
+ callback(result, status)
166
+
167
+ def parse_result(query_type, abuf, alen):
168
+ if query_type == _lib.T_A:
169
+ addrttls = _ffi.new("struct ares_addrttl[]", PYCARES_ADDRTTL_SIZE)
170
+ naddrttls = _ffi.new("int*", PYCARES_ADDRTTL_SIZE)
171
+ parse_status = _lib.ares_parse_a_reply(abuf, alen, _ffi.NULL, addrttls, naddrttls)
172
+ if parse_status != _lib.ARES_SUCCESS:
173
+ result = None
174
+ status = parse_status
175
+ else:
176
+ result = [ares_query_a_result(addrttls[i]) for i in range(naddrttls[0])]
177
+ status = None
178
+ elif query_type == _lib.T_AAAA:
179
+ addrttls = _ffi.new("struct ares_addr6ttl[]", PYCARES_ADDRTTL_SIZE)
180
+ naddrttls = _ffi.new("int*", PYCARES_ADDRTTL_SIZE)
181
+ parse_status = _lib.ares_parse_aaaa_reply(abuf, alen, _ffi.NULL, addrttls, naddrttls)
182
+ if parse_status != _lib.ARES_SUCCESS:
183
+ result = None
184
+ status = parse_status
185
+ else:
186
+ result = [ares_query_aaaa_result(addrttls[i]) for i in range(naddrttls[0])]
187
+ status = None
188
+ elif query_type == _lib.T_CAA:
189
+ caa_reply = _ffi.new("struct ares_caa_reply **")
190
+ parse_status = _lib.ares_parse_caa_reply(abuf, alen, caa_reply)
191
+ if parse_status != _lib.ARES_SUCCESS:
192
+ result = None
193
+ status = parse_status
194
+ else:
195
+ result = []
196
+ caa_reply_ptr = caa_reply[0]
197
+ while caa_reply_ptr != _ffi.NULL:
198
+ result.append(ares_query_caa_result(caa_reply_ptr))
199
+ caa_reply_ptr = caa_reply_ptr.next
200
+ _lib.ares_free_data(caa_reply[0])
201
+ status = None
202
+ elif query_type == _lib.T_CNAME:
203
+ host = _ffi.new("struct hostent **")
204
+ parse_status = _lib.ares_parse_a_reply(abuf, alen, host, _ffi.NULL, _ffi.NULL)
205
+ if parse_status != _lib.ARES_SUCCESS:
206
+ result = None
207
+ status = parse_status
208
+ else:
209
+ result = ares_query_cname_result(host[0])
210
+ _lib.ares_free_hostent(host[0])
211
+ status = None
212
+ elif query_type == _lib.T_MX:
213
+ mx_reply = _ffi.new("struct ares_mx_reply **")
214
+ parse_status = _lib.ares_parse_mx_reply(abuf, alen, mx_reply)
215
+ if parse_status != _lib.ARES_SUCCESS:
216
+ result = None
217
+ status = parse_status
218
+ else:
219
+ result = []
220
+ mx_reply_ptr = mx_reply[0]
221
+ while mx_reply_ptr != _ffi.NULL:
222
+ result.append(ares_query_mx_result(mx_reply_ptr))
223
+ mx_reply_ptr = mx_reply_ptr.next
224
+ _lib.ares_free_data(mx_reply[0])
225
+ status = None
226
+ elif query_type == _lib.T_NAPTR:
227
+ naptr_reply = _ffi.new("struct ares_naptr_reply **")
228
+ parse_status = _lib.ares_parse_naptr_reply(abuf, alen, naptr_reply)
229
+ if parse_status != _lib.ARES_SUCCESS:
230
+ result = None
231
+ status = parse_status
232
+ else:
233
+ result = []
234
+ naptr_reply_ptr = naptr_reply[0]
235
+ while naptr_reply_ptr != _ffi.NULL:
236
+ result.append(ares_query_naptr_result(naptr_reply_ptr))
237
+ naptr_reply_ptr = naptr_reply_ptr.next
238
+ _lib.ares_free_data(naptr_reply[0])
239
+ status = None
240
+ elif query_type == _lib.T_NS:
241
+ hostent = _ffi.new("struct hostent **")
242
+ parse_status = _lib.ares_parse_ns_reply(abuf, alen, hostent)
243
+ if parse_status != _lib.ARES_SUCCESS:
244
+ result = None
245
+ status = parse_status
246
+ else:
247
+ result = []
248
+ host = hostent[0]
249
+ i = 0
250
+ while host.h_aliases[i] != _ffi.NULL:
251
+ result.append(ares_query_ns_result(host.h_aliases[i]))
252
+ i += 1
253
+ _lib.ares_free_hostent(host)
254
+ status = None
255
+ elif query_type == _lib.T_PTR:
256
+ hostent = _ffi.new("struct hostent **")
257
+ parse_status = _lib.ares_parse_ptr_reply(abuf, alen, _ffi.NULL, 0, socket.AF_UNSPEC, hostent)
258
+ if parse_status != _lib.ARES_SUCCESS:
259
+ result = None
260
+ status = parse_status
261
+ else:
262
+ aliases = []
263
+ host = hostent[0]
264
+ i = 0
265
+ while host.h_aliases[i] != _ffi.NULL:
266
+ aliases.append(maybe_str(_ffi.string(host.h_aliases[i])))
267
+ i += 1
268
+ result = ares_query_ptr_result(host, aliases)
269
+ _lib.ares_free_hostent(host)
270
+ status = None
271
+ elif query_type == _lib.T_SOA:
272
+ soa_reply = _ffi.new("struct ares_soa_reply **")
273
+ parse_status = _lib.ares_parse_soa_reply(abuf, alen, soa_reply)
274
+ if parse_status != _lib.ARES_SUCCESS:
275
+ result = None
276
+ status = parse_status
277
+ else:
278
+ result = ares_query_soa_result(soa_reply[0])
279
+ _lib.ares_free_data(soa_reply[0])
280
+ status = None
281
+ elif query_type == _lib.T_SRV:
282
+ srv_reply = _ffi.new("struct ares_srv_reply **")
283
+ parse_status = _lib.ares_parse_srv_reply(abuf, alen, srv_reply)
284
+ if parse_status != _lib.ARES_SUCCESS:
285
+ result = None
286
+ status = parse_status
287
+ else:
288
+ result = []
289
+ srv_reply_ptr = srv_reply[0]
290
+ while srv_reply_ptr != _ffi.NULL:
291
+ result.append(ares_query_srv_result(srv_reply_ptr))
292
+ srv_reply_ptr = srv_reply_ptr.next
293
+ _lib.ares_free_data(srv_reply[0])
294
+ status = None
295
+ elif query_type == _lib.T_TXT:
296
+ txt_reply = _ffi.new("struct ares_txt_ext **")
297
+ parse_status = _lib.ares_parse_txt_reply_ext(abuf, alen, txt_reply)
298
+ if parse_status != _lib.ARES_SUCCESS:
299
+ result = None
300
+ status = parse_status
301
+ else:
302
+ result = []
303
+ txt_reply_ptr = txt_reply[0]
304
+ tmp_obj = None
305
+ while True:
306
+ if txt_reply_ptr == _ffi.NULL:
307
+ if tmp_obj is not None:
308
+ result.append(ares_query_txt_result(tmp_obj))
309
+ break
310
+ if txt_reply_ptr.record_start == 1:
311
+ if tmp_obj is not None:
312
+ result.append(ares_query_txt_result(tmp_obj))
313
+ tmp_obj = ares_query_txt_result_chunk(txt_reply_ptr)
314
+ else:
315
+ new_chunk = ares_query_txt_result_chunk(txt_reply_ptr)
316
+ tmp_obj.text += new_chunk.text
317
+ txt_reply_ptr = txt_reply_ptr.next
318
+ _lib.ares_free_data(txt_reply[0])
319
+ status = None
320
+ else:
321
+ raise ValueError("invalid query type specified")
322
+
323
+ return result, status
324
+
325
+
326
+ class Channel:
327
+ __qtypes__ = (_lib.T_A, _lib.T_AAAA, _lib.T_ANY, _lib.T_CAA, _lib.T_CNAME, _lib.T_MX, _lib.T_NAPTR, _lib.T_NS, _lib.T_PTR, _lib.T_SOA, _lib.T_SRV, _lib.T_TXT)
328
+ __qclasses__ = (_lib.C_IN, _lib.C_CHAOS, _lib.C_HS, _lib.C_NONE, _lib.C_ANY)
329
+
330
+ def __init__(self,
331
+ flags = None,
332
+ timeout = None,
333
+ tries = None,
334
+ ndots = None,
335
+ tcp_port = None,
336
+ udp_port = None,
337
+ servers = None,
338
+ domains = None,
339
+ lookups = None,
340
+ sock_state_cb = None,
341
+ socket_send_buffer_size = None,
342
+ socket_receive_buffer_size = None,
343
+ rotate = False,
344
+ local_ip = None,
345
+ local_dev = None,
346
+ resolvconf_path = None):
347
+
348
+ channel = _ffi.new("ares_channel *")
349
+ options = _ffi.new("struct ares_options *")
350
+ optmask = 0
351
+
352
+ if flags is not None:
353
+ options.flags = flags
354
+ optmask = optmask | _lib.ARES_OPT_FLAGS
355
+
356
+ if timeout is not None:
357
+ options.timeout = int(timeout * 1000)
358
+ optmask = optmask | _lib.ARES_OPT_TIMEOUTMS
359
+
360
+ if tries is not None:
361
+ options.tries = tries
362
+ optmask = optmask | _lib.ARES_OPT_TRIES
363
+
364
+ if ndots is not None:
365
+ options.ndots = ndots
366
+ optmask = optmask | _lib.ARES_OPT_NDOTS
367
+
368
+ if tcp_port is not None:
369
+ options.tcp_port = tcp_port
370
+ optmask = optmask | _lib.ARES_OPT_TCP_PORT
371
+
372
+ if udp_port is not None:
373
+ options.udp_port = udp_port
374
+ optmask = optmask | _lib.ARES_OPT_UDP_PORT
375
+
376
+ if socket_send_buffer_size is not None:
377
+ options.socket_send_buffer_size = socket_send_buffer_size
378
+ optmask = optmask | _lib.ARES_OPT_SOCK_SNDBUF
379
+
380
+ if socket_receive_buffer_size is not None:
381
+ options.socket_receive_buffer_size = socket_receive_buffer_size
382
+ optmask = optmask | _lib.ARES_OPT_SOCK_RCVBUF
383
+
384
+ if sock_state_cb:
385
+ if not callable(sock_state_cb):
386
+ raise TypeError("sock_state_cb is not callable")
387
+
388
+ userdata = _ffi.new_handle(sock_state_cb)
389
+
390
+ # This must be kept alive while the channel is alive.
391
+ self._sock_state_cb_handle = userdata
392
+
393
+ options.sock_state_cb = _lib._sock_state_cb
394
+ options.sock_state_cb_data = userdata
395
+ optmask = optmask | _lib.ARES_OPT_SOCK_STATE_CB
396
+
397
+ if lookups:
398
+ options.lookups = _ffi.new('char[]', ascii_bytes(lookups))
399
+ optmask = optmask | _lib.ARES_OPT_LOOKUPS
400
+
401
+ if domains:
402
+ strs = [_ffi.new("char[]", ascii_bytes(i)) for i in domains]
403
+ c = _ffi.new("char *[%d]" % (len(domains) + 1))
404
+ for i in range(len(domains)):
405
+ c[i] = strs[i]
406
+
407
+ options.domains = c
408
+ options.ndomains = len(domains)
409
+ optmask = optmask | _lib.ARES_OPT_DOMAINS
410
+
411
+ if rotate:
412
+ optmask = optmask | _lib.ARES_OPT_ROTATE
413
+
414
+ if resolvconf_path is not None:
415
+ optmask = optmask | _lib.ARES_OPT_RESOLVCONF
416
+ options.resolvconf_path = _ffi.new('char[]', ascii_bytes(resolvconf_path))
417
+
418
+ r = _lib.ares_init_options(channel, options, optmask)
419
+ if r != _lib.ARES_SUCCESS:
420
+ raise AresError('Failed to initialize c-ares channel')
421
+
422
+ self._channel = _ffi.gc(channel, lambda x: _lib.ares_destroy(x[0]))
423
+
424
+ if servers:
425
+ self.servers = servers
426
+
427
+ if local_ip:
428
+ self.set_local_ip(local_ip)
429
+
430
+ if local_dev:
431
+ self.set_local_dev(local_dev)
432
+
433
+ def cancel(self):
434
+ _lib.ares_cancel(self._channel[0])
435
+
436
+ @property
437
+ def servers(self):
438
+ servers = _ffi.new("struct ares_addr_node **")
439
+
440
+ r = _lib.ares_get_servers(self._channel[0], servers)
441
+ if r != _lib.ARES_SUCCESS:
442
+ raise AresError(r, errno.strerror(r))
443
+
444
+ server_list = []
445
+ server = _ffi.new("struct ares_addr_node **", servers[0])
446
+ while True:
447
+ if server == _ffi.NULL:
448
+ break
449
+
450
+ ip = _ffi.new("char []", _lib.INET6_ADDRSTRLEN)
451
+ s = server[0]
452
+ if _ffi.NULL != _lib.ares_inet_ntop(s.family, _ffi.addressof(s.addr), ip, _lib.INET6_ADDRSTRLEN):
453
+ server_list.append(maybe_str(_ffi.string(ip, _lib.INET6_ADDRSTRLEN)))
454
+
455
+ server = s.next
456
+
457
+ return server_list
458
+
459
+ @servers.setter
460
+ def servers(self, servers):
461
+ c = _ffi.new("struct ares_addr_node[%d]" % len(servers))
462
+ for i, server in enumerate(servers):
463
+ if _lib.ares_inet_pton(socket.AF_INET, ascii_bytes(server), _ffi.addressof(c[i].addr.addr4)) == 1:
464
+ c[i].family = socket.AF_INET
465
+ elif _lib.ares_inet_pton(socket.AF_INET6, ascii_bytes(server), _ffi.addressof(c[i].addr.addr6)) == 1:
466
+ c[i].family = socket.AF_INET6
467
+ else:
468
+ raise ValueError("invalid IP address")
469
+
470
+ if i > 0:
471
+ c[i - 1].next = _ffi.addressof(c[i])
472
+
473
+ r = _lib.ares_set_servers(self._channel[0], c)
474
+ if r != _lib.ARES_SUCCESS:
475
+ raise AresError(r, errno.strerror(r))
476
+
477
+ def getsock(self):
478
+ rfds = []
479
+ wfds = []
480
+ socks = _ffi.new("ares_socket_t [%d]" % _lib.ARES_GETSOCK_MAXNUM)
481
+ bitmask = _lib.ares_getsock(self._channel[0], socks, _lib.ARES_GETSOCK_MAXNUM)
482
+ for i in range(_lib.ARES_GETSOCK_MAXNUM):
483
+ if _lib.ARES_GETSOCK_READABLE(bitmask, i):
484
+ rfds.append(socks[i])
485
+ if _lib.ARES_GETSOCK_WRITABLE(bitmask, i):
486
+ wfds.append(socks[i])
487
+
488
+ return rfds, wfds
489
+
490
+ def process_fd(self, read_fd, write_fd):
491
+ _lib.ares_process_fd(self._channel[0], _ffi.cast("ares_socket_t", read_fd), _ffi.cast("ares_socket_t", write_fd))
492
+
493
+ def timeout(self, t = None):
494
+ maxtv = _ffi.NULL
495
+ tv = _ffi.new("struct timeval*")
496
+
497
+ if t is not None:
498
+ if t >= 0.0:
499
+ maxtv = _ffi.new("struct timeval*")
500
+ maxtv.tv_sec = int(math.floor(t))
501
+ maxtv.tv_usec = int(math.fmod(t, 1.0) * 1000000)
502
+ else:
503
+ raise ValueError("timeout needs to be a positive number or None")
504
+
505
+ _lib.ares_timeout(self._channel[0], maxtv, tv)
506
+
507
+ if tv == _ffi.NULL:
508
+ return 0.0
509
+
510
+ return (tv.tv_sec + tv.tv_usec / 1000000.0)
511
+
512
+ def gethostbyaddr(self, addr, callback):
513
+ if not callable(callback):
514
+ raise TypeError("a callable is required")
515
+
516
+ addr4 = _ffi.new("struct in_addr*")
517
+ addr6 = _ffi.new("struct ares_in6_addr*")
518
+ if _lib.ares_inet_pton(socket.AF_INET, ascii_bytes(addr), (addr4)) == 1:
519
+ address = addr4
520
+ family = socket.AF_INET
521
+ elif _lib.ares_inet_pton(socket.AF_INET6, ascii_bytes(addr), (addr6)) == 1:
522
+ address = addr6
523
+ family = socket.AF_INET6
524
+ else:
525
+ raise ValueError("invalid IP address")
526
+
527
+ userdata = _ffi.new_handle(callback)
528
+ _global_set.add(userdata)
529
+ _lib.ares_gethostbyaddr(self._channel[0], address, _ffi.sizeof(address[0]), family, _lib._host_cb, userdata)
530
+
531
+ def gethostbyname(self, name, family, callback):
532
+ if not callable(callback):
533
+ raise TypeError("a callable is required")
534
+
535
+ userdata = _ffi.new_handle(callback)
536
+ _global_set.add(userdata)
537
+ _lib.ares_gethostbyname(self._channel[0], parse_name(name), family, _lib._host_cb, userdata)
538
+
539
+ def getaddrinfo(self, host, port, callback, family=0, type=0, proto=0, flags=0):
540
+ if not callable(callback):
541
+ raise TypeError("a callable is required")
542
+
543
+ if port is None:
544
+ service = _ffi.NULL
545
+ elif isinstance(port, int):
546
+ service = str(port).encode('ascii')
547
+ else:
548
+ service = ascii_bytes(port)
549
+
550
+ userdata = _ffi.new_handle(callback)
551
+ _global_set.add(userdata)
552
+
553
+ hints = _ffi.new('struct ares_addrinfo_hints*')
554
+ hints.ai_flags = flags
555
+ hints.ai_family = family
556
+ hints.ai_socktype = type
557
+ hints.ai_protocol = proto
558
+ _lib.ares_getaddrinfo(self._channel[0], parse_name(host), service, hints, _lib._addrinfo_cb, userdata)
559
+
560
+ def query(self, name, query_type, callback, query_class=None):
561
+ self._do_query(_lib.ares_query, name, query_type, callback, query_class=query_class)
562
+
563
+ def search(self, name, query_type, callback, query_class=None):
564
+ self._do_query(_lib.ares_search, name, query_type, callback, query_class=query_class)
565
+
566
+ def _do_query(self, func, name, query_type, callback, query_class=None):
567
+ if not callable(callback):
568
+ raise TypeError('a callable is required')
569
+
570
+ if query_type not in self.__qtypes__:
571
+ raise ValueError('invalid query type specified')
572
+
573
+ if query_class is None:
574
+ query_class = _lib.C_IN
575
+
576
+ if query_class not in self.__qclasses__:
577
+ raise ValueError('invalid query class specified')
578
+
579
+ userdata = _ffi.new_handle((callback, query_type))
580
+ _global_set.add(userdata)
581
+ func(self._channel[0], parse_name(name), query_class, query_type, _lib._query_cb, userdata)
582
+
583
+ def set_local_ip(self, ip):
584
+ addr4 = _ffi.new("struct in_addr*")
585
+ addr6 = _ffi.new("struct ares_in6_addr*")
586
+ if _lib.ares_inet_pton(socket.AF_INET, ascii_bytes(ip), addr4) == 1:
587
+ _lib.ares_set_local_ip4(self._channel[0], socket.ntohl(addr4.s_addr))
588
+ elif _lib.ares_inet_pton(socket.AF_INET6, ascii_bytes(ip), addr6) == 1:
589
+ _lib.ares_set_local_ip6(self._channel[0], addr6)
590
+ else:
591
+ raise ValueError("invalid IP address")
592
+
593
+ def getnameinfo(self, address, flags, callback):
594
+ if not callable(callback):
595
+ raise TypeError("a callable is required")
596
+
597
+ if len(address) == 2:
598
+ (ip, port) = address
599
+ sa4 = _ffi.new("struct sockaddr_in*")
600
+ if _lib.ares_inet_pton(socket.AF_INET, ascii_bytes(ip), _ffi.addressof(sa4.sin_addr)) != 1:
601
+ raise ValueError("Invalid IPv4 address %r" % ip)
602
+ sa4.sin_family = socket.AF_INET
603
+ sa4.sin_port = socket.htons(port)
604
+ sa = sa4
605
+ elif len(address) == 4:
606
+ (ip, port, flowinfo, scope_id) = address
607
+ sa6 = _ffi.new("struct sockaddr_in6*")
608
+ if _lib.ares_inet_pton(socket.AF_INET6, ascii_bytes(ip), _ffi.addressof(sa6.sin6_addr)) != 1:
609
+ raise ValueError("Invalid IPv6 address %r" % ip)
610
+ sa6.sin6_family = socket.AF_INET6
611
+ sa6.sin6_port = socket.htons(port)
612
+ sa6.sin6_flowinfo = socket.htonl(flowinfo) # I'm unsure about byteorder here.
613
+ sa6.sin6_scope_id = scope_id # Yes, without htonl.
614
+ sa = sa6
615
+ else:
616
+ raise ValueError("Invalid address argument")
617
+
618
+ userdata = _ffi.new_handle(callback)
619
+ _global_set.add(userdata)
620
+ _lib.ares_getnameinfo(self._channel[0], _ffi.cast("struct sockaddr*", sa), _ffi.sizeof(sa[0]), flags, _lib._nameinfo_cb, userdata)
621
+
622
+ def set_local_dev(self, dev):
623
+ _lib.ares_set_local_dev(self._channel[0], dev)
624
+
625
+
626
+ class AresResult:
627
+ __slots__ = ()
628
+
629
+ def __repr__(self):
630
+ attrs = ['%s=%s' % (a, getattr(self, a)) for a in self.__slots__]
631
+ return '<%s> %s' % (self.__class__.__name__, ', '.join(attrs))
632
+
633
+
634
+ # DNS query result types
635
+ #
636
+
637
+ class ares_query_a_result(AresResult):
638
+ __slots__ = ('host', 'ttl')
639
+ type = 'A'
640
+
641
+ def __init__(self, ares_addrttl):
642
+ buf = _ffi.new("char[]", _lib.INET6_ADDRSTRLEN)
643
+ _lib.ares_inet_ntop(socket.AF_INET, _ffi.addressof(ares_addrttl.ipaddr), buf, _lib.INET6_ADDRSTRLEN)
644
+ self.host = maybe_str(_ffi.string(buf, _lib.INET6_ADDRSTRLEN))
645
+ self.ttl = ares_addrttl.ttl
646
+
647
+
648
+ class ares_query_aaaa_result(AresResult):
649
+ __slots__ = ('host', 'ttl')
650
+ type = 'AAAA'
651
+
652
+ def __init__(self, ares_addrttl):
653
+ buf = _ffi.new("char[]", _lib.INET6_ADDRSTRLEN)
654
+ _lib.ares_inet_ntop(socket.AF_INET6, _ffi.addressof(ares_addrttl.ip6addr), buf, _lib.INET6_ADDRSTRLEN)
655
+ self.host = maybe_str(_ffi.string(buf, _lib.INET6_ADDRSTRLEN))
656
+ self.ttl = ares_addrttl.ttl
657
+
658
+
659
+ class ares_query_caa_result(AresResult):
660
+ __slots__ = ('critical', 'property', 'value', 'ttl')
661
+ type = 'CAA'
662
+
663
+ def __init__(self, caa):
664
+ self.critical = caa.critical
665
+ self.property = maybe_str(_ffi.string(caa.property, caa.plength))
666
+ self.value = maybe_str(_ffi.string(caa.value, caa.length))
667
+ self.ttl = -1
668
+
669
+
670
+ class ares_query_cname_result(AresResult):
671
+ __slots__ = ('cname', 'ttl')
672
+ type = 'CNAME'
673
+
674
+ def __init__(self, host):
675
+ self.cname = maybe_str(_ffi.string(host.h_name))
676
+ self.ttl = -1
677
+
678
+
679
+ class ares_query_mx_result(AresResult):
680
+ __slots__ = ('host', 'priority', 'ttl')
681
+ type = 'MX'
682
+
683
+ def __init__(self, mx):
684
+ self.host = maybe_str(_ffi.string(mx.host))
685
+ self.priority = mx.priority
686
+ self.ttl = -1
687
+
688
+
689
+ class ares_query_naptr_result(AresResult):
690
+ __slots__ = ('order', 'preference', 'flags', 'service', 'regex', 'replacement', 'ttl')
691
+ type = 'NAPTR'
692
+
693
+ def __init__(self, naptr):
694
+ self.order = naptr.order
695
+ self.preference = naptr.preference
696
+ self.flags = maybe_str(_ffi.string(naptr.flags))
697
+ self.service = maybe_str(_ffi.string(naptr.service))
698
+ self.regex = maybe_str(_ffi.string(naptr.regexp))
699
+ self.replacement = maybe_str(_ffi.string(naptr.replacement))
700
+ self.ttl = -1
701
+
702
+
703
+ class ares_query_ns_result(AresResult):
704
+ __slots__ = ('host', 'ttl')
705
+ type = 'NS'
706
+
707
+ def __init__(self, ns):
708
+ self.host = maybe_str(_ffi.string(ns))
709
+ self.ttl = -1
710
+
711
+
712
+ class ares_query_ptr_result(AresResult):
713
+ __slots__ = ('name', 'ttl', 'aliases')
714
+ type = 'PTR'
715
+
716
+ def __init__(self, hostent, aliases):
717
+ self.name = maybe_str(_ffi.string(hostent.h_name))
718
+ self.aliases = aliases
719
+ self.ttl = -1
720
+
721
+
722
+ class ares_query_soa_result(AresResult):
723
+ __slots__ = ('nsname', 'hostmaster', 'serial', 'refresh', 'retry', 'expires', 'minttl', 'ttl')
724
+ type = 'SOA'
725
+
726
+ def __init__(self, soa):
727
+ self.nsname = maybe_str(_ffi.string(soa.nsname))
728
+ self.hostmaster = maybe_str(_ffi.string(soa.hostmaster))
729
+ self.serial = soa.serial
730
+ self.refresh = soa.refresh
731
+ self.retry = soa.retry
732
+ self.expires = soa.expire
733
+ self.minttl = soa.minttl
734
+ self.ttl = -1
735
+
736
+
737
+ class ares_query_srv_result(AresResult):
738
+ __slots__ = ('host', 'port', 'priority', 'weight', 'ttl')
739
+ type = 'SRV'
740
+
741
+ def __init__(self, srv):
742
+ self.host = maybe_str(_ffi.string(srv.host))
743
+ self.port = srv.port
744
+ self.priority = srv.priority
745
+ self.weight = srv.weight
746
+ self.ttl = -1
747
+
748
+
749
+ class ares_query_txt_result(AresResult):
750
+ __slots__ = ('text', 'ttl')
751
+ type = 'TXT'
752
+
753
+ def __init__(self, txt_chunk):
754
+ self.text = maybe_str(txt_chunk.text)
755
+ self.ttl = -1
756
+
757
+
758
+ class ares_query_txt_result_chunk(AresResult):
759
+ __slots__ = ('text', 'ttl')
760
+ type = 'TXT'
761
+
762
+ def __init__(self, txt):
763
+ self.text = _ffi.string(txt.txt)
764
+ self.ttl = -1
765
+
766
+
767
+ # Other result types
768
+ #
769
+
770
+ class ares_host_result(AresResult):
771
+ __slots__ = ('name', 'aliases', 'addresses')
772
+
773
+ def __init__(self, hostent):
774
+ self.name = maybe_str(_ffi.string(hostent.h_name))
775
+ self.aliases = []
776
+ self.addresses = []
777
+ i = 0
778
+ while hostent.h_aliases[i] != _ffi.NULL:
779
+ self.aliases.append(maybe_str(_ffi.string(hostent.h_aliases[i])))
780
+ i += 1
781
+
782
+ i = 0
783
+ while hostent.h_addr_list[i] != _ffi.NULL:
784
+ buf = _ffi.new("char[]", _lib.INET6_ADDRSTRLEN)
785
+ if _ffi.NULL != _lib.ares_inet_ntop(hostent.h_addrtype, hostent.h_addr_list[i], buf, _lib.INET6_ADDRSTRLEN):
786
+ self.addresses.append(maybe_str(_ffi.string(buf, _lib.INET6_ADDRSTRLEN)))
787
+ i += 1
788
+
789
+
790
+ class ares_nameinfo_result(AresResult):
791
+ __slots__ = ('node', 'service')
792
+
793
+ def __init__(self, node, service):
794
+ self.node = maybe_str(_ffi.string(node))
795
+ self.service = maybe_str(_ffi.string(service)) if service != _ffi.NULL else None
796
+
797
+
798
+ class ares_addrinfo_node_result(AresResult):
799
+ __slots__ = ('ttl', 'flags', 'family', 'socktype', 'protocol', 'addr')
800
+
801
+ def __init__(self, ares_node):
802
+ self.ttl = ares_node.ai_ttl
803
+ self.flags = ares_node.ai_flags
804
+ self.socktype = ares_node.ai_socktype
805
+ self.protocol = ares_node.ai_protocol
806
+
807
+ addr = ares_node.ai_addr
808
+ assert addr.sa_family == ares_node.ai_family
809
+ ip = _ffi.new("char []", _lib.INET6_ADDRSTRLEN)
810
+ if addr.sa_family == socket.AF_INET:
811
+ self.family = socket.AF_INET
812
+ s = _ffi.cast("struct sockaddr_in*", addr)
813
+ if _ffi.NULL != _lib.ares_inet_ntop(s.sin_family, _ffi.addressof(s.sin_addr), ip, _lib.INET6_ADDRSTRLEN):
814
+ # (address, port) 2-tuple for AF_INET
815
+ self.addr = (_ffi.string(ip, _lib.INET6_ADDRSTRLEN), socket.ntohs(s.sin_port))
816
+ elif addr.sa_family == socket.AF_INET6:
817
+ self.family = socket.AF_INET6
818
+ s = _ffi.cast("struct sockaddr_in6*", addr)
819
+ if _ffi.NULL != _lib.ares_inet_ntop(s.sin6_family, _ffi.addressof(s.sin6_addr), ip, _lib.INET6_ADDRSTRLEN):
820
+ # (address, port, flow info, scope id) 4-tuple for AF_INET6
821
+ self.addr = (_ffi.string(ip, _lib.INET6_ADDRSTRLEN), socket.ntohs(s.sin6_port), s.sin6_flowinfo, s.sin6_scope_id)
822
+ else:
823
+ raise ValueError("invalid sockaddr family")
824
+
825
+
826
+ class ares_addrinfo_cname_result(AresResult):
827
+ __slots__ = ('ttl', 'alias', 'name')
828
+
829
+ def __init__(self, ares_cname):
830
+ self.ttl = ares_cname.ttl
831
+ self.alias = maybe_str(_ffi.string(ares_cname.alias))
832
+ self.name = maybe_str(_ffi.string(ares_cname.name))
833
+
834
+
835
+ class ares_addrinfo_result(AresResult):
836
+ __slots__ = ('cnames', 'nodes')
837
+
838
+ def __init__(self, ares_addrinfo):
839
+ self.cnames = []
840
+ self.nodes = []
841
+ cname_ptr = ares_addrinfo.cnames
842
+ while cname_ptr != _ffi.NULL:
843
+ self.cnames.append(ares_addrinfo_cname_result(cname_ptr))
844
+ cname_ptr = cname_ptr.next
845
+ node_ptr = ares_addrinfo.nodes
846
+ while node_ptr != _ffi.NULL:
847
+ self.nodes.append(ares_addrinfo_node_result(node_ptr))
848
+ node_ptr = node_ptr.ai_next
849
+ _lib.ares_freeaddrinfo(ares_addrinfo)
850
+
851
+
852
+
853
+ __all__ = exported_pycares_symbols + list(exported_pycares_symbols_map.keys()) + ['AresError', 'Channel', 'errno', '__version__']
854
+
855
+ del exported_pycares_symbols, exported_pycares_symbols_map
pycares/__main__.py ADDED
@@ -0,0 +1,84 @@
1
+
2
+ import collections.abc
3
+ import pycares
4
+ import select
5
+ import socket
6
+ import sys
7
+
8
+
9
+ def wait_channel(channel):
10
+ while True:
11
+ read_fds, write_fds = channel.getsock()
12
+ if not read_fds and not write_fds:
13
+ break
14
+ timeout = channel.timeout()
15
+ if not timeout:
16
+ channel.process_fd(pycares.ARES_SOCKET_BAD, pycares.ARES_SOCKET_BAD)
17
+ continue
18
+ rlist, wlist, xlist = select.select(read_fds, write_fds, [], timeout)
19
+ for fd in rlist:
20
+ channel.process_fd(fd, pycares.ARES_SOCKET_BAD)
21
+ for fd in wlist:
22
+ channel.process_fd(pycares.ARES_SOCKET_BAD, fd)
23
+
24
+
25
+ def cb(result, error):
26
+ if error is not None:
27
+ print('Error: (%d) %s' % (error, pycares.errno.strerror(error)))
28
+ else:
29
+ parts = [
30
+ ';; QUESTION SECTION:',
31
+ ';%s\t\t\tIN\t%s' % (hostname, qtype.upper()),
32
+ '',
33
+ ';; ANSWER SECTION:'
34
+ ]
35
+
36
+ if not isinstance(result, collections.abc.Iterable):
37
+ result = [result]
38
+
39
+ for r in result:
40
+ txt = '%s\t\t%d\tIN\t%s' % (hostname, r.ttl, r.type)
41
+ if r.type in ('A', 'AAAA'):
42
+ parts.append('%s\t%s' % (txt, r.host))
43
+ elif r.type == 'CAA':
44
+ parts.append('%s\t%d %s "%s"' % (txt, r.critical, r.property, r.value))
45
+ elif r.type == 'CNAME':
46
+ parts.append('%s\t%s' % (txt, r.cname))
47
+ elif r.type == 'MX':
48
+ parts.append('%s\t%d %s' % (txt, r.priority, r.host))
49
+ elif r.type == 'NAPTR':
50
+ parts.append('%s\t%d %d "%s" "%s" "%s" %s' % (txt, r.order, r.preference, r.flags, r.service, r.regex, r.replacement))
51
+ elif r.type == 'NS':
52
+ parts.append('%s\t%s' % (txt, r.host))
53
+ elif r.type == 'PTR':
54
+ parts.append('%s\t%s' % (txt, r.name))
55
+ elif r.type == 'SOA':
56
+ parts.append('%s\t%s %s %d %d %d %d %d' % (txt, r.nsname, r.hostmaster, r.serial, r.refresh, r.retry, r.expires, r.minttl))
57
+ elif r.type == 'SRV':
58
+ parts.append('%s\t%d %d %d %s' % (txt, r.priority, r.weight, r.port, r.host))
59
+ elif r.type == 'TXT':
60
+ parts.append('%s\t"%s"' % (txt, r.text))
61
+
62
+ print('\n'.join(parts))
63
+
64
+
65
+ channel = pycares.Channel()
66
+
67
+ if len(sys.argv) not in (2, 3):
68
+ print('Invalid arguments! Usage: python -m pycares [query_type] hostname')
69
+ sys.exit(1)
70
+
71
+ if len(sys.argv) == 2:
72
+ _, hostname = sys.argv
73
+ qtype = 'A'
74
+ else:
75
+ _, qtype, hostname = sys.argv
76
+
77
+ try:
78
+ query_type = getattr(pycares, 'QUERY_TYPE_%s' % qtype.upper())
79
+ except Exception:
80
+ print('Invalid query type: %s' % qtype)
81
+ sys.exit(1)
82
+
83
+ channel.query(hostname, query_type, cb)
84
+ wait_channel(channel)
pycares/_cares.pyd ADDED
Binary file
pycares/_version.py ADDED
@@ -0,0 +1,2 @@
1
+
2
+ __version__ = '4.5.0'
pycares/errno.py ADDED
@@ -0,0 +1,50 @@
1
+
2
+ from ._cares import ffi as _ffi, lib as _lib
3
+ from .utils import maybe_str
4
+
5
+
6
+ exported_pycares_symbols = [
7
+ 'ARES_SUCCESS',
8
+ # error codes
9
+ 'ARES_ENODATA',
10
+ 'ARES_EFORMERR',
11
+ 'ARES_ESERVFAIL',
12
+ 'ARES_ENOTFOUND',
13
+ 'ARES_ENOTIMP',
14
+ 'ARES_EREFUSED',
15
+ 'ARES_EBADQUERY',
16
+ 'ARES_EBADNAME',
17
+ 'ARES_EBADFAMILY',
18
+ 'ARES_EBADRESP',
19
+ 'ARES_ECONNREFUSED',
20
+ 'ARES_ETIMEOUT',
21
+ 'ARES_EOF',
22
+ 'ARES_EFILE',
23
+ 'ARES_ENOMEM',
24
+ 'ARES_EDESTRUCTION',
25
+ 'ARES_EBADSTR',
26
+ 'ARES_EBADFLAGS',
27
+ 'ARES_ENONAME',
28
+ 'ARES_EBADHINTS',
29
+ 'ARES_ENOTINITIALIZED',
30
+ 'ARES_ELOADIPHLPAPI',
31
+ 'ARES_EADDRGETNETWORKPARAMS',
32
+ 'ARES_ECANCELLED',
33
+ 'ARES_ESERVICE'
34
+ ]
35
+
36
+ errorcode = {}
37
+
38
+ for symbol in exported_pycares_symbols:
39
+ value = getattr(_lib, symbol)
40
+ globals()[symbol] = value
41
+ globals()["errorcode"][value] = symbol
42
+
43
+
44
+ def strerror(code):
45
+ return maybe_str(_ffi.string(_lib.ares_strerror(code)))
46
+
47
+
48
+ __all__ = exported_pycares_symbols + ['errorcode', 'strerror']
49
+
50
+ del exported_pycares_symbols
pycares/utils.py ADDED
@@ -0,0 +1,56 @@
1
+
2
+ try:
3
+ import idna as idna2008
4
+ except ImportError:
5
+ idna2008 = None
6
+
7
+
8
+ def ascii_bytes(data):
9
+ if isinstance(data, str):
10
+ return data.encode('ascii')
11
+ if isinstance(data, bytes):
12
+ return data
13
+ raise TypeError('only str (ascii encoding) and bytes are supported')
14
+
15
+
16
+ def maybe_str(data):
17
+ if isinstance(data, str):
18
+ return data
19
+ if isinstance(data, bytes):
20
+ try:
21
+ return data.decode('ascii')
22
+ except UnicodeDecodeError:
23
+ return data
24
+ raise TypeError('only str (ascii encoding) and bytes are supported')
25
+
26
+
27
+ def is_all_ascii(text):
28
+ for c in text:
29
+ if ord(c) > 0x7f:
30
+ return False
31
+ return True
32
+
33
+ def parse_name_idna2008(name):
34
+ parts = name.split('.')
35
+ r = []
36
+ for part in parts:
37
+ if is_all_ascii(part):
38
+ r.append(part.encode('ascii'))
39
+ else:
40
+ r.append(idna2008.encode(part))
41
+ return b'.'.join(r)
42
+
43
+ def parse_name(name):
44
+ if isinstance(name, str):
45
+ if is_all_ascii(name):
46
+ return name.encode('ascii')
47
+ if idna2008 is not None:
48
+ return parse_name_idna2008(name)
49
+ return name.encode('idna')
50
+ if isinstance(name, bytes):
51
+ return name
52
+ raise TypeError('only str and bytes are supported')
53
+
54
+
55
+ __all__ = ['ascii_bytes', 'maybe_str', 'parse_name']
56
+
@@ -0,0 +1,20 @@
1
+ Copyright (C) 2012 by Saúl Ibarra Corretgé
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining a copy
4
+ of this software and associated documentation files (the "Software"), to deal
5
+ in the Software without restriction, including without limitation the rights
6
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7
+ copies of the Software, and to permit persons to whom the Software is
8
+ furnished to do so, subject to the following conditions:
9
+
10
+ The above copyright notice and this permission notice shall be included in
11
+ all copies or substantial portions of the Software.
12
+
13
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19
+ THE SOFTWARE.
20
+
@@ -0,0 +1,151 @@
1
+ Metadata-Version: 2.1
2
+ Name: pycares
3
+ Version: 4.5.0
4
+ Summary: Python interface for c-ares
5
+ Home-page: http://github.com/saghul/pycares
6
+ Author: Saúl Ibarra Corretgé
7
+ Author-email: s@saghul.net
8
+ License: MIT
9
+ Platform: POSIX
10
+ Platform: Microsoft Windows
11
+ Classifier: Development Status :: 5 - Production/Stable
12
+ Classifier: Intended Audience :: Developers
13
+ Classifier: License :: OSI Approved :: MIT License
14
+ Classifier: Operating System :: POSIX
15
+ Classifier: Operating System :: Microsoft :: Windows
16
+ Classifier: Programming Language :: Python
17
+ Classifier: Programming Language :: Python :: 3
18
+ Classifier: Programming Language :: Python :: 3.9
19
+ Classifier: Programming Language :: Python :: 3.10
20
+ Classifier: Programming Language :: Python :: 3.11
21
+ Classifier: Programming Language :: Python :: 3.12
22
+ Classifier: Programming Language :: Python :: 3.13
23
+ Classifier: Programming Language :: Python :: Implementation :: CPython
24
+ Classifier: Programming Language :: Python :: Implementation :: PyPy
25
+ Requires-Python: >=3.9
26
+ Description-Content-Type: text/x-rst
27
+ License-File: LICENSE
28
+ Requires-Dist: cffi>=1.5.0
29
+ Provides-Extra: idna
30
+ Requires-Dist: idna>=2.1; extra == "idna"
31
+
32
+ Looking for new maintainers
33
+ ===========================
34
+
35
+ https://github.com/saghul/pycares/issues/139
36
+
37
+ pycares: Python interface for c-ares
38
+ ====================================
39
+
40
+ pycares is a Python module which provides an interface to c-ares.
41
+ `c-ares <https://c-ares.org>`_ is a C library that performs
42
+ DNS requests and name resolutions asynchronously.
43
+
44
+
45
+ Documentation
46
+ -------------
47
+
48
+ http://readthedocs.org/docs/pycares/
49
+
50
+
51
+ Bundled c-ares
52
+ --------------
53
+
54
+ pycares currently bundles c-ares as a submodule for ease of building. Using the system
55
+ provided c-ares is possible if the ``PYCARES_USE_SYSTEM_LIB`` environment variable is
56
+ set to ``1`` when building.
57
+
58
+ NOTE: Versions prior to 4.0.0 used to embed a modified c-ares with extended TTL support.
59
+ That is no longer the case and as a result only A and AAAA records will have TTL information.
60
+ Follow this PR in uppstream c-ares, looks like TTLs will be added: https://github.com/c-ares/c-ares/pull/393
61
+
62
+
63
+ Installation
64
+ ------------
65
+
66
+ GNU/Linux, macOS, Windows, others:
67
+
68
+ ::
69
+
70
+ pip install pycares
71
+
72
+ FreeBSD:
73
+
74
+ ::
75
+
76
+ cd /usr/ports/dns/py-pycares && make install
77
+
78
+
79
+ IDNA 2008 support
80
+ ^^^^^^^^^^^^^^^^^
81
+
82
+ If the ``idna`` package is installed, pycares will support IDNA 2008 encoding otherwise the builtin idna codec will be used,
83
+ which provides IDNA 2003 support.
84
+
85
+ You can force this at installation time as follows:
86
+
87
+ ::
88
+
89
+ pip install pycares[idna]
90
+
91
+
92
+ Running the test suite
93
+ ----------------------
94
+
95
+ From the top level directory, run: ``python -m unittest -v``
96
+
97
+ NOTE: Running the tests requires internet access and are somewhat environment sensitive because real DNS quesries
98
+ are made, there is no mocking. If you observe a failure that the CI cannot reproduce, please try to setup an
99
+ environment as close as the current CI.
100
+
101
+
102
+ Using it from the cli, a la dig
103
+ -------------------------------
104
+
105
+ This module can be used directly from the command line in a similar fashion to dig (limited, of course):
106
+
107
+ ::
108
+
109
+ $ python -m pycares google.com
110
+ ;; QUESTION SECTION:
111
+ ;google.com IN A
112
+
113
+ ;; ANSWER SECTION:
114
+ google.com 300 IN A 172.217.17.142
115
+
116
+ $ python -m pycares mx google.com
117
+ ;; QUESTION SECTION:
118
+ ;google.com IN MX
119
+
120
+ ;; ANSWER SECTION:
121
+ google.com 600 IN MX 50 alt4.aspmx.l.google.com
122
+ google.com 600 IN MX 10 aspmx.l.google.com
123
+ google.com 600 IN MX 40 alt3.aspmx.l.google.com
124
+ google.com 600 IN MX 20 alt1.aspmx.l.google.com
125
+ google.com 600 IN MX 30 alt2.aspmx.l.google.com
126
+
127
+
128
+ Author
129
+ ------
130
+
131
+ Saúl Ibarra Corretgé <s@saghul.net>
132
+
133
+
134
+ License
135
+ -------
136
+
137
+ Unless stated otherwise on-file pycares uses the MIT license, check LICENSE file.
138
+
139
+
140
+ Supported Python versions
141
+ -------------------------
142
+
143
+ Python >= 3.9 are supported. Both CPython and PyPy are supported.
144
+
145
+
146
+ Contributing
147
+ ------------
148
+
149
+ If you'd like to contribute, fork the project, make a patch and send a pull
150
+ request. Have a look at the surrounding code and please, make yours look
151
+ alike :-)
@@ -0,0 +1,11 @@
1
+ pycares/__init__.py,sha256=EIkDaFs0MmRAYi6Ce2VzcA3H14-25zPyiD_6TfSBCSQ,30609
2
+ pycares/__main__.py,sha256=-WwwGX4NQ8hpOqrNuCy59quCQJt7IAwQXdQjga5s4WA,2880
3
+ pycares/_cares.pyd,sha256=lda5UE9DOhQQlJpPbieXRsLYk7OYHybPy97BnquuXzQ,107008
4
+ pycares/_version.py,sha256=LndxDVTCo2jR91ipCX7gSKfdLq7B21t2ofpRE-CSa_E,25
5
+ pycares/errno.py,sha256=cForwpErZsnPUQYttEgUjWZgi37cyrOGCWxUcO76HUE,1089
6
+ pycares/utils.py,sha256=lKohvVcx0fjDcbkK0hGgvgY5WWnshj1Snxy-gGRbDNo,1399
7
+ pycares-4.5.0.dist-info/LICENSE,sha256=ZzIVbIpf5QFzaiLCDSjxhvH5EViAWLVO-W4ZgBzWvb8,1090
8
+ pycares-4.5.0.dist-info/METADATA,sha256=2zq6M_4O0AU22YCieCf9t1DXPLZzHvlce5fYCA1JFYs,4331
9
+ pycares-4.5.0.dist-info/WHEEL,sha256=pUXSsnGWJ09R6rv65E7mPoM8rWovBqrkySmEuK3ly_A,97
10
+ pycares-4.5.0.dist-info/top_level.txt,sha256=nIeo7L2XUVBQZO2YE6pH7tlKaBWTfmmRcXbqe_NWYCw,15
11
+ pycares-4.5.0.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (75.6.0)
3
+ Root-Is-Purelib: false
4
+ Tag: cp313-cp313-win32
5
+
@@ -0,0 +1,2 @@
1
+ _cares
2
+ pycares