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 raiseValueErrorwithout changing state or time. Equal timestamps are allowed. Set time and remove every entry withexpires_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 raisesValueErrorbefore other checks. Omitted TTL means no expiration. ReturnFalsefor an existing name or insufficient quota, unchanged. On success store expirycurrent_time + ttlorNone, charge size, and returnTrue.get(name) -> dict | None: return exactly{"name": name, "size": bytes, "expires_at": integer_or_None}for an existing entry, otherwiseNone.copy(source, destination) -> bool: empty destination raisesValueErrorbefore other checks. Missing source, existing destination (including source == destination), or insufficient quota returnsFalseunchanged. Success creates distinct metadata with the same size and absolute expiry as the source, charges the full size again, and returnsTrue.delete(name) -> bool: returnTrueand release the size if present; returnFalseif 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-sensitivename.startswith(prefix). Empty prefix matches all. Sort by size descending then name ascending, return at mostlimitsnapshots. Negative limit raisesValueErrorwithout 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.
Complete the starter code, then run the checks.
Results and errors will appear here.