RESTful and HTTP APIs in Pyramid Transcripts
Chapter: Customer Renderers
Lecture: Adding a CSV renderer
0:01 All right let's start adding some custom renderers to our web api. Now, notice over here, I made a copy of this web app
0:10 and moved it into content negotiation, so we're going to come up with different contents and then we're actually going to add content negotiation
0:17 and I try to structure all of that together so if you want to follow along you can start from here,
0:21 there's a starter copy of the site just as we're starting from right now in there. So, what do we want to do? The goal of this first part would be
0:29 to change that to csv, and have csv comma separated values come out of this method, now we don't want to really lose json
0:37 I'm going to do this here, we can actually if we really wanted to we could come over here and set the accept type I believe,
0:45 equal to something like /text/ csv and then only apply that renderer in the case that this type matches, but where we're going
0:57 is actually going to be a better place than having separate methods so we're not going to do that,
1:02 and also it turns out that the accept type is more complex, we need some logic not just a string match there, okay.
1:08 So for now, I'm just going to switch this to csv, and then we'll switch it back to enable both json and csv as well.
1:15 Now, if we run this, how well is it going to work? It turns out probably not so well, let's find out.
1:23 So we come over here, all right, are we ready to get all the autos, we are— oops, we're not, no such renderer factory csv, sad face.
1:30 Okay, so our job is going to be to create such a renderer scv so let's go over here and because we're going to make many of these
1:37 and we care about having a well structured web application, let's make a folder, so come over here and I'll call this renderers
1:45 and we'll come down here, for now we won't worry about base classes
1:48 or inheritance or type checking sort of like do an abstract type or anything like that we're just going to come down here, I'll call this csv renderer,
2:00 let's just do it like this for the file name, and in here, we're going to add a class called csv renderer factory.
2:08 Now remember, the api that we needed to follow here is we had to have a call and it just took an info here
2:16 and then the call has to return a function, and we don't want this to be— we do want to be an instance,
2:24 and then it will return self._render, something like that, now not calling it, but just return the function, and then we're going to define this
2:33 and this is going to take the value and the system, remember the system gives us access to things like the request and so on.
2:41 Okay, so we'll say something like if not value return nothing let's just say we'll return empty string for our csv
2:48 if it's the case that nothing is going on there. It says it may be static but let's just say don't bother me for now,
2:57 I don't think it can remain static for long. Alright, so what do we need to do? In the end, our goal is to come up with a csv string equals something
3:06 and in the end it will return the csv string, right so that's going to be our implementation, but the question is
3:12 how do we take this value and convert it over? So, we need to be a little bit careful here,
3:20 because we may be given a single object, we may be given a list of objects and it turns out doing this in a super general way is not easy
3:27 but let's write this function here, assuming initially that we're given a list of dictionaries or a single dictionary and we got to turn that to a csv,
3:39 that's pretty general, and then we'll see that we can do a little bit better. So we'll say something like this, if is instance value
3:46 and it's a dictionary, then we're going to say the value is going to come
3:50 a list of one item containing that, we'll say if value is not, if not is instance value list, we'll raise value error, something like this;
4:06 can only process lists, something to that effect, and then we should check, maybe inside there's only dictionaries
4:13 there's a little bit of annoying tests that we have to do here so what we're going to assume is,
4:18 we're going to assume that this is either just a dictionary or a list of dictionaries and those are the only two types that we can handle.
4:27 Like I said, there should be probably more error handling here. So the first thing that we're going to do
4:32 is we're going to also assume that all the types are the same right that the list is homogeneous in terms of the types it contains
4:39 so the first one is representative of all of them. Alright, great, so how are we going to do this
4:44 let's come down, we'll get the headers, and this is going to be- let's save first, now we want to call this here like the value of zero
4:53 how do we know that this is ok— because if it's a list it's going to be false here and will return nothing,
4:59 if it's not a list, it's going to be a list of one item, right; so either way this should be okay, there should be a first
5:07 and in here, we're going assume this is a dictionary so we are going to go and say give me all the keys, this is going to be the header, so we'll say
5:15 this is going to be the comma separated value of header— now I probably should be using the csv module, but I'm just going to just type it out of here
5:23 just for simplicity sake, I want to introduce more things. So what we want to do is come over here and join this on all the headers
5:31 so we'll put commas between all the items and make a single string out of it, great, then what's next— we'll say for let's call them row in value
5:40 all right so remember value is a list at this point we've adapted it to that pretty much and each row is going to be a dictionary,
5:48 so let's come up with the line data so we're going to store all the values and we need to of course store them in the same order, alright,
5:57 so we'll say something like this, for k in header so we're going to get the key out of the header,
6:03 and then we'll say line data append, row of key right this is one of the parts we're assuming
6:11 every entry in this list is going to have all the keys right otherwise this is going to crash, and then we'll say something like this
6:18 the line is going to be again join this on the line data and then let's store up the rows here, let's go like this,
6:26 so we'll create this list here and then we'll just store each line and then finally we'll like generate the lines with having the header up here,
6:34 as well as all of these, so maybe the first thing we want to do is response rows csv string, and let's go ahead and just inline that,
6:43 all right, so we'll go like this and then we're going to come down here and we don't want this anymore, we want to put new lines after each row
6:51 so again we'll join and then response rows, there we go. So I think this is going to do it for a basic version here
6:58 so we're going to come through and just like this, so we're going to come though and we want to create this list of rows
7:07 the first row is going to be comma separated values of the headers and then we're going to go through each row and make sure we put in the same order,
7:15 because dictionaries are not ordered by default so we're going to go through the headers and pull out the value
7:20 for each one of those and then we'll get to join that to a line, add it to our response generate the whole response here.
7:25 Now, the final thing is we're not using this yet, so let's go ahead and let's do that down, I guess we'll do it at the top.
7:33 So we'll get the request, we can go to system, say get request then to the request, we can say response—
7:43 the request always has the response objects on it and then we want to set the content type
7:51 text/ csv, let's try this again; now it's going to turn out to still not work
7:58 well, first I guess we have to install it, but let's go ahead and just verify it still didn't do, there is as if no such thing as csv.
8:05 So our next step will be to install this in our dunder init, just like where we configured our json,
8:13 we also want to do another one of these add renderers but not for json but this time for csv, okay, let's make that csv renderer,
8:22 we're just going to have to import this, allocate instance of it, now that should almost work, it's not going to work
8:28 you're going to see it's still not going to work but we'll get a different error. Car has no object attribute keys,
8:35 remember our data layers now returning rich objects that's good, that's how we wanted to work but it's not so happy about this car thing
8:42 okay so let me just hack something in here for a minute it's kind of not great, we'll come down here and say
8:48 if this is car, we're not going to leave this here so don't worry, we'll go like this if it's a car, then it's not to dict, yeah
8:58 down here we'll go through and we'll say if it's a list, go through each item and convert it, not great, but that's what we're going to do,
9:10 let's do it like this idx, and we want to enumerate this we can put it back.
9:14 So, we can ask the same question, if it's a car set back in that same location we want to set item.to dict this better be item,
9:24 okay so that's not amazing, so if it's a car, we'll turn it to a list of dictionaries, if it's a list and it happens to contain cars,
9:35 we're going to turn those into their dictionaries and we know how to do that right there, okay let's see if it works now. Ready, set, go!
9:46 We've chosen to download a csv file, amazing what do you want to do, let's save that, come over here and let's change that to . csv, we're good
9:58 now let's see it in excel, you can always see in the preview, it looks like it's going to work; oh would you guys like to see what's new in excel?
10:05 Look at this, all right so this is totally working here we've got our price, we've got a year, we've got a little bit of damage
10:12 yes it has damage, ja, ok so we've got all the pieces that we need represented as a csv, we've got our header at the top and everything
10:21 this looks great, even when there was no item when it was like null basically, we still generated that correctly so this looks really good.
10:28 Now, of course if commas are in our values that's not so great like I said we should really use the csv module
10:34 but the point is just like keep it simple, right. That's great, but what if we want to go back, what if we wanted json again?
10:42 Well we could, let's go and run postmen against that same url. We could get this to work with both, but we're going to need to do a few things first
10:50 we're going to need to fix this car thing because obviously hard coding car into the renderer like what's the point of the renderer then
10:59 so there's a few options on how we can deal with that, and the other is how do we keep json around; those are up next.