Inputs have the documented types: names/prefixes are strings; sizes, quota, timestamps, limits, and supplied TTLs are integers. Sizes are bytes; no unit parsing is required. Instances are independent. Every returned dictionary and list must be fresh. There is no overwrite, filesystem access, or historical rollback.

Prototype: implement FileCatalog() with upload(name, size) -> bool and get(name) -> int | None. An empty upload name or negative size raises ValueError. Duplicate names return False and retain their size. Otherwise upload returns True. Zero-byte files are valid. Missing get returns None. The prototype has no quota or expiration.

Final: implement FileStorage(quota):

  • Negative quota raises ValueError; zero is valid. Start at logical time 0 with usage 0.
  • advance_to(timestamp) -> None: require timestamp >= current time (and thus nonnegative), otherwise raise ValueError without changing state or time. Equal timestamps are allowed. Set time and remove every entry with expires_at <= timestamp, releasing its size exactly once. No other method advances time.
  • upload(name, size, ttl=None) -> bool: empty name, negative size, or a supplied TTL below 1 raises ValueError before other checks. Omitted TTL means no expiration. Return False for an existing name or insufficient quota, unchanged. On success store expiry current_time + ttl or None, charge size, and return True.
  • get(name) -> dict | None: return exactly {"name": name, "size": bytes, "expires_at": integer_or_None} for an existing entry, otherwise None.
  • copy(source, destination) -> bool: empty destination raises ValueError before other checks. Missing source, existing destination (including source == destination), or insufficient quota returns False unchanged. Success creates distinct metadata with the same size and absolute expiry as the source, charges the full size again, and returns True.
  • delete(name) -> bool: return True and release the size if present; return False if absent. Repeated deletion must not release space twice.
  • usage() -> int: return the total sizes of all current entries.
  • search(prefix, limit=10) -> list[dict]: match case-sensitive name.startswith(prefix). Empty prefix matches all. Sort by size descending then name ascending, return at most limit snapshots. Negative limit raises ValueError without mutation; zero returns [].

Rejected calls leave all entries and quota accounting unchanged. Run python main.py --stage prototype, then python main.py --stage final (the default). Edit only exercise.py.

Drag or use the arrow keys to resize. Home collapses the question; End collapses the editor. Enter or double-click resets the split.
Loading editor…
No results yet.

Complete the starter code, then run the checks.
Results and errors will appear here.