Metadata-Version: 2.4
Name: rdnsresolver
Version: 0.1.0
Summary: rdnsresolver - Retrying DNS Resolver Package.
Author-email: Alex Semenyaka <alex.semenyaka@gmail.com>
License: MIT
Keywords: dns,resolver,retry,dnspython,async
Classifier: License :: OSI Approved :: MIT License
Classifier: Operating System :: OS Independent
Classifier: Intended Audience :: Developers
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Programming Language :: Python :: 3.14
Classifier: Topic :: Software Development :: Libraries :: Python Modules
Requires-Python: >=3.12
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: dnspython>=2.3.0
Requires-Dist: tenacity>=8.2.0
Dynamic: license-file

# rdnsresolver Package Documentation

[![PyPI version](https://img.shields.io/pypi/v/rdnsresolver.svg)](https://pypi.org/project/rdnsresolver/)
[![PyPI - Python Version](https://img.shields.io/pypi/pyversions/rdnsresolver.svg)](https://pypi.org/project/rdnsresolver/)
[![PyPI - License](https://img.shields.io/pypi/l/rdnsresolver.svg)](https://pypi.org/project/rdnsresolver/)
[![Coverage Status](https://coveralls.io/repos/github/alexsemenyaka/rdnsresolver/badge.svg?branch=main)](https://coveralls.io/github/alexsemenyaka/rdnsresolver?branch=main)
[![CI/CD Status](https://github.com/alexsemenyaka/rdnsresolver/actions/workflows/ci.yml/badge.svg)](https://github.com/alexsemenyaka/rdnsresolver/actions/workflows/ci.yml)


## Overview
The `rdnsresolver` package provides synchronous and asynchronous DNS resolution wrappers based on `dnspython`. It implements automatic exponential backoff retry mechanisms utilizing `tenacity` for transient network failures, timeouts, and nameserver unreachability.

## Class: RetryingResolver

### `__init__`

`__init__(resolver: Optional[dns.resolver.Resolver] = None, **kwargs: Any) -> None`

This method initializes a synchronous DNS resolver instance with integrated retry logic.

It accepts an optional custom `resolver` instance, `max_attempts` (integer, default 5), `backoff_factor` (float, default 1.5), and `initial_delay` (float, default 1.0).

It instantiates and returns the configured `RetryingResolver` object.

It raises a `TypeError` if unsupported keyword arguments are supplied during initialization.

If no custom resolver is provided, it falls back to the global `dns.resolver` module functions, which share the system's underlying default DNS configuration.

### `resolve`

`resolve(qname: Union[str, dns.name.Name], rdtype: Union[int, str] = dns.rdatatype.A, **kwargs: Any) -> dns.resolver.Answer`

This method executes a synchronous DNS query applying an exponential backoff strategy for transient network failures.

It requires the target `qname` (string or `dns.name.Name`), an optional `rdtype` (integer or string, default `A`), and accepts optional `kwargs` representing standard `dnspython` query parameters or temporary retry configuration overrides.

It returns a `dns.resolver.Answer` object containing the query results upon successful resolution.

It propagates permanent `dnspython` errors such as `NXDOMAIN` or `NoAnswer` immediately, and raises `dns.exception.Timeout` or `dns.resolver.NoNameservers` only after all retry attempts are exhausted.

Providing override retry parameters via `kwargs` modifies the behavior strictly for that specific query execution, leaving the instance's base configuration unmodified for subsequent calls.

### `resolve_ptr`

`resolve_ptr(ip_address: str, **kwargs: Any) -> dns.resolver.Answer`

This method constructs a reverse DNS name from an IP address and executes a synchronous PTR record query with retry logic.

It requires the target `ip_address` (string) and accepts optional `kwargs` for standard query parameters or temporary retry overrides.

It returns a `dns.resolver.Answer` object containing the reverse resolution records.

It propagates permanent errors like `NXDOMAIN` immediately, while raising timeout or nameserver unavailability exceptions only after the retry limit is reached.

Invalid IP address strings passed to this method will immediately trigger a `dns.exception.SyntaxError` from the underlying reverse name constructor before any network requests are initiated.

## Class: AsyncRetryingResolver

### `__init__`

`__init__(resolver: Optional[dns.asyncresolver.Resolver] = None, **kwargs: Any) -> None`

This method initializes an asynchronous DNS resolver instance with integrated retry logic for non-blocking operations.

It accepts an optional custom `dns.asyncresolver.Resolver` instance, `max_attempts` (integer, default 5), `backoff_factor` (float, default 1.5), and `initial_delay` (float, default 1.0).

It instantiates and returns the configured `AsyncRetryingResolver` object.

It raises a `TypeError` if unsupported keyword arguments are supplied to the underlying constructor.

Instantiating this class outside of an active asynchronous event loop is permitted, provided that the underlying custom resolver, if supplied, does not tightly bind to an active loop during its own initialization.

### `resolve`

`async resolve(qname: Union[str, dns.name.Name], rdtype: Union[int, str] = dns.rdatatype.A, **kwargs: Any) -> dns.resolver.Answer`

This method executes an asynchronous DNS query using the specified name and record type, applying the configured exponential backoff strategy for transient network exceptions.

It requires the target `qname` (string or `dns.name.Name`), an optional `rdtype` (integer or string, default `A`), and accepts optional `kwargs` representing standard asynchronous query parameters or temporary retry overrides.

It asynchronously yields a `dns.resolver.Answer` object representing the final server reply upon successful execution.

It propagates permanent `dnspython` errors like `NXDOMAIN` or `NoAnswer` instantly without retrying, and raises transient errors like `dns.exception.Timeout` or `dns.resolver.NoNameservers` only if the maximum retry limit is breached.

Executing this method requires an active event loop; attempting to run it synchronously or across mismatched event loops will result in an `asyncio` RuntimeError.

### `resolve_ptr`

`async resolve_ptr(ip_address: str, **kwargs: Any) -> dns.resolver.Answer`

This method constructs a reverse DNS name from the provided IP address and executes an asynchronous PTR record query with retry logic.

It requires the target `ip_address` (string) and accepts optional `kwargs` for standard query parameters or temporary retry configuration overrides.

It asynchronously yields a `dns.resolver.Answer` object containing the requested reverse pointer records.

It propagates permanent errors instantly without retrying, raising timeout or nameserver unavailability exceptions solely after the maximum retry attempts are depleted.

Processing large batches of IP addresses concurrently with this method should be rate-limited by the calling application to prevent localized socket exhaustion or upstream DNS rate-limiting responses.

## Global Functions

### `resolve`

`resolve(qname: Union[str, dns.name.Name], rdtype: Union[int, str] = dns.rdatatype.A, **kwargs: Any) -> dns.resolver.Answer`

This function executes a synchronous DNS request utilizing a globally maintained `RetryingResolver` singleton instance.

It requires the target `qname` (string or `dns.name.Name`), an optional `rdtype` (integer or string, default `A`), and accepts optional `kwargs` for request parameters or retry configuration overrides.

It returns the `dns.resolver.Answer` object generated by the global singleton session.

It propagates permanent `dnspython` errors instantly and raises transient errors like `dns.exception.Timeout` or `dns.resolver.NoNameservers` only after all retry attempts are exhausted.

The global singleton defaults to the system's DNS configuration; if the environment dictates custom nameservers, developers must either modify the global `dns.resolver.default_resolver` or instantiate a dedicated `RetryingResolver` instead of using this function.

### `aresolve`

`async aresolve(qname: Union[str, dns.name.Name], rdtype: Union[int, str] = dns.rdatatype.A, **kwargs: Any) -> dns.resolver.Answer`

This function executes an asynchronous DNS request utilizing a globally maintained `AsyncRetryingResolver` singleton instance.

It requires the target `qname` (string or `dns.name.Name`), an optional `rdtype` (integer or string, default `A`), and accepts optional `kwargs` for request parameters or retry configuration overrides.

It asynchronously yields the `dns.resolver.Answer` object generated by the global asynchronous singleton session.

It propagates permanent errors immediately and raises transient errors only after exhausting the configured retry limit.

The global asynchronous instance delegates to the `dns.asyncresolver` module; invoking this function from a distinctly different thread or a newly created event loop will trigger cross-loop execution exceptions if the underlying UDP sockets were bound elsewhere.

### `resolve_ptr`

`resolve_ptr(ip_address: str, **kwargs: Any) -> dns.resolver.Answer`

This function performs a synchronous reverse DNS lookup utilizing the global `RetryingResolver` singleton instance.

It requires the target `ip_address` (string) and accepts optional `kwargs` for request parameters or retry configuration overrides.

It returns the `dns.resolver.Answer` object generated by the global singleton session.

It propagates permanent errors immediately and raises transient errors only after exhausting the configured retry limit.

Supplying non-standard IP representations may trigger parsing errors in the `dns.reversename` module before the global resolver instance is invoked.

### `aresolve_ptr`

`async aresolve_ptr(ip_address: str, **kwargs: Any) -> dns.resolver.Answer`

This function performs an asynchronous reverse DNS lookup utilizing the global `AsyncRetryingResolver` singleton instance.

It requires the target `ip_address` (string) and accepts optional `kwargs` for request parameters or retry configuration overrides.

It asynchronously yields the `dns.resolver.Answer` object generated by the global asynchronous singleton session.

It propagates permanent errors immediately and raises transient errors only after exhausting the configured retry limit.

Similar to the synchronous variant, invalid input strings will bypass the retry mechanism entirely by failing during the local address parsing phase.

## Migration Guide

### Import Replacement

Projects utilizing direct `dnspython` implementations typically import `dns.resolver` and `dns.asyncresolver`. Migrating to this package requires importing the equivalent global functions from `rdnsresolver`.

The application must replace operational calls to `dns.resolver` and `dns.asyncresolver` by adding `from rdnsresolver import resolve, aresolve, resolve_ptr, aresolve_ptr`. 

Imports for supporting modules such as `dns.name`, `dns.rdatatype`, and `dns.exception` remain necessary within the codebase for constructing strict query types and handling specific error structures.

### Synchronous Query Migration

Existing synchronous codebase patterns execute queries using direct invocations like `dns.resolver.resolve(qname, rdatatype)`. 

The migration involves substituting `dns.resolver.resolve` with the `resolve` function. 

For example, the invocation `dns.resolver.resolve(current, dns.rdatatype.DS)` becomes `resolve(current, dns.rdatatype.DS)`. 

The return signatures remain identical; the application will continue to receive a `dns.resolver.Answer` object, ensuring that downstream data processing logic requires zero modifications.

### Asynchronous Query Migration

Asynchronous implementations currently rely on statements like `await dns.asyncresolver.resolve(qname, rdatatype)`. 

The migration requires replacing `dns.asyncresolver.resolve` with the `aresolve` function.

For example, the statement `await dns.asyncresolver.resolve(current, dns.rdatatype.SOA)` translates directly to `await aresolve(current, dns.rdatatype.SOA)`.

The internal event loop interactions remain unchanged, but the overall execution time of the awaited function will increase dynamically if transient failures trigger the exponential backoff mechanism.

### Reverse DNS (PTR) Simplification

Standard `dnspython` reverse lookups require a two-step process: converting the IP address string using `dns.reversename.from_address(ip)` and subsequently querying the resulting `dns.name.Name` object for a PTR record.

The `rdnsresolver` package provides dedicated helper functions to abstract this conversion phase and integrate the retry logic simultaneously.

A legacy code block containing `rev_name = dns.reversename.from_address(ip)` followed by `answers = dns.resolver.resolve(rev_name, 'PTR')` is replaced entirely by the single call `answers = resolve_ptr(ip)`.

Similarly, the asynchronous equivalent `answers = await dns.asyncresolver.resolve(rev_name, 'PTR')` is consolidated into `answers = await aresolve_ptr(ip)`.

### Exception Handling Adjustments

Applications migrating to this package must account for changes in exception timing resulting from the introduced retry behavior.

Existing `try...except` blocks configured to catch `dns.exception.Timeout` or `dns.resolver.NoNameservers` will continue to function correctly.

However, these exceptions will no longer be raised immediately upon the first network failure; they will surface only after the maximum configured retry limit is exceeded.

Developers must evaluate overarching application timeouts to ensure that the cumulative delay introduced by the backoff multiplier does not violate strict internal performance boundaries or trigger secondary timeouts in higher-level services.
