"""
UDP discovery client for django-udp-discovery servers.
Public API: discover(), discover_one(), ClientConfig, load_config(), DiscoveryResult.
Use discover() or discover_one() to find servers on the local network; configure
via ClientConfig or load_config() (env: DISCOVERY_CLIENT_*).
"""
__version__ = "1.0.0"
import logging
from discovery_client.config import ClientConfig, load_config
from discovery_client.results import DiscoveryResult
from discovery_client.network.socket import (
discover_servers_single_broadcast,
discover_servers_multi_interface,
)
from typing import List, Optional
# Module-level logger
logger = logging.getLogger("django_udp_discovery_client")
__all__ = [
'ClientConfig',
'load_config',
'DiscoveryResult',
'discover',
'discover_one',
]
[docs]
def discover(config: Optional[ClientConfig] = None) -> List[DiscoveryResult]:
"""
Discover django-udp-discovery servers on the local network.
Sends UDP discovery requests (DISCOVER_SERVER) to the network and collects
responses from servers that respond with the SERVER_IP: prefix. Uses
multi-interface broadcast; results are deduplicated by (ip, port).
Args:
config: Optional ClientConfig. If None, uses load_config() (defaults + env).
Returns:
List[DiscoveryResult]: One entry per discovered server (ip, port, raw_response, extra).
Empty list if no servers are found, timeout, or on network/import errors (logged).
Example:
>>> from discovery_client import discover, load_config
>>> servers = discover()
>>> for s in servers:
... print(s.ip, s.port) # DiscoveryResult attributes
>>> servers = discover(config=load_config(timeout=10.0))
"""
if config is None:
config = load_config()
# Perform discovery using multi-interface broadcast
try:
logger.info("Starting server discovery")
return discover_servers_multi_interface(config)
except OSError as e:
# Socket errors - return empty list
logger.error(
f"Network error during discovery: {e}. "
"Discovery failed due to socket operation error.",
exc_info=True
)
return []
except ImportError as e:
# Missing network libraries - return empty list
logger.error(
f"Missing network interface libraries: {e}. "
"Install with: pip install django-udp-discovery-client[network]",
exc_info=True
)
return []
except Exception as e:
# Unexpected errors - log and return empty list
logger.error(
f"Unexpected error during discovery: {e}",
exc_info=True
)
return []
[docs]
def discover_one(config: Optional[ClientConfig] = None) -> Optional[DiscoveryResult]:
"""
Discover a single django-udp-discovery server (first respondent).
Convenience wrapper around discover(): returns the first DiscoveryResult
or None if no servers are found. Same protocol and config as discover().
Args:
config: Optional ClientConfig. If None, uses load_config() (defaults + env).
Returns:
Optional[DiscoveryResult]: First discovered server (ip, port, raw_response, extra),
or None if none found or on error.
Example:
>>> from discovery_client import discover_one
>>> server = discover_one()
>>> if server:
... print(server.ip, server.port)
"""
if config is None:
config = load_config()
# Call discover() and return first result
logger.debug("discover_one() called - will return first server or None")
servers = discover(config=config)
if servers:
logger.debug(f"discover_one() found server: {servers[0].ip}:{servers[0].port}")
return servers[0]
logger.debug("discover_one() found no servers")
return None