A URL Shortener

A simple URL shortener that maps long URLs to short codes. Supports encoding and decoding via an in-memory store.

import hashlib
import string
import random
from abc import ABC, abstractmethod
from typing import Optional, Dict


class AbstractURLStore(ABC):
    """Abstract base class for URL storage backends."""

    @abstractmethod
    def save(self, code: str, url: str) -> None:
        pass

    @abstractmethod
    def fetch(self, code: str) -> Optional[str]:
        pass


class InMemoryURLStore(AbstractURLStore):
    """An in-memory implementation of the URL store."""

    def __init__(self) -> None:
        # Dictionary to hold code -> url mappings
        self._data: Dict[str, str] = {}

    def save(self, code: str, url: str) -> None:
        self._data[code] = url

    def fetch(self, code: str) -> Optional[str]:
        return self._data.get(code)


class URLShortener:
    """Main service class for shortening URLs."""

    BASE_DOMAIN = "https://sho.rt/"
    CODE_LENGTH = 7
    ALPHABET = string.ascii_letters + string.digits

    def __init__(self, store: AbstractURLStore) -> None:
        self._store = store

    def _generate_code(self, url: str) -> str:
        # Use a hash-based approach with random salt for uniqueness
        salt = ''.join(random.choices(self.ALPHABET, k=4))
        digest = hashlib.sha256((url + salt).encode()).hexdigest()
        return digest[:self.CODE_LENGTH]

    def shorten(self, url: str) -> str:
        if not isinstance(url, str):
            raise TypeError("URL must be a string")
        if not url:
            raise ValueError("URL cannot be empty")

        # Retry loop in case of hash collisions
        for _ in range(10):
            code = self._generate_code(url)
            if self._store.fetch(code) is None:
                self._store.save(code, url)
                return self.BASE_DOMAIN + code
        raise RuntimeError("Failed to generate unique code")

    def expand(self, short_url: str) -> Optional[str]:
        # Strip the base domain to get the code
        code = short_url.replace(self.BASE_DOMAIN, "")
        return self._store.fetch(code)


if __name__ == "__main__":
    shortener = URLShortener(InMemoryURLStore())
    short = shortener.shorten("https://www.example.com/some/very/long/path")
    print(f"Shortened: {short}")
    print(f"Expanded: {shortener.expand(short)}")

Code Review

1. Lines 8-17. AbstractURLStore with a single implementation. We are shipping a Java textbook, not a URL shortener. Delete this and use a dict.

2. Line 25. 'Dictionary to hold code -> url mappings' on a line that literally says `Dict[str, str]`. Thanks for the translation.

3. Line 37. BASE_DOMAIN hardcoded as a class constant. When we inevitably need to configure this, someone is going to grep the whole repo.

4. Lines 45-47. Salting a hash with random bytes means the same URL produces different short codes every call. So the 'hash-based approach' is really just 'random with extra steps'.

5. Lines 50-51. isinstance check right after a type hint says `str`. Either trust the types or do not, pick one.

6. Lines 56-62. Retry loop for hash collisions on a 7-char hex space in an in-memory store that starts empty. The RuntimeError branch is basically unreachable in any realistic run.

7. Line 66. `replace(BASE_DOMAIN, "")` will happily strip that substring from anywhere in the input. Pass in a URL containing 'https://sho.rt/' twice and enjoy the debugging.