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.