"""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"