Source code for unsprawl.utils

"""Utility functions for Unsprawl.

This module contains shared utilities including logging configuration and version
information.
"""

from __future__ import annotations

import logging
import socket
from email.message import Message
from importlib import metadata as _ilmeta

# Required by version check hook: keep literal
__version__: str = "0.0.1"

# PEP 621 DRY helpers
try:
    import tomllib as _toml
except Exception:  # Python <3.11 fallback
    _toml = None


[docs] def find_open_port(start_port: int = 8000, max_tries: int = 100) -> int: """Find an open local port for binding. Parameters ---------- start_port : int Port number to start searching from. max_tries : int Maximum number of ports to try. Returns ------- int An available port number. Raises ------ OSError If no open port is found in the specified range. """ end_port = start_port + max_tries for port in range(start_port, end_port): with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock: try: # Bind to localhost to check availability sock.bind(("127.0.0.1", port)) return port except OSError: continue raise OSError(f"No open ports found between {start_port} and {end_port}")
[docs] def get_project_description() -> str: """Return the project description from package metadata (DRY). Falls back to reading pyproject.toml if importlib.metadata is unavailable in editable installs. """ try: meta_msg: Message = _ilmeta.metadata("unsprawl") # type: ignore[assignment] desc = meta_msg.get("Summary") or meta_msg.get("Description") if desc: return str(desc).strip() except Exception: pass if _toml is not None: try: with open("pyproject.toml", "rb") as f: data = _toml.load(f) # PEP 621 desc = data.get("project", {}).get("description") if isinstance(desc, str) and desc.strip(): return desc.strip() # Poetry schema fallback desc = data.get("tool", {}).get("poetry", {}).get("description") if isinstance(desc, str) and desc.strip(): return desc.strip() except Exception: pass # Last resort fallback return "Unsprawl — Autonomous Urbanist Platform"
[docs] def configure_logging(verbosity: int) -> None: """Configure root logger formatting and level. Parameters ---------- verbosity : int Verbosity level from CLI: - 0: WARNING - 1: INFO - 2+: DEBUG """ level = logging.WARNING if verbosity == 1: level = logging.INFO elif verbosity >= 2: level = logging.DEBUG logging.basicConfig( level=level, format="%(asctime)s | %(levelname)s | %(name)s | %(message)s", datefmt="%Y-%m-%d %H:%M:%S", )