django-udp-discovery modules

Import shortcuts (start_udp_service, stop_udp_service, is_running, and helpers imported from utilities) live in django_udp_discovery/__init__.py; authoritative docstrings appear on the underlying modules below.

Configuration

Configuration module for django-udp-discovery.

This module provides a settings wrapper that allows configuration of the UDP discovery service through Django’s settings system, with sensible defaults as fallbacks. All settings can be overridden in the host project’s settings.py.

The configuration system checks Django settings first, then falls back to default values if a setting is not defined. This allows projects to customize the discovery behavior without requiring all settings to be specified.

Available Settings:

DISCOVERY_PORT (int): UDP port number for discovery service (default: 9999). DISCOVERY_MESSAGE (str): Message clients send to discover servers (default: “DISCOVER_SERVER”). RESPONSE_PREFIX (str): Prefix for server response messages (default: “SERVER_IP:”). DISCOVERY_TIMEOUT (float): Timeout in seconds for client discovery (default: 0.5). DISCOVERY_BUFFER_SIZE (int): UDP buffer size in bytes (default: 1024). ENABLE_LOGGING (bool): Enable/disable logging for discovery service (default: True).

Usage:
Access settings directly as attributes:
>>> from django_udp_discovery.conf import settings
>>> port = settings.DISCOVERY_PORT
>>> message = settings.DISCOVERY_MESSAGE
Or use the safe getter method:
>>> port = settings.get("DISCOVERY_PORT", default=8888)
Override in Django settings.py:

DISCOVERY_PORT = 7777 DISCOVERY_MESSAGE = “FIND_SERVER”

Listener

UDP Discovery Listener Module

This module provides a threaded UDP listener that runs in the background, listening on a configurable port and responding to discovery messages from client nodes. The listener operates in a daemon thread, allowing it to run concurrently with Django without blocking the main application.

The listener implements a simple discovery protocol:
  1. Listens for UDP messages on the configured port

  2. Validates incoming messages against the configured discovery message

  3. Responds with the server’s IP address when a valid discovery request is received

  4. Ignores messages that don’t match the discovery protocol

The service is thread-safe and provides graceful shutdown capabilities. It automatically detects the server’s IP address (using utility.get_server_ip()) and includes error handling for common network issues like port conflicts (using utility.is_port_error()).

Usage:
Manual control of the service:
>>> from django_udp_discovery.listener import start_udp_service, stop_udp_service, is_running
>>> start_udp_service()
True
>>> is_running()
True
>>> stop_udp_service()
True

The service typically starts automatically when Django loads (via apps.py), but can be controlled manually if needed.

Thread Safety:

All public functions are thread-safe and use locks to prevent race conditions when starting or stopping the service concurrently.

Error Handling:
  • Port conflicts are detected and logged with clear error messages

  • Socket errors are caught and logged without crashing the application

  • The service gracefully handles shutdown signals and cleanup

django_udp_discovery.listener.start_udp_service() bool[source]

Start the UDP discovery service in a background daemon thread.

This function initializes and starts the UDP discovery listener in a separate daemon thread. The thread runs in the background and won’t prevent the main application from shutting down.

The function is idempotent - calling it multiple times when the service is already running will return False without starting additional threads.

Before starting, it checks for stale state (dead threads, unclosed sockets) and cleans them up to ensure a fresh start.

The service binds to the port specified in DISCOVERY_PORT setting and begins listening for discovery messages. If the port is already in use, the function will return False and log an error.

Returns:

True if the service started successfully, False if:
  • The service is already running

  • The port is already in use

  • The listener thread failed to start

Return type:

bool

Raises:

OSError – If socket binding fails (e.g., port already in use, insufficient permissions). The error is logged before being raised.

Example

>>> from django_udp_discovery.listener import start_udp_service
>>> if start_udp_service():
...     print("Discovery service started")
... else:
...     print("Service already running or failed to start")
Discovery service started

Note

The service typically starts automatically when Django loads. Manual calls to this function are only needed if you’ve stopped the service or need to restart it.

django_udp_discovery.listener.stop_udp_service() bool[source]

Stop the UDP discovery service gracefully.

This function performs a graceful shutdown of the UDP discovery service. It sets the running flag to False, closes the UDP socket (which interrupts any blocking receive operations), and waits for the listener thread to terminate.

The shutdown process includes:
  1. Setting the _running flag to False

  2. Closing the UDP socket to interrupt blocking operations

  3. Waiting for the listener thread to finish (with timeout)

  4. Cleaning up thread and socket references

The function is idempotent - calling it when the service is not running or when the thread is already dead will clean up any stale state and return True.

Returns:

True if the service was stopped successfully or was already stopped,

False if the listener thread did not terminate within the timeout period

Return type:

bool

Example

>>> from django_udp_discovery.listener import stop_udp_service, is_running
>>> if is_running():
...     stop_udp_service()
...     print("Service stopped")
Service stopped

Note

The shutdown timeout is 2 seconds. If the thread doesn’t terminate within this time, the function returns False but the thread may continue running until it naturally exits.

django_udp_discovery.listener.is_running() bool[source]

Check if the UDP discovery service is currently running.

This function checks the current state of the UDP discovery service by verifying both the running flag and the listener thread’s status. This provides a reliable way to determine if the service is active.

If the thread is dead but the flag is still True (e.g., after Django shutdown), this function will reset the state and return False. This ensures that after Django is stopped with Ctrl+C, is_running() will correctly return False.

Returns:

True if the service is running (flag is set and thread is alive),

False otherwise.

Return type:

bool

Example

>>> from django_udp_discovery.listener import is_running, start_udp_service
>>> is_running()
False
>>> start_udp_service()
True
>>> is_running()
True

Note

This function provides a snapshot of the service state. The state may change immediately after this function returns, so it should not be used for synchronization purposes.

Utilities

Utility functions for django-udp-discovery package.

This module provides common utility functions used across multiple modules in the django-udp-discovery package. These functions are designed to be reusable and independent of specific module implementations.

Functions:

get_server_ip(): Detect the server’s local IP address. format_duration(): Format seconds into human-readable duration strings. is_port_in_use(): Check if a port is currently in use. validate_port(): Validate that a port number is in the valid range.

These utilities help reduce code duplication and provide consistent functionality across the package.

django_udp_discovery.utility.get_server_ip() str[source]

Determine the server’s local IP address.

This function attempts to detect the server’s actual network IP address by creating a temporary UDP socket and connecting to a public DNS server. This method works across different network configurations and avoids returning localhost when the server has a real network interface.

The function uses a connection attempt to 8.8.8.8 (Google’s public DNS) which doesn’t actually send data (UDP), but allows the socket to determine which network interface would be used for external communication.

This is a utility function that can be used by:
  • The UDP listener to determine the server IP for responses

  • Management commands to display server information

  • Test clients to verify IP detection

  • Any other module that needs to know the server’s IP address

Returns:

The server’s IP address as a dotted-quad string (e.g., “192.168.1.100”).

Falls back to “127.0.0.1” if IP detection fails.

Return type:

str

Example

>>> from django_udp_discovery.utility import get_server_ip
>>> ip = get_server_ip()
>>> ip
'192.168.1.100'
>>> # IP is always a valid format
>>> parts = ip.split('.')
>>> len(parts)
4

Note

This function may return “127.0.0.1” if: - The server has no network interface - Network access is restricted - The detection method fails for any reason

The function is safe to call from any thread and does not require network access to complete (it will return localhost if detection fails).

See also

socket.getsockname(): Used internally to get the socket’s local address

django_udp_discovery.utility.format_duration(seconds: int) str[source]

Format a duration in seconds to a human-readable string representation.

Converts a duration specified in seconds into a more readable format that includes hours, minutes, and seconds as appropriate. Only non-zero components are included in the output.

This utility function is useful for:
  • Management commands displaying duration information

  • Logging time-based operations

  • User-facing duration displays

  • Any module that needs to present time durations in a readable format

Parameters:

seconds (int) – Duration in seconds to format. Must be non-negative. Negative values will be treated as 0.

Returns:

Human-readable duration string. Examples:
  • ”1h 30m 15s” for 5415 seconds

  • ”5m” for 300 seconds

  • ”60s” for 60 seconds

  • ”0s” for 0 seconds

Return type:

str

Example

>>> from django_udp_discovery.utility import format_duration
>>> format_duration(3661)
'1h 1m 1s'
>>> format_duration(120)
'2m'
>>> format_duration(45)
'45s'
>>> format_duration(0)
'0s'

Note

The function handles edge cases: - Zero seconds returns “0s” - Only non-zero components are included - At least one component is always displayed (even if 0s)

See also

time.sleep(): Often used with formatted durations for user feedback

django_udp_discovery.utility.is_port_in_use(port: int, host: str = '0.0.0.0') bool[source]

Check if a network port is currently in use.

This function attempts to bind to the specified port to determine if it’s already in use by another process. This is useful for:

  • Validating port availability before starting services

  • Port conflict detection and reporting

  • Testing and debugging port-related issues

Parameters:
  • port (int) – The port number to check. Must be in valid range (1-65535).

  • host (str, optional) – The host address to check. Defaults to “0.0.0.0” (all interfaces). Use “127.0.0.1” to check only localhost.

Returns:

True if the port is in use (cannot bind), False if available.

Return type:

bool

Example

>>> from django_udp_discovery.utility import is_port_in_use
>>> is_port_in_use(9999)
False  # Port is available
>>> # Start a service on port 9999...
>>> is_port_in_use(9999)
True  # Port is now in use

Note

This function creates a temporary socket to test port availability. The socket is immediately closed after the check, so it doesn’t interfere with actual service binding.

On some systems, this check may have race conditions - a port might become available or in use between the check and actual use.

Raises:
  • ValueError – If port is not in valid range (1-65535).

  • OSError – If there’s a system-level error checking the port.

See also

validate_port(): Validate port number range socket.bind(): Used internally to test port availability

django_udp_discovery.utility.validate_port(port: int) bool[source]

Validate that a port number is in the valid range.

This utility function checks if a port number is within the valid TCP/UDP port range (1-65535). This is useful for:

  • Configuration validation

  • Input validation in management commands

  • Settings validation

  • API parameter validation

Parameters:

port (int) – The port number to validate.

Returns:

True if port is valid (1-65535), False otherwise.

Return type:

bool

Example

>>> from django_udp_discovery.utility import validate_port
>>> validate_port(9999)
True
>>> validate_port(0)
False
>>> validate_port(65536)
False
>>> validate_port(-1)
False

Note

Port 0 is technically valid in some contexts (system-assigned port), but this function returns False for it as it’s not useful for explicit port configuration.

See also

is_port_in_use(): Check if a valid port is available

django_udp_discovery.utility.is_port_error(err: Exception) bool[source]

Check if an exception indicates a port conflict or binding error.

This utility function helps identify port-related errors across different operating systems. It checks for common error codes and messages that indicate a port is already in use.

This is useful for:
  • Error handling in listener startup

  • Port conflict detection

  • Providing user-friendly error messages

  • Cross-platform error detection

Parameters:

err (Exception) – The exception to check. Typically an OSError or socket-related exception.

Returns:

True if the error indicates a port conflict, False otherwise.

Return type:

bool

Example

>>> from django_udp_discovery.utility import is_port_error
>>> try:
...     socket.bind(("0.0.0.0", 9999))
... except OSError as e:
...     if is_port_error(e):
...         print("Port conflict detected")

Note

This function checks for: - Error codes: 98 (Linux), 10048 (Windows) - Error messages containing “Address already in use” - Error messages containing “address is already in use”

Different operating systems use different error codes for the same condition, so this function provides a unified way to detect them.

See also

is_port_in_use(): Proactively check port availability

App config

Django App Configuration for UDP Discovery

This module defines the Django application configuration for django-udp-discovery. It automatically starts the UDP discovery service when Django is ready, unless running in a test environment.

The app configuration ensures the UDP listener is initialized and running whenever Django starts, providing seamless service discovery functionality.

class django_udp_discovery.apps.UdpDiscoveryConfig(app_name, app_module)[source]

Bases: AppConfig

Django application configuration for django-udp-discovery.

This configuration class automatically starts the UDP discovery service when Django is ready. The service runs in a background daemon thread and responds to discovery requests from client nodes.

The service is automatically disabled during test runs to prevent port conflicts and ensure clean test execution.

default_auto_field

Default primary key field type for models.

Type:

str

name

Full Python path to the application.

Type:

str

default_auto_field: str = 'django.db.models.BigAutoField'
name: str = 'django_udp_discovery'
ready() None[source]

Initialize the UDP discovery service when Django is ready.

This method is called by Django when the application is fully loaded. It automatically starts the UDP discovery listener unless the application is running in a test environment. Test detection checks for:

  • ‘test’ in sys.argv (Django test command)

  • ‘pytest’ in sys.modules (pytest framework)

  • TESTING setting in Django settings

The service starts in a daemon thread, so it won’t prevent Django from shutting down gracefully. An atexit handler is registered to ensure the service is properly stopped when the process exits.

Note

This method may be called multiple times during Django startup in some configurations. The start_udp_service() function handles duplicate start attempts gracefully.

Raises:

OSError – If the configured UDP port is already in use.

Management command

Django management command to manually start the UDP discovery service.

This module provides a Django management command that allows developers and administrators to start the UDP discovery listener manually for debugging, testing, or verification purposes. The command includes automatic cleanup of existing services, port conflict resolution, and optional duration-based execution with automatic shutdown.

The command extends Django’s BaseCommand class and provides:
  • Automatic stopping of existing services before starting

  • Port conflict detection and resolution

  • Duration-based execution with automatic cleanup

  • Graceful shutdown handling (Ctrl+C support)

  • Comprehensive error handling and user feedback

Usage:

Basic usage (runs indefinitely):

python manage.py start_discovery

With duration (auto-stops after specified seconds):

python manage.py start_discovery --duration 60    # 60 seconds
python manage.py start_discovery --duration 120   # 2 minutes
python manage.py start_discovery --duration 3600   # 1 hour
Command Behavior:

When executed, the command will:

  1. Check if a UDP discovery service is already running

  2. Stop any existing service and free the port if needed

  3. Display current configuration (port, messages, etc.)

  4. Start the UDP discovery service on the configured port

  5. If duration is specified, set up an auto-stop timer

  6. Wait for duration to expire or user interruption (Ctrl+C)

  7. Automatically clean up resources when stopping

Error Handling:

The command handles various error scenarios:

  • Port conflicts: Attempts to free the port and retry

  • Service already running: Automatically stops and restarts

  • Invalid duration: Validates and provides clear error messages

  • Network errors: Provides detailed error information

Examples

Start service for testing (runs for 2 minutes):

python manage.py start_discovery --duration 120

Start service indefinitely (until manually stopped):

python manage.py start_discovery

Stop early: Press Ctrl+C to interrupt and clean up

See also

django_udp_discovery.listener: Core UDP listener implementation django_udp_discovery.conf: Configuration settings django.core.management.base.BaseCommand: Django command base class

class django_udp_discovery.management.commands.start_discovery.Command(*args: Any, **kwargs: Any)[source]

Bases: BaseCommand

Django management command to start the UDP discovery service manually.

This command provides manual control over the UDP discovery listener, allowing it to be started independently of Django’s automatic startup mechanism. It is particularly useful for:

  • Debugging and testing the discovery service

  • Restarting the service without restarting Django

  • Running the service for a limited duration

  • Verifying service configuration and status

The command automatically handles cleanup of existing services, resolves port conflicts, and provides optional duration-based execution with automatic shutdown.

help

Short description of the command displayed in help.

Type:

str

_timer_thread

Thread used for duration-based auto-stop functionality. Set to None when not in use.

Type:

Thread, optional

_should_stop

Flag indicating whether the service should stop. Used for coordination between the main thread and timer thread.

Type:

bool

Example

Basic usage:

python manage.py start_discovery

With duration:

python manage.py start_discovery --duration 120

Note

The command will block execution when a duration is specified, waiting for the timer to expire or user interruption. Without duration, the command returns immediately after starting the service.

See also

handle(): Main command execution method add_arguments(): Command argument definitions

help: str = 'Start the UDP discovery service manually'
add_arguments(parser: Any) None[source]

Add command-line arguments to the argument parser.

This method is called by Django to register command-line arguments that can be passed to the command. Currently defines the –duration argument for time-limited execution.

Parameters:
  • parser – The argument parser instance provided by Django. Arguments are added to this parser. Typically an argparse.ArgumentParser instance.

  • --duration (int, optional) – Duration in seconds to run the service before automatically stopping. Must be a positive integer. If not specified, the service runs indefinitely until manually stopped or Django is shut down.

Example

The duration argument can be used like:

python manage.py start_discovery --duration 120

Note

The duration must be greater than 0. Invalid values will raise a CommandError during command execution.

handle(*args: Any, **options: Any) None[source]

Execute the command to start the UDP discovery service.

This is the main entry point for the command. It orchestrates the entire process of starting the UDP discovery service, including cleanup of existing services, port conflict resolution, and optional duration-based execution.

The method performs the following steps:

  1. Validates the duration argument (if provided)

  2. Stops any existing UDP discovery service

  3. Attempts to free the port if it’s in use

  4. Displays current configuration to the user

  5. Starts the UDP discovery service

  6. Sets up auto-stop timer if duration is specified

  7. Waits for duration to expire or user interruption

  8. Cleans up resources when stopping

Parameters:
  • *args – Variable length argument list (not used by this command).

  • **options

    Dictionary of command-line options. Expected keys: - duration (int, optional): Duration in seconds for time-limited

    execution. If None, service runs indefinitely.

Raises:

CommandError – Raised in the following scenarios: - Duration is provided but is less than or equal to 0 - Service fails to start after cleanup attempts - Port is in use by another application and cannot be freed - Other unexpected errors occur during service startup

Returns:

This method does not return a value. It may block execution if a duration is specified, waiting for the timer to expire.

Return type:

None

Example

The command is typically invoked via Django’s management interface:

python manage.py start_discovery --duration 60

Note

  • If a duration is specified, this method will block until the duration expires or the user interrupts with Ctrl+C

  • The method attempts multiple cleanup and retry strategies if the initial start fails

  • All output is written to stdout using Django’s command output methods for proper formatting

See also

_display_configuration(): Display service configuration _display_status(): Display service status _setup_auto_stop(): Set up duration-based auto-stop _cleanup(): Clean up resources