Random Port Generator — Python source
Generate one or many random TCP/UDP port numbers across registered, ephemeral, or the full range - optionally unique. Runs entirely in your browser with crypto-grade randomness.
This is the Python implementation — the same logic the interactive tool runs, in a shareable, citable form.
"""random-port-generator — random/dynamic port picker over well-known ranges.
Language: Python (3.9+, standard library only)
Source: CosmoDev polyglot showcase port of the Random Port Generator tool,
ported from src/lib/random-port.ts (the canonical TypeScript
implementation) and kept in lock-step with cli/random-port-generator
(the Go twin). Pure + deterministic via an injectable RNG; invalid
custom ranges raise ``ValueError`` (mirroring the TS throw).
License: display source — part of CosmoDev's polyglot tool pages.
Design goals:
- Pure + deterministic; an injectable RNG makes generation reproducible.
- Functionally equivalent to the TS reference and the Go twin: same named
ranges, same validation, same Fisher–Yates partial-shuffle unique pick.
- Self-contained: stdlib only. The default RNG is ``secrets.random`` (a
CSPRNG, matching the TS ``crypto.getRandomValues`` default).
The injectable ``rng`` is the key that makes a *random* tool deterministic and
therefore testable: every showcase assertion under ``__main__`` uses a fixed
lambda and reproduces a vector from src/lib/random-port.test.ts exactly.
"""
from __future__ import annotations
import secrets
from dataclasses import dataclass
from typing import Callable, List, Literal, Optional, Tuple
__all__ = ["PortRange", "PortOptions", "resolve_range", "random_port", "random_ports"]
PortRange = Literal["any", "registered", "ephemeral", "custom"]
# Named ranges mirror RANGES in random-port.ts (and the Go bounds() switch).
_RANGES: dict[str, tuple[int, int]] = {
"any": (1, 65535),
"registered": (1024, 49151),
"ephemeral": (49152, 65535),
}
def _default_rng() -> float:
"""CSPRNG float in [0, 1) — the Python twin of TS's crypto-backed default."""
return secrets.random()
@dataclass
class PortOptions:
"""Options mirror the TS ``PortOptions`` interface.
Every field defaults to the TS default, so callers can construct the
object incrementally with keyword args (a dataclass gives us that for free
and keeps the option set self-documenting).
"""
range: PortRange = "registered"
min: Optional[int] = None # custom-range lower bound (TS: ``min ?? 1``)
max: Optional[int] = None # custom-range upper bound (TS: ``max ?? 65535``)
count: int = 1 # how many ports random_ports returns
unique: bool = False # dedupe via Fisher–Yates partial shuffle
rng: Optional[Callable[[], float]] = None # injectable random in [0, 1)
def resolve_range(opts: PortOptions) -> Tuple[int, int]:
"""Resolve an options object to its ``(lo, hi)`` bounds."""
if opts.range == "custom":
return (opts.min if opts.min is not None else 1,
opts.max if opts.max is not None else 65535)
return _RANGES[opts.range]
def _assert_range(lo: int, hi: int) -> None:
"""Mirror TS ``assertRange``: raise ValueError on out-of-bounds/inverted ranges."""
if (not isinstance(lo, int) or not isinstance(hi, int)
or lo < 0 or lo > 65535 or hi > 65535 or lo > hi):
raise ValueError(f"Invalid port range {lo}-{hi}")
def random_port(opts: Optional[PortOptions] = None) -> int:
"""Return a single random port within the resolved range."""
if opts is None:
opts = PortOptions()
rng = opts.rng or _default_rng
lo, hi = resolve_range(opts)
_assert_range(lo, hi)
return lo + int(rng() * (hi - lo + 1))
def random_ports(opts: Optional[PortOptions] = None) -> List[int]:
"""Return ``count`` ports; when ``unique``, a Fisher–Yates partial shuffle
over the range yields distinct values (capped at range capacity)."""
if opts is None:
opts = PortOptions()
count = max(1, opts.count)
lo, hi = resolve_range(opts)
_assert_range(lo, hi)
if not opts.unique:
return [random_port(opts) for _ in range(count)]
# Fisher–Yates partial shuffle over the range to pick `n` unique ports.
capacity = hi - lo + 1
n = min(count, capacity)
rng = opts.rng or _default_rng
pool = list(range(lo, hi + 1))
for i in range(n):
j = i + int(rng() * (capacity - i))
pool[i], pool[j] = pool[j], pool[i]
return pool[:n]
if __name__ == "__main__":
# Showcase vectors mirror the deterministic cases in src/lib/random-port.test.ts.
assert random_port(PortOptions(rng=lambda: 0.0)) == 1024
assert random_port(PortOptions(range="any", rng=lambda: 0.0)) == 1
assert random_port(PortOptions(range="ephemeral", rng=lambda: 0.0)) == 49152
assert resolve_range(PortOptions()) == (1024, 49151)
assert random_port(PortOptions(range="custom", min=8000, max=8000, rng=lambda: 0.9)) == 8000
assert resolve_range(PortOptions(range="custom")) == (1, 65535)
assert random_ports(PortOptions(count=5, rng=lambda: 0.0)) == [1024, 1024, 1024, 1024, 1024]
uniq = random_ports(PortOptions(count=5, unique=True, range="custom",
min=1, max=3, rng=lambda: 0.5))
assert len(uniq) == 3 and sorted(uniq) == [1, 2, 3]
try:
random_port(PortOptions(range="custom", min=100, max=50))
raise AssertionError("expected ValueError")
except ValueError:
pass
print("ok")
Also available in 13 other languages
Every CosmoDev tool ships its pure logic in TypeScript (web) and Go (CLI), with authored implementations in a dozen-plus languages — the same contract, ported. Compare all languages side by side →