I don't feel that a ORM is better than this personally. I know exactly what this is doing at all times. No magic, no guess work about the philosophy of the software. This is probably faster as well.
I'm going to give you an equivalent example using JAX-RS and Hibernate:
@Path("/things/{thingId}/tags")
public class ThingTagsResource {
@PUT
@Transactional
public Thing setTags(final @PathParm("thingId") long thingId, final SortedSet<String> tags) {
final Thing thing = dao().load(Thing.class, thingId);
thing.setTags(tags);
return thing;
}
}
I think this code hews much closer to the programmer's intention, providing essential input validation with minimal boilerplate. It's also comparatively easy to test.
Sorry, maybe I wasn't being clear. I don't just validate that the request is JSON, I validate that the fields in the JSON are valid fields to send over the wire. I do this by automatically hooking my ORM models into my request validation. If a request doesn't specify a column that is NOT-NULL, for example, it will automatically send an error response telling the client that it needs to specify that column in the request JSON.
Python doesn't have type safety to begin so those sort of checks have less utility to me and since json.loads returns a dictionary and python objects are effectively dictionaries you are pretty much done.
Type annotations were recently introduced as a standard library feature as well though they aren't enforced and require a third party tool like you are describing. I've found that 90% of the time you don't actually care what type something actually is and instead you care what it needs to be.
Python doesn't force type safety but there benefits of being type safe to ensure things stay valid/bugs are caught/defense in depth for malicious requests.
For example if I'm going to use a value in a query, because I'm using parameterized queries the type conversion to string happens implicitly so type doesn't actually matter. If I get 2 or '2' it all ends up as '2' and the database infers type by the column type.
If I need something to be a integer and I don't trust the upstream system then you have to:
int(*number*)
At the end of the day if my JSON is going back to JavaScript I can't trust types either so I have to take the same precautions.
I gotcha. I work on a lot of user-facing stuff, so if the type is somehow wrong, I generally prefer to let the user know, which is a lot easier with models.
That is sensible and is generally the approach I take in user facing code. When I have my way I have validators on all input fields in the UI to warn users of invalid input. I like them to be very specific, checking to see if a phone number is formatted in such a way that it is usable by Twillo for example.
On the other hand services that aren't actively utilizing data I write to be fairly agnostic about that data. "be conservative in what you do, be liberal in what you accept from others." is sort of how I aim.