Every Flask route that accepts POST data needs the same boilerplate: check the key exists, check the type is right, return a sensible error if not. Write that check once per route and you’ve got 10 copies of the same logic scattered across server.py. Change one rule and you need to find every copy.
validate_input_data_generic() from the CPAP monitor’s server.py solves this by validating any dictionary against a schema defined as two parallel lists — expected keys and expected types. One function handles every route.
What Makes This Non-Trivial
The obvious solution (check type(value) is int) breaks on data coming from JSON, where numbers often arrive as strings. CPAP pressure readings come in as "15" not 15. The function handles this, along with empty strings as “optional data” signals — a pattern that lets callers do partial updates without resending every field.
The Full Function, Step by Step
Step 1: Verify the input is actually a dictionary
def validate_input_data_generic(in_data, expected_keys, expected_types):
if type(in_data) is not dict:
return "Input is not a dictionary"
Don’t skip this. If a client sends a raw string instead of a JSON object, calling in_data["key"] crashes with TypeError: string indices must be integers. Check the container type first.
Step 2: Iterate key-type pairs with zip()
for key, value_type in zip(expected_keys, expected_types):
if key not in in_data:
return "Key {} is missing from input".format(key)
zip() pairs each key with its expected type without creating intermediate lists — it returns an iterator, so memory usage is O(1) regardless of schema size. The early return on a missing key means we stop immediately rather than accumulating all errors.
Step 3: Allow empty strings through
if in_data[key] == "":
continue
This is the partial update pattern. A request like {"patient_name": "John Doe", "CPAP_pressure": ""} is treated as “update only the name.” Empty string means “no data provided for this field,” not “invalid data.” This matters for routes where you want to update one field without touching others.
Step 4: Type validation with numeric string handling
if type(in_data[key]) is not value_type:
if value_type == float:
try:
float(in_data[key])
continue
except ValueError:
return "Key {} is not an int or numeric string".format(key)
if value_type == int:
if str(in_data[key]).isnumeric() is False:
return "Key {} is not an int or numeric string".format(key)
else:
return "Key {} has the incorrect value type".format(key)
return True
If the type doesn’t match exactly, we check whether it’s a numeric string before rejecting. float("15.5") succeeds; "15.5".isnumeric() fails (the decimal point breaks it). This is why float and int have separate code paths.
The function uses type(x) is not value_type rather than isinstance(x, value_type) deliberately. isinstance(True, int) returns True because bool is a subclass of int. For API validation you want strict matching — {"age": True} should not pass as a valid integer.
The isnumeric() limitation: it only returns True for positive integers with no decimal point. "-42".isnumeric() is False. This is fine for CPAP pressure values, which are always positive integers between 4 and 25 cmH2O. If your domain has negative or decimal integer inputs, use a try/except int(x) instead.
Using It in a Route
expected_keys = ["patient_name", "CPAP_pressure"]
expected_types = [str, int]
result = validate_input_data_generic(request.get_json(), expected_keys, expected_types)
if result is not True:
return result, 400
The function returns either the string True or an error message string. Callers check result is not True rather than truthiness, since an empty string would be falsy but True would pass both checks.
One Edge Case Worth Knowing
None doesn’t pass the empty string check:
{"patient_name": None} # Fails with "incorrect value type"
{"patient_name": ""} # Passes (treated as "no data")
If your API needs to treat None the same as an empty string, add or in_data[key] is None to the continue condition. The current code is intentional for the CPAP monitor’s needs — a missing pressure field is different from an explicitly null one.