RESTful and HTTP APIs in Pyramid Transcripts
Chapter: Content negotiation
Lecture: Adding a negotiating renderer
0:01 Now let's pick up where we left off with our app previously. We have our csv, our image and json renderers,
0:08 we have our abstract base type that enforces some of the common functionality,
0:12 and notice, this green one, I've now added a new type a negotiating renderer factory, and this is just a totally empty implementation here,
0:20 we've got our ability to have adapters, we've got our call our renderer, our can serialize and add adapter,
0:26 this is just basically what we need to be a renderer abstract base type. So what we're going to do is we're going to apply a design pattern here
0:33 we're going to apply something called the composite design pattern, so let me just give you a quick background if that's new to you,
0:40 so the idea is we may work with fundamental types that kind of do individual things, but if we can somehow put the same interface on a group of things
0:47 and then we could treat that group as a building block in our system as well, so maybe you think of like a gui type of thing, right,
0:56 maybe you've got buttons, you've got windows, you've got text boxes, things like that, and those all have a common api let's say;
1:05 well, what if you want to have a frame that can contain many of them; well, if you could add on to that frame object to have basically
1:12 all the behaviors, buttons and whatnot, but also has a collection of children and somehow could manage those,
1:17 well then you can treat a frame like you could a button and move it around and position it and render it, things like that.
1:23 So we are going to apply the same basic idea here for renderers. So you can see, the negotiating renderer factory implements the renderer abstract base
1:30 but we're going to actually have a few more pieces of functionality, so let's go down here and add a renderer,
1:36 so we are going to add this function where we can pass in a renderer and it will be used when a certain content type is asked for,
1:43 so we'll ask for the content type, so we could say something like for application/ json use this renderer,
1:49 for image/ png use this other renderer, and so on. Now we'll just call this renderer, but we can enforce that this is a render abstract base class.
1:57 Now of course, this does nothing right, this is a suggestion to the editor by enforce I mean if not is instance, then we'll raise an exception.
2:05 Renderer must be abstract based, must be a based renderer or something like that Ok, so from then on, we pretty much are guaranteed like hey
2:15 we're actually working with a known type, that we can call on, we can serialize values, we can add adapters, all those kinds of things.
2:24 The next, let's do a little bit of normalization on our content type here, so we'll say, so we're going to lower case it and strip it
2:33 we're going to use this for a key, and so if somebody says capital json versus the system passing the lower case json or something like that
2:40 we don't really want to have that be a variation right, it doesn't really matter, it's not case sensitive as far as we're concerned.
2:45 And now we need somewhere to put these renderers and so well put them like so, in just another dictionary
2:50 and how are we going to find them, we're going to find them by content type, so just like we looked up adapters by type, and then we got a function,
2:57 now we're going to look up renderers by the type that they can transform to, like text/ csv, and then we'll have the type which is one of these,
3:07 this would be like a json renderer or a csv renderer, something like that.
3:11 You're going to see that this is all well and good, but depending on the situation the client may not even pass a content type,
3:16 and so we might want to explicitly say well if the client just says I'll take whatever, what renderer is supposed to handle that situation?
3:24 So let's add something like this, like default renderer, or let's say accept all, because that's basically star.star
3:33 alright, so I have a renderer that's going to come in here and all we're going to do is say self.add renderer,
3:39 and the content type is going to be */* and this is going to be the renderer.
3:44 Okay, this is looking pretty good, now I think we have this kind of generalized thing let's go ahead and integrate it into our system down here,
3:54 so we've got our json renderer, our csv renderer, an image renderer, and let's go down here and have a negotiate renderer,
4:01 it's going to be one of these types here, and now we need to basically say hey all of these renderers are available for you
4:08 in what particular content type are those is going to be; now, you might say well, we still need to have these lines
4:15 we still want to register them independently, it's up to you, but if you want to be able to say this one is not negotiable, this one is json
4:21 then you want to leave line 56 for example. All right, so let's go over here to our negotiate renderer and say add renderer,
4:29 and let's say application/ json is going to be the json renderer, and also, let's make json the default, accept all renderer,
4:39 and then for text/ csv, grab the csv renderer, and for image/ png, we'll have the image renderer.
4:50 Now, this is configuring this object, but still, the last thing we have to do of course is to register with pyramid.
4:57 So it's going to go like that, and let's pick a name that we can put here
5:01 we'll say negotiate, if we say negotiate, that means we're going to pick this renderer,
5:06 which actually has this functionality built into it. Well, it almost does. Okay, let's go to our API and focus on let's do this all cars thing here,
5:17 and here we have a bunch of different versions let’ s get rid of this, let's go over here and say okay this one if you do a get
5:26 we're going to let you negotiate what it is, and we're just going to return the cars.
5:29 Now, remember, we've already taught all three of these to deal with cars and so we don't have to worry about anything here,
5:35 this should just delegate to those other pieces and you'll get cars so if you ask for application/ json, it'll find its way over to here
5:43 which knows how to deal with the car; if you ask for text/ csv, it will find its way over here, which knows how to deal with the car.
5:49 So, as far as what we do in our code it's totally simple, it just return cars. Now, this is not quite perfect yet,
5:55 because we haven't finished implementing the negotiate renderer, but let's go and just make a request and see what we get.
6:01 All right, do ascend and you can see we get nothing, although it does come back ok, so that's kind of cool, sort of,
6:06 and why did it come back ok, well, because we just return none. So now, the question is what are we going to do when we render this,
6:13 well first thing, let's go ahead and get a hold of the request, the next, we need to figure out what are they asking for, right,
6:21 so what they're asking for is sent over in the headers, here. So in this case, I said accept is application json
6:29 so let's go and actually get the headers and pull those out so we'll say accept headers and this should be accept, like this okay.
6:37 You can print out really quick, found accept header, and who knows what format that is, so let's say accept headers like this, so if we run this again,
6:48 we go over here and click this, what are we going to get, you can see we found accept headers this,
6:52 so let's take a first pass and say all right, we're just going to like use this as the key
6:56 it turns out that that's insufficient, but let's go ahead and just say we are going to use that as the key,
7:02 so let's go ahead and apply this lower case normalization to it and we'll say something like this, we'll say if not accept header,
7:08 so maybe if they don't pass anything here, what are we going to do we're going to return, we want to get the renderer, so self.renderer, that has */*
7:17 now this assumed somebody's called that function so you want to be a little careful but it's okay,
7:22 so we're going to have it render with a value and system who knows whatever that means, okay.
7:28 Next, we need to find the renderer, so renderer, let's do it like this we had to come over here and we'll say accept,
7:34 self.renderer.get, and we'll just pass it the accept header. This is not good enough, but for the simple request it's going to work,
7:44 so we'll say if not renderer we'll raise an exception like could not alright, so if we don't find one, we're going to complain and say hey
7:54 we didn't find one, you asked for say application/text or text/ I don't know, some type that we have no idea about it,
8:03 there's many mime types and content types. So then, once we have this, now we're just going to go down there and say
8:09 if we do have one, let's just return renderer.render, pass the value, pass the system, okay. This is looking good, let's see, I think we might be good
8:25 let's go ahead and run this and see if we get, if we got it working yet. We don't want to see non down here, we want to see a list of json ;
8:31 and, it has no render, oh yes, yes, yes, okay I'm calling this incorrectly so let's do something really quick here, let's say self.info and down here
8:42 let's store this, okay, so instead of doing this renderer we need to do the call to get a hold of the object, self.info, and then
8:51 we're going to get back the render function, and then we'll do this, so yes, that was a little bit off, wasn't it.
8:58 Alright, remember these renderers are render factories and so this call right here creates an instance of it
9:04 by calling this function which returns this render function and then we're calling value, passing value system to the function return there.
9:13 All right, try again— there we go, perfect, json. I think that other code that I wrote would have worked fine for images, for csvs,
9:23 but because we're using just the built in json 1 for this type with a little adaptation, it doesn't have that _render,
9:30 let's ask for something else, let's go have a different accept type, and we want csv, look at that, that is awesome, that is great,
9:41 so we're going to this one function right here, let's look up with api, we're going to this one function, and we just said,
9:48 I don't care what renderer you get me, you do your best that you can to provide the type that they're asking for,
9:54 so if we come over here and ask for text/ csv, we get this if we come over here and ask for json, we get this;
10:00 now if we ask for something that can't handle like image/ png which the individual one can handle, but the list like I said
10:09 would you make a composite image, what the heck would this be, it'll say error, could not convert type list to an image basically,
10:17 which I think that's reasonable, I can't imagine we could do much better than that. So it doesn't mean that it's always going to have a good answer
10:25 but I think it makes a lot of sense. Put this negotiation thing in there, and let the client ask.
10:30 Now, we can just set the default, like hey, if we don't know what it is, then do json, so for example one adaptation we could make here
10:37 is right now I'm raising an exception, say look if you ask for a type and we don't have a renderer that maps to it
10:43 we could just return the default type, right, we could say okay fine give me the one for this, right, this will be the all, the accept all renderer,
10:52 so that would be a possibility, or we could just say just return json or whatever but we chose to say look, we have no way to deal with this,
11:00 if it's a type that we haven't preset up, so you can be more precise, more restrictive, less precise, less restrictive
11:09 by just falling back to json or something to that effect. Ok, but that is how we work with our renderer,
11:16 now we don't have to do any of the adaptation stuff because it's already happening,
11:20 we may want to delegate here, like we might want to say something like this for renderer in self.renderers.values, right, so if somebody calls
11:31 here is how you move a car over like, suppose I take this off here for now and try, this should crash when I call this function,
11:40 don't have a converter right, but if I go and I add this adapter to the generalized one
11:45 now careful, it has to be done at the end, copied a little too much there didn't I, there, now that should add it to all of them,
11:57 it looks like that little bit here— let's go and fix up that json renderer, add adapter, it looks like I misspelled add adapter,
12:10 okay, so there we go, now let's just check if I remember this didn't convert because we had no adapter for car, now we've added it back in.
12:22 All right, so this is probably a good implementation, where we have this adapter add in for each one of those,
12:30 but we still need to be over here, if we're going to use it individually we still need to add it on this one as well,
12:40 so how much value is that, you know it's up to you guys— let's put this back as well, okay, so this is the composite pattern in action, right,
12:49 this thing really holds a bunch of these, it's made up of a bunch of renderers
12:52 but in fact acts as its own renderer, which means that we can just go here and say render=negotiate, and however we've configured the system
13:00 it's just going to handle that for us, and that's quite a cool thing. Let's go down here and fix up this other one,
13:07 we have single auto and single auto image, that's super annoying, isn't it, let's apply a little fix here, for this as well;
13:13 so we have this, we have get, but this accept type can now go away and this is now also going to be negotiate, all right,
13:20 how about post this one, it's your call, does it make sense to put it here, maybe it does, it doesn't, the renderer doesn't mean you can post like csv,
13:30 so you have to post json right, from here right, really from right there, but the type you're going to get back does it make sense—
13:37 I'm thinking for the creating auto since you got to post json, I am going to leave that one alone, the put, this one doesn't have a renderer,
13:44 it doesn't do anything, same with delete, it doesn't do anything. So this one I think I'll go and leave this as json
13:49 like if you're creating an auto, maybe just it's json in, it's json out. But everything else here is fine, now notice, this one this responds 404
14:00 and this json body again, if there's an error, you're getting json back, basically but if there's no errors, you get what you ask for.
14:06 I'm okay with that to keep it simple, we could do a lot of work we could actually like somehow work with the renderer here
14:14 but let's not do that right, error cases, they can just look at the status code right 404 is going to be telling them everything they need to know
14:22 and that works with all the different formats. But this one I think is really sweet, right
14:26 we've added this negotiate and now it can do whatever it wants,
14:29 let's get rid of this, I think I had copied that from duplicating it somewhere and so on but we don't need the csv, just all autos, right,
14:35 let's see this one more time, we've added negotiate to the single auto so let's go to that;
14:38 so here, for all the autos let's start with json, do we have it, we have json for text/ csv
14:46 we have csv, now we don't have image because who knows how to do that right error 500 internal server error, maybe that's what we want
14:54 it could be bad request, like we could catch that, determine that, return a bad request, but we're not doing that.
15:00 Now, let's get one of these individually, and let's start out with json again,
15:03 remember my little trick, first, so if we go ask for first, now we get json pretty cool, go up here and ask for text/ csv,
15:14 name is not defined, what have we done wrong here? Oh, again, that's supposed to be none, we'll get the bugs out of the system all right try again,
15:26 so we ask for text/ csv, we get text/ csv, it's just one car but you know, maybe your parser is expecting csv,
15:31 so here's the one car right, again json works. Now, if we ask for image, we should get which one do we have configured,
15:37 I think we have the direct one configured and boom— there it is, status 200 ok. Let's just double check which one we've got running here
15:47 we have the image direct factory running, so when we hit this, it goes off to the server, right
15:52 and sucks it down and then resends it through our web server, pretty darn cool. Now, again, we can do this against an individual car,
16:02 we can do not that, but we could do this against all of the cars if I can put that together correctly, because we just said negotiate.
16:10 Now, there is one more little catch that we have to be aware of and we'll come back to that next.