aiodns

repository·master·Indexed 20 days ago

https://github.com/aio-libs/aiodns

An asynchronous DNS resolver for asyncio that uses pycares to perform non-blocking DNS queries. It provides the DNSResolver class for performing queries via query_dns(), as well as methods like getaddrinfo(), gethostbyaddr(), and getnameinfo(). The library supports various query types including A, AAAA, MX, TXT, and CNAME, and includes compatibility layers for pycares 4.x and 5.x result types.

Tokens
4K
Snippets
12
Records
15
Agent score
69%

What's inside aiodns

  1. Use DNSResolver as an async context manager

    master

    For short-lived scenarios like tests or one-off scripts where automatic cleanup is required, DNSResolver supports the async context manager protocol. Using async with ensures resolver.close() is called automatically upon exiting the block.

    Note: This is generally discouraged for production applications. DNSResolver instances are designed to be long-lived and reused. Frequent creation and destruction of resolvers adds unnecessary overhead.

    async with aiodns.DNSResolver() as resolver:
        result = await resolver.query_dns('example.com', 'A')
        # resolver.close() is called automatically when exiting the context
  2. Migrate from aiodns 3.x to 4.x/5.x

    master

    In aiodns 4.x, query_dns() was introduced to return native pycares 5.x result types. While the old query() method is deprecated, it remains for backward compatibility.

    In aiodns 5.x, the roles will swap: query() will become the primary API returning native types, and query_dns() will become an alias for query() to maintain backward compatibility.

    Migration Comparison:

    Old API (3.x style):

    result = await resolver.query('example.com', 'MX')
    for record in result:
        print(record.host, record.priority)

    New API (4.x/5.x style):

    result = await resolver.query_dns('example.com', 'MX')
    for record in result.answer:
        print(record.data.exchange, record.data.priority)
    # Old API (deprecated)
    result = await resolver.query('example.com', 'MX')
    for record in result:
        print(record.host, record.priority)
    
    # New API (recommended)
    result = await resolver.query_dns('example.com', 'MX')
    for record in result.answer:
        print(record.data.exchange, record.data.priority)
  3. Configure event loop for Windows users

    master

    If you are using a custom build of pycares that links against a system-provided c-ares library without thread-safety support, you cannot use the default ProactorEventLoop on Windows. This is because ProactorEventLoop lacks the add_reader or add_writer functions required by non-thread-safe c-ares builds.

    Note: This does not apply if you use the official prebuilt pycares wheels from PyPI (version 4.7.0 or later), as they include a thread-safe version of c-ares.

    To switch to the compatible loop, set the event loop policy very early in your application:

    import asyncio
    asyncio.set_event_loop_policy(asyncio.WindowsSelectorEventLoopPolicy())
    asyncio.set_event_loop_policy(asyncio.WindowsSelectorEventLoopPolicy())
  4. Perform asynchronous DNS resolution with DNSResolver

    master

    Use aiodns.DNSResolver() to perform asynchronous DNS queries. The primary method for modern versions (4.x+) is query_dns(host, type).

    Supported query types include: A, AAAA, ANY, CAA, CNAME, MX, NAPTR, NS, PTR, SOA, SRV, TXT.

    When using query_dns(), the returned asyncio.Future resolves to a pycares.DNSResult object. This object contains answer, authority, and additional attributes, which are lists of pycares.DNSRecord objects. Each record contains type, ttl, and data attributes.

    import asyncio
    import aiodns
    
    async def main():
        resolver = aiodns.DNSResolver()
        result = await resolver.query_dns('google.com', 'A')
        for record in result.answer:
            print(record.data.addr)
    
    asyncio.run(main())
  5. DNSResolver API Reference

    master

    The DNSResolver class provides the following methods for DNS operations:

    • query_dns(host, type): Performs DNS resolution for the given type and hostname. Returns a pycares.DNSResult object. Recommended for 4.x+.
    • query(host, type): Deprecated. Returns results in a legacy format for 3.x compatibility.
    • gethostbyname(host, socket_family): Deprecated. Use getaddrinfo() instead.
    • gethostbyaddr(name): Performs a reverse lookup for an address.
    • getaddrinfo(host, family, port, proto, type, flags): Resolves a host and port into a list of address info entries.
    • getnameinfo(sockaddr, flags): Resolves a socket address to a host and port.
    • cancel(): Cancels all pending DNS queries. Pending futures will raise a DNSError with ARES_ECANCELLED errno.
    • close(): Closes the resolver and releases resources. Must be called when the resolver is no longer needed. Should be called from the event loop that created it.
  6. Handle DNS errors with DNSError

    master

    When a DNS query fails (e.g., due to a malformed hostname or a network error), aiodns raises an error.DNSError. This exception contains the error number and a descriptive error message obtained from pycares.

    Example Error Handling:

    from aiodns import DNSResolver, error
    
    # ... inside async function
    try:
        await resolver.query_dns('invalid..hostname', 'A')
    except error.DNSError as e:
        print(f"Caught DNS error: {e}")
  7. Perform DNS queries with query_dns()

    master

    Use query_dns(host, qtype, qclass=None) to perform a DNS query. This method is the recommended way to interact with the resolver as it returns native pycares 5.x DNSResult objects.

    Parameters:

    • host: The hostname to query.
    • qtype: The query type (e.g., 'A', 'AAAA', 'MX', 'TXT', 'CNAME', etc.).
    • qclass: (Optional) The query class (e.g., 'IN', 'CHAOS').

    Returns: An asyncio.Future that resolves to a pycares.DNSResult object.

    import asyncio
    from aiodns import DNSResolver
    
    async def main():
        resolver = DNSResolver()
        try:
            result = await resolver.query_dns('google.com', 'A')
            print(result)
        except Exception as e:
            print(f"DNS Error: {e}")
    
    asyncio.run(main())
  8. Resolve hostnames with getaddrinfo()

    master

    The getaddrinfo(host, family, port, proto, type, flags) method resolves a hostname to its address information, similar to the standard library's socket.getaddrinfo but asynchronously.

    Parameters:

    • host: The hostname to resolve.
    • family: socket.AddressFamily (defaults to socket.AF_UNSPEC).
    • port: (Optional) The port number.
    • proto: Protocol (defaults to 0).
    • type: Socket type (defaults to 0).
    • flags: Resolver flags (defaults to 0).

    Returns: An asyncio.Future that resolves to a pycares.AddrInfoResult object.

    import asyncio
    import socket
    from aiodns import DNSResolver
    
    async def main():
        resolver = DNSResolver()
        result = await resolver.getaddrinfo('google.com', family=socket.AF_INET)
        print(result)
    
    asyncio.run(main())
  9. Cleanly close the DNSResolver

    master

    If you are not using the async context manager, you must manually call await resolver.close() to release resources. Calling close() cancels any pending operations in the underlying channel and cleans up event loop readers/writers and timers.

    Once close() has been called, the resolver instance should not be used again.

    import asyncio
    from aiodns import DNSResolver
    
    async def main():
        resolver = DNSResolver()
        try:
            await resolver.query_dns('example.com', 'A')
        finally:
            await resolver.close()
    
    asyncio.run(main())
  10. Initialize the DNSResolver class

    master

    The DNSResolver class is the primary entry point for performing asynchronous DNS queries. You can initialize it with optional nameservers and an explicit asyncio event loop.

    Parameters:

    • nameservers: An optional sequence of strings representing the DNS servers to use.
    • loop: An optional asyncio.AbstractEventLoop. If not provided, it defaults to asyncio.get_event_loop().
    • **kwargs: Additional arguments passed to the underlying pycares.Channel.

    Note for Windows users: aiodns is incompatible with asyncio.ProactorEventLoop on Windows because it requires add_reader or add_writer functionality. If you are using a ProactorEventLoop, you will encounter a RuntimeError.

    import asyncio
    from aiodns import DNSResolver
    
    async def main():
        resolver = DNSResolver(nameservers=['8.8.8.8', '8.8.4.4'])
        # ... use resolver
        await resolver.close()
    
    asyncio.run(main())
  11. Convert pycares 5.x results using convert_result()

    master

    The convert_result function is used to transform pycares 5.x DNSResult objects into a format compatible with pycares 4.x. This is useful for maintaining backward compatibility in codebases that expect the older data structures.

    Signature: convert_result(dns_result: pycares.DNSResult, qtype: int) -> QueryResult

    Behavior:

    • It iterates through the answer section of the DNS result.
    • It filters records to match the requested qtype.
    • For CNAME, SOA, and PTR types, it returns a single record object.
    • For all other types, it returns a list of record objects.
    • If no records of the requested type are found (even if the status is NOERROR), it raises a aiodns.error.DNSError with the ARES_ENODATA error code to match pycares 4.x behavior.
    # Example usage concept
    # result = convert_result(dns_result, pycares.QUERY_TYPE_A)
    # if isinstance(result, list):
    #     for record in result:
    #         print(record.host, record.ttl)
  12. DNS Query Result Types (pycares 4.x compatibility)

    master

    When using aiodns, the library provides compatibility classes to ensure DNS query results match the structure of pycares 4.x. These dataclasses are used to represent different DNS record types.

    Key Result Types:

    • A / AAAA Records: AresQueryAResult and AresQueryAAAAResult both provide host (str) and ttl (int).
    • CNAME Record: AresQueryCNAMEResult provides cname (str) and ttl (int).
    • MX Record: AresQueryMXResult provides host (str), priority (int), and ttl (int).
    • NS Record: AresQueryNSResult provides host (str) and ttl (int).
    • TXT Record: AresQueryTXTResult provides text (str | bytes) and ttl (int). Note that text is returned as a str if it is ASCII, otherwise as bytes.
    • SOA Record: AresQuerySOAResult provides detailed fields including nsname, hostmaster, serial, refresh, retry, expires, minttl, and ttl.
    • SRV Record: AresQuerySRVResult provides host (str), port (int), priority (int), weight (int), and ttl (int).
    • NAPTR Record: AresQueryNAPTRResult provides order, preference, flags, service, regex, replacement, and ttl.
    • CAA Record: AresQueryCAAResult provides critical (int), property (str), value (str), and ttl (int).
    • PTR Record: AresQueryPTRResult provides name (str), ttl (int), and aliases (list[str]).
    • Host Result: AresHostResult provides name (str), aliases (list[str]), and addresses (list[str]).

    Important Behavior: For certain query types (CNAME, SOA, and PTR), the result is returned as a single object rather than a list. For other types, results are returned as a list of the corresponding result objects.