Playing with the Up API. Learning via Marko's API examples in joel.ipynb.
make basic requests to the up api
-
find out what pydantic is
-
why might you want to use it
-
create types for the up apis
-
make fn that takes an enum and returns the appropriate data type
-
.ipynb
-
.toml
when you are first writing except blocks: how do you know which errors to catch? by trial and error?
-
e.g. when you first used
requests.exceptions.RequestException- although
RequestExceptionsounds rather broad(?)
- although
- same question as above, e.g. how did you know to use these:
except requests.exceptions.Timeout:
print("Request timed out. Please try again later.")
return None
except requests.exceptions.HTTPError as e:
print(f"HTTP Error: {e}")
return None
except requests.exceptions.RequestException as e:
print(f"Error fetching data: {e}")
return None
except json.JSONDecodeError:
print("Error decoding JSON response")
return None-
thoughts:
-
Timeout-> makes sense -
HTTPError-> this makes sense, it comes fromraise_for_status() -
RequestException-> is this a catch-all? is this meaningfully different from just usingException? -
JSONDecodeError-> i can imagine this happening, but wouldn'tRequestExceptionalways catch it first?- ohh no it wouldn't, because maybe the whole request process goes fine, but the json content is somehow bullshit and the decoding process fails
-
APIConnectionError and APIResponseError
-
i don't understand what these are adding to the functionality. who are they for?
-
APIError was made from using
class APIError(Exception); but how do you know something non-API-related isn't gonna go wrong? by sayingexcept APIError as e:we are framing all possible exceptions as API errors. what if something else went wrong? we would catch it but we would be naming at an API error -
but we never print or log
APIConnectionErrorandAPIResponseError; we just raise them; so they will never appear in the terminal/log. so who are they for? for me, for when i am looking at the code itself? -
EDIT: ohhh okay, Claude has informed me how this works. the exceptions you created are subclasses of
Exception, but they behave independently ofException. just because you defined them withclass APIError(Exception), doesn't mean that all exceptions will get funnelled intoAPIError... RATHER, you raiseAPIErrorexactly when you want to, and you catch SPECIFICALLY IT yourself. if some other error gets thrown, we will not catch it-
you're saying when there's a
TimeoutorConnectionErrororRequestException, we are naming it anAPIConnectionError -
and when there's a
HTTPErrororJSONDecodeError, we are calling that anAPIResponseError
-
-
but why use
except APIError as e:in the fn that calls the fn? we never raised it. is it a way to get all remaining un-caught errors? or does every error in the sub-fn bubble up into this except block? i think not the latter- if there's a
Timeout, we raise anAPIConnectionError, which bubbles up a level, but why wouldexcept APIErrorcatch anAPIConnectionError? i wouldn't expect it to. it seems to contradict my epiphany above, where i thought that we only throw and catch specific errors by their specific names, therefore the class we inherit from is kind of irrelevant in that sense.
- if there's a
-
it says
def fetch_api_data(...) -> list[dict[str, Any]]:but if we reachraise(), doesn't that fn return nothing?
Joel's conclusion: Marko is obsessed with error handling. but it's just a facade. he never actually writes code that DOES anything :p