[MUSIC PLAYING]
BRIAN YU: Welcome back, everyone, to web programming with Python and JavaScript.
So picking up where we left off last time-- we were talking about data
and, ultimately, how to represent data in our web applications--
ultimately in the form of databases.
So we looked a lot at this example of trying
to represent airlines and the things that an airline might
need to keep track of-- keeping track of different flights, each of which
has an origin and a destination and a duration for how long that flight is.
And today, we're going to dive in even more into databases,
looking at how we can take advantage of a full-featured programming language,
like Python, to be able to interact with databases all the more powerfully,
take advantage of the features that the Python programming language has
to offer, to let us interact with the data in a way that's
a little more convenient.
And then a little later on today, we'll take a look
at APIs, Application Programming Interfaces-- ways
that we can allow other applications to interact with our application and ways
that we can use APIs written by other people to get access to other data
as well.
And this come into play as you begin to work on project 1, which is ultimately
about building a system that lets you rate reviews for books
and look up reviews for books that exist out there on the internet.
And as part of that, you'll be using an existing API
to get at reviews that other people have written elsewhere on the internet
and, also, to build an API of your own so that users of your website
can use your API to be able to access the rating data that is ultimately
going to be inside of your own database.
So in order to interact with this database, in the past,
we've been using SQL or S-Q-L, a language that
lets us interact with data by performing commands like Insert to insert rows
into a table, or Update to take existing rows from the table
and update the contents of those tables, Delete to get rid of them,
and Select in order to extract data out of those tables.
But what we're going to turn to now is to take a look at this
from a more Python-based approach.
And in particular, before we dive back into databases,
we're first just going to talk about object-oriented programming, which
is a type of programming that Python allows
us to do, along with a bunch of other programming languages, that primarily
focuses on programming-- thinking in the concept of objects,
where you can think of an object as just a thing.
For example, an object in one of our applications
might be a flight, for example.
We might have an object for each individual flight.
We might have an object for each individual passenger.
And what object-oriented programming allows us to do
is to create what are called "classes" or the generic idea
for a generic object.
So we'll have a Flight class that is going
to describe all of the components that make up a flight.
And from that Flight class, we can also describe things
that we want our flights to be able to do.
We would like for our Flight class, for example,
to be able to easily add a passenger to that flight.
And likewise, we'll have a Passenger class
that is going to represent the generic idea of a passenger, where
that passenger is going to have a name and they'll also
be associated with the flight that they're currently registered for.
And so using object-oriented programming in Python,
we'll be able to programmatically devise these ideas inside of code.
And so let's take a look at what that would
look like by looking at a simple example of a Python class.
We took a look at this earlier when we first introduced Python.
But now, we'll begin to dive a little deeper into how Python classes work
and how they operate, so that we can see how we can then
use those ideas to apply them to the databases
that we'll be building out inside of our web applications.
So the first thing we'll look at is that Class is 0.py.
And all this file does is define a Python class.
In this case, we're just defining the generic idea for what a flight is.
And so up here on line 1, we say class Flight
to mean we're going to create a new Python
class called Flight that is going to represent flights inside
of our application.
Here on line 3 is what's called a method.
You can think of a method as a function that
is performed on individual flights.
And in this case, this is a special method built into Python,
called the init method, which is going to describe what happens what we first
want to create a flight.
When we don't have a flight before and we want to create our first Flight
object, we'll call this init method.
And what this init method will do is it will take a couple of parameters.
It will take self--
so methods in Python classes will, generally, all
have self as their first argument.
That self first argument just refers to the objects that we are working with.
And you'll see more examples of this once we actually
start using this class.
Then we also have a bunch of other arguments to this method.
We have origin and destination and duration.
Because when we create a new flight, those
are the different fields that we're going to want to populate.
That's the information that we want to store about any particular flight.
We want to store that flight's origin, its destination, and its duration.
And in order to store them, we'll store them inside
of properties inside of our object.
So if self is our object, then we'll set the property self.origin
to be equal to whatever this origin is, whatever
we pass in to this init method.
And likewise, we'll do the same thing for destination and for duration.
That way, when we create a flight, all this init method is doing is saying,
when we create a flight, I want to provide,
as input, the origin, destination, and duration of the flight.
And then I want to save that information inside of variables
contained within our Flight object.
And the names for those properties will be Origin, Destination, and Duration.
So how then do we actually take this class
and actually use it for something meaningful?
So far, it just seems very abstract.
Well, let's take a look at classes1.py, where
you'll see an example of how we might actually use this.
So the first six lines of classes1.py does exactly the same thing.
We're just defining this generic class to represent flights inside
of our Python program.
But now, here, we have a main function, which
is going to do a couple of things.
So let's take a look at it.
So inside of our main function, the first thing we're doing here on line 12
is creating a new flight.
And so to create a new flight, we're going to use the name of the class.
The name of the class is Flight with a capital F.
And then in parentheses, we're going to specify
those parameters that we defined as part of the init method of our Flight class.
And so Python knows that, when I want to create a new flight,
it's going to call that init method to create a new Flight object.
And we're going to pass in those specific parameters.
We're going to say, we'll create a flight whose origin is
New York, whose destination is Paris.
And the duration is going to be 540 minutes in this case.
And we're going to save that newly-created object
inside of this variable called F.
So F is a variable of type Flight.
Just like before we were able to have variables that were integers or strings
or lists of other things, we're now able to create a variable that is of a type
that we've created.
It's type is this class, Flight, that we defined earlier in this file.
So that's how we've now created this flight.
And now, because our flight has stored information about itself--
it's stored information about its origin, about its destination,
about its duration-- we can now access those individual properties.
So if I want to change the value of this flight's duration,
because the flight was delayed and now it's going to take a little bit longer,
I can access the duration of my flight, F, by writing F.duration.
And then plus equals 10 is just Python syntax for,
increment the value of the duration of the flight by 10.
I could have equivalently written f.duration=f.duration+10.
And that would mean exactly the same thing.
But the idea here is that I'm updating this value of duration
in order to reflect some new value.
So I can access them that way.
And likewise, I can also just read them by calling them
by name-- f.origin, f.destination, and f.duration.
Saying Print in front of each one is just
going to print out this flight's origin, print out the flight's destination,
and print out the duration of the flight as well.
So when I go to run this code and I run Python classes1.py,
what I get is one on each line--
the origin of the flight--
New York-- the destination of the flight--
Paris-- and 550, which was the duration of the flight--
540 with 10 added to it, as we updated the value of that duration
property of the flight.
Questions about how that worked?
About how we were able to use this class definition for what a flight is up
in the first six lines of this file and then
use that inside of our main function to create a flight, update its properties,
and then print out information about the flight afterwards?
Yeah?
AUDIENCE: So do we have to use "self" always?
[INAUDIBLE]
BRIAN YU: Good question.
So the question is, what is this self?
Do we have to use it?
So self is-- whenever we want to call methods on individual flights,
we're going to need to have that Self perimeter there,
so that we're able to update specific properties of the flight.
So we can update the origin of this specific object.
And you'll see this in a couple of minutes
when we start to look at examples where we might
start to have more than one flight.
So I create one flight.
And I create another Flight object.
And now, if I want to access the destination of a flight inside of one
of these methods, well, these methods need to have some idea
of, which flight am I referring to?
And so that's what this Self property is doing.
It's telling the method which specific flight I'm referring to, when
I want to use it inside of a method.
And we never actually need to pass self into the methods when we use them.
Notice that when I created the flight down here, I didn't have to say,
self equals anything.
That wasn't part of the syntax of creating a new flight.
That self is just implicit.
The first argument of the method is just assumed to be Self
and it will be put there automatically by Python.
AUDIENCE: So it has to be S-E-L-F always?
BRIAN YU: OK, so the question is, does it have to be exactly S-E-L-F?
That word "self"?
Technically, no.
You could probably get away with using something else.
But by convention, we'll usually just call it "self"
when dealing with Python classes.
Good question.
Other things?
Yeah?
AUDIENCE: Do we need an init method?
BRIAN YU: Good question-- do you need an init method?
Strictly speaking, you could create a class that didn't have an init method.
So I could have created a class that just had nothing in it.
And in Python, the way to say that we want nothing in here
and just keep going is to use the word "pass."
That would have created just an empty class that doesn't have
any init method or any methods at all.
And I could still assign properties to it.
But in order to allow me to do syntax like this where,
when I create a new flight, I specify the origin and the destination
and duration, then you need the init method there,
in order to tell the class what to do when you create a new objects.
So conventionally, we'll see it there.
But later on today, we'll actually see examples where not all of our classes
will have that init method, because we don't always need it.
AUDIENCE: Can you put the last slide in [INAUDIBLE]??
Why do we need this equal statement, name equal to main?
BRIAN YU: Good question.
So the question is, why do we need this last line here, this if name equals
equals main?
So just naturally, Python is going to execute our code from top to bottom.
So it's just going to read the top and go down to the bottom.
And all of the relevant and interesting code
that we're doing here in this program is contained inside of this main function.
And so, unless I ever call that main function,
this code is never going to run.
And so what these last few lines here are doing is saying, if__name__=main--
this is saying, effectively, if I am running this particular file,
if I am running the classes1.py file, what should I do?
And in this case, we're just going to call the main function.
And this is a convention that you'll frequently
see whenever we're writing short scripts in Python.
AUDIENCE: If I remove these lines, like, if I remove this if statement
and directly call main, that can still [INAUDIBLE]..
BRIAN YU: OK, so the question is, if I remove the if statement
and just called main, would that work?
And yes, if I just ran this, it would successfully call main,
because it reads it top to bottom and it calls the main function.
The reason why we generally wouldn't want to do that
is, if ever we wanted to import this file into some other file,
if I wanted to use my Flight class inside of some other Python file,
if I tried to import classes1.py, it would read the code top to bottom.
It would hit this main line.
And then it would try to run the main function, which I might not
want to happen if I don't want this particular flight to be created
every time I import the file somewhere.
And so, the "if name equals main" syntax says, only when I'm running this file
and not when I'm importing it into some other file,
then I want to run this particular function.
Good questions.
OK, so now that we have this init method that's just a default method that's
used to create a new object, let's start looking at other methods
we could use in order to add functionality to our flights.
So we would like our flights to be able to do more things.
So let's take a look at classes2.py.
And in this case, we noticed that, in the previous example,
we had to, after creating the flight, print out
information about that flight.
We had to say, print the duration, print the origin, print the destination.
Maybe we would like for the flight itself to know how
to print information out about itself.
And so how might we do that?
So the first six lines here are identical.
We're just creating that init method that
allows us to create a brand new flight.
But down here on line 8, we've added a new method
that we've created for this Flight class that's
going to give the flight some additional functionality.
In particular, we're calling it Print Info.
And this Print Info method, which takes self, once again--
the object that we're going to be operating on--
is going to serve the purpose of printing out
information about the flight.
So the flight will now know how to print out its origin and destination
and duration.
How does it do that?
Well, inside of the Print Info method, we're saying,
here's what we should do whenever we print info.
Print this formatted string--
Flight origin.
And what is the origin?
Well, I have access to this variable self that represents the object
that I'm operating on.
So Flight origin colon--
and then, remember, these curly braces are used in formatted strings to say,
insert some variable here.
And in particular, in this case, the variable that we're inserting
is self.origin.
Self refers to the flight that we're trying to print information.
.origin will access that origin property.
And likewise, we do the same thing for destination and duration.
And now, this is part of the definition of the Flight class.
So now, anytime I create a new flight using this class definition,
we're going to be able to use this Print Info method,
because every flight will now know how to print out information about itself.
And so, now this seems like more lines of code.
Why would we want to do this?
But this is now useful if we have multiple flights.
Now, all of them will have that same functionality.
So if I look at what's going on inside of my Main method here,
now I have two flights.
So on line 16, I have f1, my first flight-- is going to be a flight.
And this is just using that init method syntax to create a new flight--
origin is New York.
Destination is Paris.
Duration is 540 minutes.
And then I'm going to call upon that method, using that same dot syntax.
So whenever I want to access a property of an object
or call on a method of that object to run one of the functions
that I've defined inside that Flight class, we'll use the dot syntax.
And so f1.print_info is going to say, take my object, F1--
this is a flight--
and print information about it by calling that Print Info method
that we defined earlier in the file.
And likewise, we'll do the same thing for F2,
another flight with a different origin, a different destination,
and a different duration.
Let's print out information for that flight as well by calling
f2.print_info.
And so when we call f1.print_info, then the self in this Print Info method will
be f1, that first flight.
And when we call it the second time, self will be f2, the second flight.
And so self.origin, self.destination and self.duration will refer to f1 and f2
respectively, when we call upon those methods.
So now, all I need to do, if I run Python classes2.py,
is I'll see, on the first three lines, the origin, destination,
and duration of the first flight and then,
following that, the origin, destination, and duration of the second flight.
Because both of these flights now have that ability
to print out information about themselves.
Yeah?
AUDIENCE: Do you actually have to say origin equals New York?
Or can you go, New York, Paris, [INAUDIBLE]??
BRIAN YU: Great question.
So the question is, did I need to specify origin equals,
destination equals, duration equals?
Strictly speaking, no.
Because Python assumes that I can just pass in the parameters in order.
I could have said, just New York, Paris, and 550.
I added the parameters there, which are optional but allowable,
just to be very explicit about, what are the names of each parameter?
And another thing that allows me to do is put them in whatever order
that I want to, if I wanted to.
But if you know the order of the parameters
and you put those parameters in the correct order,
then-- strictly speaking-- you don't need those names in front of them.
AUDIENCE: So then that--
BRIAN YU: Yeah?
AUDIENCE: --means, when we define the class first,
self should come first always?
BRIAN YU: Yeah, good question.
So does self need to come first?
Yes, self is always going to be the first thing that
is passed into a method for an individual object--
good question.
OK, so that was a Print Info method.
And the Print Info method didn't take any other arguments other
than just self.
It just required the self to be passed in,
which was just whatever object we're working with.
But we can also allow these custom methods
that we write to take in arguments of their own.
And so we'll take a look at an example of that.
Here in classes3.py, we have the same init method, the same Print Info
method.
But now, let's give the flight the ability to delay itself.
So if the flight's running late and we need the flight to be delayed,
let's give the flight the ability to change its duration by whatever
the delay amount is.
And so here, on line 13, I've defined a new method called Delay,
which takes in the same self as the initial argument--
what is the object were operating on?--
and then an amount to delay the flight by.
And so what's happening here--
inside the Delay function, I've set self.duration plus equals amount
to mean, take whatever duration it was previously and add amount to it.
And so now, inside the main method here, we have--
f1 is this new flight.
We're going to do f1.delay to say, call the Delay method,
passing in amount as that argument.
So Delay technically takes two arguments-- self and amount.
But remember, self is just implicitly put in there by Python,
so we don't need to worry about it.
Amount is the only one we now need to specify.
So f1.delay(10) is going to say, delay the f1 flight by 10 minutes.
And that is going to update the duration of the flight.
And then we can print the information out after that.
And so, by running Python classes3.py, we now
see that the duration is 550 minutes, as compared to the 540 that we had before.
Questions about any of that so far?
And so one of the nice things-- yeah, quick question?
AUDIENCE: Yes, is main considered a method as well?
Or is it a function?
BRIAN YU: Main is a function, in this case.
It's not a method that's associated with the Flight class.
It just happens to use the flight in it.
And so one of the nice things here is that, inside of main,
I really don't need to worry about exactly how my individual methods are
implemented.
Delay and Print Info might do things that I'm not aware of.
But as long as I understand what the interface is,
that there is a Delay function that allows
me to delay a flight by a certain number of minutes
and a Print Info function that prints out information about a flight,
I just need to know what those are and how
to use them without needing to worry about how they work.
And so, especially on larger projects where multiple people might
be working on different parts of the same program or application,
if one person is writing up the actual implementation of the methods,
someone else can be writing the code that uses those methods,
without needing to worry too much about how exactly
those methods are implemented and what exactly is happening inside of them.
So we've been able to create a Flight class that represents a generic flight.
Let's look at a more sophisticated example
now, where we might have multiple different classes-- where
in addition to having a Flight class, we might also want a class
to represent Passengers, going back to last time
where we were talking about having a separate way of keeping
track of passengers for flights and the flights themselves.
So let's look at classes4.py, which is just going
to be a little more sophisticated.
So the first thing that I'm going to do inside the Flight class now is--
I'm keeping track of a counter that is going to allow me to number
each one of my flights.
And so this seems similar to that ID column that we had in our SQL
databases last time, where each flight was going to have a different ID.
The reason we're now doing this is that our passengers
need to have some notion of what flight are they associated with.
And an easy way to do that, as we saw last time,
was to associate each passenger with what flight number
that they are tied to.
So this counter is just going to start at 1.
And we're going to update that counter every time we create a new flight.
And we'll see how we do that.
So inside of our init method, we have a couple
of things going on here that are different from last time.
So the first thing that we want to do is keep
track of this particular flight's ID.
So we're going to-- here on line 8-- set the self.ID property
to whatever the Flight.counter is.
Counter was that variable that we created inside of Flight before.
And whatever it is, that's going to be the ID.
And then we're going to take Flight.counter and update it.
Flight.counter plus equals 1, to say, increment the counter
to be the next number.
So that the first flight we create has the ID 1.
Then, Counter updates to 2, so that the next time that I create a new flight,
the ID is going to be 2, and so on and so forth.
Notice that ID is not a parameter that is passed in in the init function.
It's just something that's happening inside of the method
that the user doesn't need to worry about.
We're also going to keep track now of the passengers that
are on this particular flight.
And so we're adding another property, self.passengers,
which is just going to start out equal to the empty list.
We'll maintain a list of all the passengers on this flight.
And it starts out as empty, because when we first create the flight,
nobody is a passenger on that flight.
And then, finally, the last three lines are the same things we've seen before.
We're keeping track of the origin and the destination
and the duration of that flight, in order
to store all that information inside of our Flight object.
So what else do we need to do now?
Print Info looks exactly the same, except we're also
going to print passengers.
And Delay looks the same.
But let's quickly take a look at the Passenger class on line 37.
So the Passenger class is actually very simple.
All the Passenger class is doing right now
is it's keeping track of its own name.
So when we create a new passenger, we're setting
self.name equal to whatever the name of the passenger is.
And now, we've created a new passenger.
And so how then do we keep track or tie passengers together
with an individual flight?
Well, that's happening here inside of this Add Passenger method.
And so the Add Passenger method takes this input, self, which is the flight.
Notice that Add Passenger is a method of the Flight class.
And so what is Add Passenger doing?
It takes, as input, itself.
And it takes, as input, p, which is just going to be a Passenger object.
And here are the two things that we're going
to do to keep track of these relationships between flights
and passengers.
The first thing we're going to do is take
self.passengers which, you'll recall from up here, begins as an empty list.
self.passengers begins as an empty list.
And what we're going to do is append to that list.
All lists in Python have an Append method
that adds another thing to the list.
And we're just going to add the passenger, p,
to our list of passengers.
So now, our flight, which is keeping track of our list of passengers,
now knows that there's another passenger on this flight.
And now, on this next line, we're setting the passenger's flight ID
to be equal to whatever self.id is.
So now, the passenger knows, via that flight ID
property, what the ID of the flight is.
Remember, self is the Flight object.
P is the Passenger object.
So we add to the flight's list of passengers that new Passenger object.
And then we update the Passenger object to keep track of what flight
it is associated with.
And Print Info now, in addition to printing out the origin, destination,
and duration of the flight, will also run a loop,
looping over all of self.passengers, looping
over every passenger in that list of passengers,
and printing out that passenger's name.
So how do we use that?
What does that look like?
Well, inside of our Main function here, we first
create a flight, f1, same as before.
And now we're going to create Passenger objects--
Alice and Bob.
We just create them using the Passenger class
and providing a name for what their name is.
And now, in order to add passengers, we just
need to call this Add Passenger method on the flight.
So f1 is our flight.
Add Passenger is the method.
Alice is the name of a passenger object.
And so each time we call Add Passenger, that
is going to append to the list of f1's passengers
and also update Alice's flight ID property to be
equal to whatever the ID of f1 is.
And so, when we Print Info at the end here, after adding two passengers,
the result is that we get the information about the flight and then,
also, the list of the names of the two passengers that we have on that flight.
And so was two different classes now interacting together
in a way that's meaningful.
Questions about any of that and how that worked?
Yeah?
AUDIENCE: So when I have [INAUDIBLE] passengers in the flight,
if I want to [INAUDIBLE] this particular passenger in the flight,
how would I search him?
BRIAN YU: Good question.
So the question is, well, OK, I'm maintaining this list of passengers.
What if I wanted to search through the passengers?
Find a passenger named Alice, for example,
in a list of a dozen or more passengers?
So certainly, you could write Python code that would be able to do that.
That could, for instance, loop through your list of passengers
in order to find it.
But ultimately, this is starting to be the type of situation
where we would really like to be able to use a database.
Because a database, like a SQL database, lets us perform queries like Select*
from passengers where Name equals Alice, for instance,
that lets us get at just Alice's passenger information.
And so far, what we've been doing has been
a lot of just writing Python code that doesn't actually
interact with any database yet.
We haven't tied this to a SQL database.
But that's where we're headed.
And so in fact, what we're going to talk about next is going to be something
called object relational mapping.
And so the idea of Object Relational Mapping, or ORM,
is that we have this powerful language feature in Python,
this object-oriented programming syntax that
lets us create classes, like the Flight class,
define properties on those classes, like the destination and duration;
lets us define methods on those classes that
let us add behavior or functionality to those classes--
the ability to add a passenger or the ability to delay a flight.
And we also have-- in sort of a separate world--
our world of SQL databases-- or database of tables, each one of which
has some number of rows to which we can insert, or update, or select, or delete
from them.
And what object-relational mapping is going to allow us to do
is tie those two worlds together.
Its going to allow us to use Python classes and methods
and objects to interact with a SQL database.
And so that's ultimately where we're headed
and what we're going to look at a couple of examples of in just a moment.
And in order to do that--
tie it with web applications written in Flask--
we're going to use a package called Flask SQLAlchemy, which
is a way of tying SQLAlchemy, which we were using last time in order
to write Python code that allowed us to execute database statements.
And it's going to make it easier to tie that in with a Flask application,
so it's such that, when users are using our Flask application,
they can take advantage of more database features.
And we'll take a look at examples of how that's going to work in just a moment.
But the end goal here is to be able to use
Python code and classes and object-oriented programming
to be able to interact with our databases.
So let's take a look at how we might go about doing that in SQLAlchemy
with Flask.
So let's take a look at models.py here, which
is going to be a key file for understanding how we're
going to tie these two worlds together.
And so this is a file written using this Flask SQLAlchemy framework.
And what we're doing here is we're going to define classes.
And the relationship that we're going to understand
is that, for any table that we want inside of our database,
we're going to have one class inside of this Models file.
And so the key lines to take a look at here are 5 through 10,
where we're defining this Flight class.
We added this db.model here in parentheses
to mean that the flight is inheriting from the DB.model
or the model of the SQLAlchemy database.
No need to worry too much about what exactly that means--
but effectively, we're allowing our Flight class
to be defined as a class that has some built in relationship that SQLAlchemy
has written for interacting with our database.
And so, what does this class, Flight, look like?
Well, in line 6, where you define this first property, __tablename__--
this is going to correspond with the name of the table
that we want to create in our database.
So recall that, inside of our SQL database,
every database has a number of tables.
And every table has a name.
This is saying that the class, Flight, should correspond with the table
name, flights-- all lowercase.
And now, after that--
one on each row--
we're going to define the columns that are contained inside of our Flight
table, much like we did before.
So each flight is going to have an ID.
And so, ID is a column.
And what type is it?
Well, it's an integer.
And it's going to be, primary key is true.
So it's going to be the primary way via which we identify a Flight object.
And likewise, we have origin, which is going
to be a column that is a string value.
And nullable=false is the equivalent of what we did in SQL to say, no,
we don't want a flight to have no origin.
And same for destination.
That is likewise going to be a string that is not nullable.
And the duration is going to be the amount of time,
in minutes, that the flight lasts.
That's going to be an integer.
And so what we've done here is defined a Python class
that has all of these properties that represent the individual columns.
And that's going to allow us to use Flight objects,
like the ones we were using in the examples a moment ago,
to actually interact with the database, such that when we say, f1.duration+=10,
because they update the duration-- that is actually going to, automatically--
through SQLAlchemy--
perform an update on our database to update the duration of that flight
to be 10 minutes longer.
And so, likewise, we're doing the same thing here in the Passenger class,
where we defined a class called Passenger.
And the table name for that is going to be Passengers.
Each passenger has an ID, which is an integer.
Each passenger has a name, which is a string.
And then, flight ID--
if we recall, flight ID is what we've call
a foreign key, a column of our table that was referencing
some column of some other table.
In particular, the flight ID column of our Passengers table
was going to reference the ID column of our Flights table.
That way, for any given passenger, you can associate them with which flight
that they are a part of.
And so flight ID is also going to be a column.
It's going to be of type integer.
And it's going to be a foreign key, we can additionally specify.
And what column is it going to reference?
Well, it's going to reference the flights.ID column-- in particular,
the ID column of our Flights table.
And so, so far, this hasn't yet added any functionality for us.
But you can see that we've rewritten the existing syntax of the Create Table
commands that you've likely been using in order to create tables in SQL.
And we've rewritten that, using Python classes, such that, in a moment,
we'll be able to actually interact with them,
in order to do some interesting or useful work.
So let's take a look now at how we might actually use that.
And so, one immediately nice feature is that, when we have a class like this--
this is just the same thing that we saw in models.py just a moment ago--
it makes it very easy for us, now, to be able to create those tables.
So before, when in SQL you wanted to create a new table,
you would either-- via the command line or by using a web interface, like admin
or-- you would type in a Create Table command that
would create a table, Flights, that has all of these columns.
Well, if you define a class like this, SQLAlchemy already
knows how to take this and translate it into SQL syntax.
So instead of needing to write code, like, Create Table flights,
with all of these columns, like we saw before-- this
is how we would conventionally just create a table using raw SQL.
SQLAlchemy lets us do a command, like, db.create_all that will take
our classes and just automatically create all of those tables,
using the types that we specified, using those nullable=false constraints
to say, don't allow this particular column to be null.
And so this vastly simplifies the process of creating a table,
without needing to worry about the exact SQL syntax.
We can just use our Python classes instead.
And so let's take a look at how we would actually do that,
using Flask SQLAlchemy.
So inside of create.py, we have, up here, a little bit of configuration.
This configuration is just telling our Flask application what database to use.
Here, we're drawing from the environment variable database URL,
much like you saw in some of the examples and in project 1,
for instance.
And I also am going to import from Models
that file that we had earlier that defined all of the various classes--
import*, to mean, import everything.
Import all of those classes.
And import that database.
And this db.init_app(app) is saying, tie this database with this Flask
application.
So we're not actually running a Flask application yet.
But we're going to use Flask SQLAlchemy and build up to, eventually,
using a web application using these features.
And so now, inside of our main function, all we need to do is say,
db.create_all.
And that's going to create tables, based on what
we had inside of our Models file and all of those classes that we defined there.
And if you're curious, down here in the "if name equals main," this here,
the with app.app_context is just one of the nuances of Flask,
which is that Flask has particular rules for when you're allowed to interact
with the application.
In particular, Flask behaves slightly differently
when a user is requesting to access a Flask web-page versus when
there is no request to the application.
And we need this with app.app_context to allow us to, on the command line,
interact with our Flask application.
Because our command line program needs to have some notion of what
Flask application we're dealing with.
So no need to worry about that too much.
But just wanted to give you some context for what that line is, in case you
see it in a couple of the files here.
So what I'm going to do now is open up the lecture 4 database.
And I'm doing this via the command line.
But again, you could look at the same thing
using Admin or any of the other web-based tools
for taking a look at web sites.
And in a post square syntax, backslash d lists out all of the tables
that I currently have.
And right now, we find no relations-- no tables, effectively.
And so, if I now run my create.py code, remembering that all I did inside
of create.py was run this db.create_all line inside of my main function--
once that's done, if I go back here and do backslash d,
now I have a Flights table and this Passengers table.
Those tables were created for me.
I didn't need to run the Create Table command in order
to allow that to happen.
And so that's one compelling feature right out-of-the-box of Flask
SQLAlchemy, which is that it lets us, very easily,
create tables just using Python code and without needing to worry about
the specific syntax in SQL.
But let's take a look now at other things
that we might reasonably want to do.
I'll take a look at it at a high level first.
And then we'll dive into some more concrete examples.
So let's say that we wanted to do something
like inserting into the Flights table.
And so we might reasonably have, in SQL, a line that
would look something like this-- insert into flights in origin,
destination, and duration.
What values?
Well, the origin will be New York.
The destination is Paris.
The duration of 540.
That was what we've seen before, just in the world of SQL.
Now, how would we translate that to use our Python classes
to be able to do the same thing?
What would the Python code for that look like, using SQLAlchemy?
Well, it might look something like this.
These first three lines are just what we've already done before.
Flight is equal to a new Flight object, using this Flight class.
We're specifying the origin, destination, and duration.
We saw this in the code examples.
This is just one line.
But I've broken it up on the multiple, just for space reasons.
And then after we've created that Flight object,
I want to add it to my database.
And so I'll use a line like db.session.add(flight).
Recall from last time that we talked about
how, if we wanted to group a bunch of commands together,
we might use sessions that we commit at the end of every session to group
a bunch of commands together.
Flask SQLAlchemy does that for us and just automatically will create these
sessions for us, so that we can say, db.session.add(flight) to mean,
add this flight object to my database.
And so this would be the Python Flask SQLAlchemy version of the same SQL code
that we wrote before.
We create the object, just the same way we would create any Python object.
And then we add it to our database.
And so that would be the equivalent of the insert.
What about select?
Select star from flights--
well, now that we have this Flight class,
we can use any of the features that have been added to this class.
So recall that, in the flight examples that we
saw before, we added a special function called
Delay that would delay our flight.
We added a function called Add Passenger that added a passenger to our flight.
SQLAlchemy does this for us by adding a property
to our flights called Query that lets us query for particular information
about our flights.
And in particular, syntax like Flight.query.all
is going to be a method that will do exactly this, just
query for all of the flight information and give it back to us.
And again, we'll take a look at a couple of examples of what
that looks like in just a moment.
But I wanted to show you a couple other ones.
So what about if we only wanted to select
from flights where the origin is Paris?
So we use this Where clause to filter out only particular flights
that we care about.
Well, that would look something like this.
Flight.query is the same as before.
And
Then this filter_by lets us add additional information to this query.
In particular, I want to filter my flight query,
only where origin is equal to Paris.
And then .all, again, is just going to say,
select all of the flights that match origin equal to Paris.
And so let's take a look at some of that, in actual code,
to take a look at how we might actually use that.
So let's first take a look at importing information into our database.
So this is import0.py.
This is what we saw last time, in fact.
This is a repeat of the code that we saw before,
where we had a line like db.execute, insert into flights origin,
destination, duration--
these particular values, where we were explicitly
writing SQL syntax into our Python code to mean, execute this SQL syntax.
What we're going to be looking at today is
how we can use Python classes and objects and this technology of ORM
to be able to avoid needing to write explicit SQL syntax
inside of our Python files.
And we can just use these classes and objects
to be able to achieve the same effects.
So let's take a look at import1, which is our new version of the same thing.
The configuration up here at the top is the same as we saw before.
But the interesting stuff is happening here.
So after we open our CSV file, we're going
to run a for loop that is going to loop over every line in the CSV file,
just like before.
But instead of running an insert into query
explicitly by reading db.execute insert into the flight's table,
we're just going to create a new flight, which
is going to be a Flight object that has an origin, destination,
and duration, just like we saw when we were creating objects
from classes before.
We're going to say db.session.add(flight) to mean add this
flight to our database.
This is the equivalent of running that Insert command.
And then at the end db.session.commit says,
we've made changes to the database.
I need to tell the database, yes, I'm done making changes.
Now, go ahead and actually make those changes, same as before.
And so db.session.commit says, go ahead and actually make those changes.
So now, if I run the import1.py, it's going
to say that we've added all those flights.
And so, now how do I look at them?
How do I select from them?
Let's take a look at some of those Select queries as well.
And so, let's look at list0.py.
And so I had list0.py.
This is, again, the same thing as we saw before.
We have db.execute select origin, destination, and duration from flights.
This was the explicit SQL syntax for selecting information out
of our tables.
And so after we selected that information,
we looped over our list of flights and just printed out information
about each one.
But now, inside of list1.py inside our main function,
we'll do something slightly differently.
Instead of explicitly using that select star from flights syntax,
we'll use flights=Flights.query.all to mean, select all of the flights.
And now, we're just going to do the same thing.
These little two lines are the same.
We're looping over each flight.
And for each individual flight, we're print out
its origin, destination, and duration.
And so now, by running Python list1.py, we've
listed out information about all of those flights.
And so we're slowly starting to translate the language of pure SQL
to using Python classes and methods to be able to perform the same thing.
And we'll see, soon, how that can become very powerful, as our SQL syntax might
to get more complicated but our Python code can remain relatively simple.
Questions about any of this so far?
Yeah?
AUDIENCE: Is Flights in the dictionary?
Or it's a list?
BRIAN YU: Good question.
What is the type of this Flights?
Flights here is going to be a list, where
each individual element of that list is going to be an individual flight.
And that is of the type Flight, that class that we've created.
And you can access the properties of those individual flights
by origin, destination, and duration.
And so you can see, if I were to print Flights here and just
print out what the value of that is and run list1 again,
you'll see that what I have here is what Flight
is, which is just a list of all of those individual flights,
where each one is one of these individual flight objects
that I've created from my models.
Good question though.
Yeah-- so .all gives me back a list of all of the objects that successfully
returned from that query.
Other things?
OK, let's take a look at a little more of the syntax
of how we might do things.
One common thing that we want to do is select from a database,
but only select one thing, only select the first thing that comes back.
For instance, if we wanted to select from our table of users
and we only wanted to get back the one user that
matches a particular username, well, the syntax for that might look something
like Limit 1, to say, only return back one thing.
And the way we would do that in SQLAlchemy is just to say,
after we have Flight.query.filtery_byorigin=Pairs,
to mean, select from flights where the origin is Paris--
.first is a special method that's built in to SQLAlchemy to say,
just extract that first result. And so then,
what you'll get back is not a list, like we just saw a moment ago,
but actually just that first thing that returned back.
And if there was nothing that met that particular filter-by-query,
if there was no flight whose origin was Paris,
then .first is going to give back None, as in nothing got returned.
Other things that we can do--
if we want to do Select count star-- this is something we saw before--
if we wanted to look at how many rows actually match this particular
property, we can do that via .count after the filter by.
So Flight.query.filter_byorigin=Paris stays the same.
And then the added line, .count, is going to say,
just return to me the number of rows that matched that filter by constraint.
Other things we can do.
Select star from flights where ID equals 28--
this is a common paradigm where we want to select something
by its individual ID.
We've already seen a way that we can do this.
We can use Flight.query.filter_by(ID=28).first
to mean, query my flights table.
Filter it out, so that I'm only getting the things whose ID is 28.
And get me the first thing.
But this paradigm is so common in SQLAlchemy--
so often it is that you want to take and extract from a table just one
particular row by its primary key, by its ID column,
that SQLAlchemy has a built-in way to do this even more efficiently,
which is just Flight.query.get(28).
And so this line, Flight.query.get(28), is going to say,
get me the flight whose ID is 28, and just return that object or none,
if there is nothing with ID 28.
And so this can often be a much conciser way
to extract information out of a table if you just want to get it by its ID.
Questions about these Select queries and how
we able to translate them into SQLAlchemy syntax?
And again, no need to worry about memorizing all of this.
The slides will be posted-- the source code as well.
So you can reference them as you think about how to perform your own queries.
But questions about the ideas so far in these Select queries?
OK, so we've looked at selecting data from a table.
We've looked at inserting data into a table.
We've looked at creating tables.
What about updating a table in order to change
the values inside of the columns of a particular row inside our database?
Well before, that would have looked something like this--
update flights set duration equals 280 where ID equals 6.
We would have gotten flight with ID 6 and changed its duration
to be some other duration.
How might we do that using our classes and objects
in this object-relational mapping that we're looking at in Python?
Well, we might do something like this. flight=Flight.query.get(6).
We saw before, that is just going to extract
for us the flight whose ID is 6.
And now that we have this flight object, we can manipulate its properties.
When we say Flight.duration=280, that is telling my flight object,
update your duration to be 280.
And that will have the effect of running this Update query.
We'll need to commit our changes at the end, like we did in the examples
before.
But this will have the effect of updating individual columns inside
of our table.
Likewise, if we wanted to delete something,
like delete from flights where ID equals 28, we could query for the 20th flight.
And then db.session.delete is sort of the opposite of db.session.add.
It will take an item, and it will delete it from our table.
And so now we've been able to recreate all of the same things
that we were able to do, using just raw SQL,
by using this Python syntax and SQLAlchemy that
helps to make things a little easier.
I'll go through a couple other quick pieces
of the syntax that might be useful.
But then I want to dive into a couple more examples
to give you a better sense for how this would actually work.
So at the end, when you're done making your changes
and you want to commit your changes and you ordinarily
would have written Commit inside of your SQL syntax,
db.session.commit is the syntax for doing that.
And we saw an example of that in the example
before, when we were inserting rows into our database.
What if we wanted to have slightly more advanced queries when we're
selecting information from our table?
So we saw before, maybe we want to select
all of our flights in a particular order-- order them by their origin
in a particular order.
That might look something like this.
Instead of filter by, our SQLAlchemy syntax
also has an Order By method that allows us to order
our flights in a particular way.
So in this case Flight.query.order_by(Flight.origin)
says query from the Flights table, put them in a particular order,
sort them by the origin column of the Flights table,
and get me back all of the results.
If we wanted it in reverse order, origin descending,
we would just add the .desc for Descending syntax into there as well.
And so order_by(Flight.origin.desc) is going to query for all the flights.
And we'll get back a list in reverse alphabetical order by their origin.
What about something like this, Select star from flights
where origin is not equal to Paris?
Well, inside of our filter by constraints
that we saw before, we were only ever able to say that something
is equal to something else.
When we're running a Python method, we can
pass in our parameters of what we want different parameter
values to be equal to.
But we don't really have a way of specifying that something is not
equal to something, for instance.
And so SQLAlchemy offers more advanced ways to perform queries.
In particular-- confusingly enough--
they call this filter, instead of filter by.
You'll see the slight nuance here.
So what the syntax looks like here is Flight.query.filter.
And then, inside a filter, I can put in arbitrary Boolean expressions
that are going to evaluate to True or False
on a particular flight and query that way.
So I can say Flight.query.filter flight.origin not equal to Paris
and then extract all from there to say, query
from the flights table for anything whose origin is not Paris.
And so this is allowable syntax in SQLAlchemy as well.
And as a result of that, you can begin to have
analogs for some of the more complicated Select expressions
that we looked at last week.
So we looked at, how do you select all flights whose origin
contain the letter A within them?
There is likewise a way to do Flight.origin.like,
which simulates that same effect and has the effect of performing
that Like query that we saw in SQL just last time.
And I'll go through a couple others very quickly.
Select star from flights where origin is in Tokyo and Paris.
That looks something like this.
Again, we're going to do Flight.query.filter.
And then inside of the filter, we have Flight.origin.in_--
in with an underscore, because in happens to be a Python keywords,
so the underscore helps to differentiate it--
and then just this list, Tokyo and Paris.
This is just a Python list.
And we're saying, query for where the flight's origin is
in this list of possible origins.
We can also do Boolean expressions that are a compound, that have ands
and ors in them in much the same way.
We can specify "and" to mean, make sure that both of these constraints
are true when we're trying to query for something.
And likewise, we can say "or", to specify that one or the other
needs to be true.
These ands and ors are special SQLAlchemy syntax
that you'll need to import at the top of your file.
But no need to worry about them too much.
They will come up in some of your more advanced queries.
But in large part, you can likely get away
with just using the filter by syntax that we've been looking at earlier.
And finally, we'll look at one more example, an example
of joining multiple tables together.
So this is an example we saw from last time,
where we wanted to combine data from our Flights and Passengers table.
And we want to combine them on a particular constraint.
How does our query know how the Flights table and the Passengers table
are related?
Well, in particular, we want to make sure that the ID of our Flights table
corresponds with the Flight ID of our Passengers table.
And so the way that we would ultimately do
that is via syntax like this-- db.session.query.
We're going to query for multiple tables, query
for the Flight table and the Passengers table.
And we're going to filter it such that the flight ID needs to be
equal to the passengers' flight ID.
And we're going to select everything from there.
So-- so far, that's just been a lot more syntax.
It's not altogether clear, just yet, why this
is any better than what we've seen before, because it
seems like it's just different syntax.
It seems, maybe, a little bit simpler, but still verbose.
So why is this actually more powerful?
Well, let's take a look at how we can actually
begin to use some of this syntax in order
to do more interesting and more powerful things.
So let's take a look at the airline website example that we used last time,
where we wanted to create a website that lets people register for flights
and then view who is registered for a given flight.
So we'll go into Airline1, which is the same as an example
that we saw last time, just so we can recall what happens in Airline1.
When we run this program, we get this Book
a Flight page that shows me a list of all of the flights
that I currently have inside of my table.
And if I type in a passenger name, like Alice, and book the flight,
it's a success.
You've now booked your flight.
And that was all that Airline1 did for us.
It also had /flights, which was a route we could go to that would list out all
of our different flights.
And if I clicked on Paris to New York, for example,
it would give me details about that flight-- the origin, destination,
and duration, and the passenger.
And this was all code that we wrote last time.
But we're going to look at ways we can now improve upon that code this time.
Inside of Airline2, we're going to look at application.py
and see what's going on inside of application.py.
I'm just using a text editor inside of my terminal window for now.
And so what we're doing up here in the first couple lines
is just configuring the database to interact with our Flask application,
in much the same way as you saw in some of the command line programs.
But now, what's going on inside of some of these individual functions?
Let's take a look.
So in particular, our Index page, this original route
where we just went to our website and we wanted
to list out all of the flights-- right?
We wanted that dropdown box that let us select an individual flight we
wanted to register for and type in a passenger name and booked that flight.
We needed to first query for all of the flights that we wanted.
And so now, using SQLAlchemy the syntax for doing that is just to say,
flights=Flight.query.all, to mean, select all of that flight information.
And now that we have this variable flights,
we can pass it into our index.html template.
So that that template that displays the dropdown list of all the flights
that let us book a particular flight for a passenger
has access to the result of that query.
And so that was querying for everything.
And then, recall that, when a user selected a flight from the dropdown
list, typed in their name, and clicked Book Flight, what happened?
Well, it sent a request to this Book route via the Post HTTP Request method.
And what's happening inside the Book route?
Well, just like before-- and you can look to last time for more explanation
on that--
we're going to extract from the form the person's name--
because I typed in their name into that HTML form.
And I want to get that name, so that I can insert it into the database.
And we're going to try to get the flight ID by making sure
that whatever they passed in, whatever flight they picked,
is actually an integer.
And then, down here, we're going to make sure that the flight actually exists.
So what we're going to do is, if they say,
I want to register Alice for flight number 1, the first thing I should do
is make sure that flight number 1 actually exists.
So on line 28 here at the bottom, I've said,
Flight=Flight.query.get(Flight_id).
Recall that Flight.query.get was our method of saying,
extract from the Flights table the thing that has a particular ID.
And here, we're just extracting whatever has ID Flight ID.
And so this is more concise than our previous Select star from flights
where ID equals whatever Flight ID was and needing to pass that in separately.
So this allows us to extract that flight.
And now, if that flight is None--
in other words, if we didn't get anything back--
we're going to return an Error page.
But otherwise, we're going to add a passenger to our flight.
So we're saying, passenger equals new passenger,
whose name is Name and whose flight ID is .flight_id.
We're creating a new passenger, just like we have previously.
Then db.session.add(passenger) says, insert this passenger
into my Passengers table.
And then commit those changes.
And then we return.
It's been successful.
Likewise, inside of our Flights route, in order to list all the flights,
we're going to again use Flight.query.all to list out
all those flights, so that we can display a listing of all those flights.
And when someone clicks on an individual flight, what needs to happen?
Well, once again, we need to make sure the flight exists,
during Flight.query.get(flight_id).
And then we need to get all of the passengers on that flight.
Recall that on our individual page here, in addition
to information about the flight, we also listed
who is on that flight, who are the passengers that are on this flight.
And so, to do that, we can use a line like line 56 here.
Passengers=Passenger.query.filter_by.
So I'm querying the Passenger table under certain constraints.
And in particular, I'm querying it where flight ID is equal to this flight ID
and then getting back all the results.
So I'm querying for all of the passengers whose flight ID matches
whatever flight I'm trying to view.
And then after that, I'm going to return the flight.html template,
passing in all of that information about those individual passengers.
So a lot of this is the same as last time.
But we've made a couple of slight modifications.
In particular, notice that there is no raw SQL
queries actually inside of this code.
I'm just using SQLAlchemy and using the power of this ORM
to use classes and objects to be able to insert and select from my database.
But questions about how any of that is working so far?
OK, so one of the powerful features of the ORM,
in addition to just letting us avoid SQL syntax,
is that, because we now have classes, we can
begin to define specific behavior that we can add to individual classes.
Recall that, way back in the beginning of lecture,
we started with a very generic Flight class.
And then we gave the Flight class the ability
to add a passenger, for instance.
Let's try not to do the same thing with our web application here.
So we'll take a look at Airline3.
And in particular, let's take a look at models.py.
So what we have here is our Flight class, same as before.
Our flight class is going to have an ID, origin, destination, and duration.
But we can now add functionality, add features to our models.
In particular, lets add an Add Passenger method to our Flight class.
This Add Passenger method is going to take self as a parameter,
as well as the name of the passenger that we want to add.
And so what's going to happen now inside of Add Passenger--
well, to add a passenger, how do we do it?
We start with creating a new passenger.
It's a Passenger class.
The name is whatever the name that was passed in.
What is the flight ID that we should give to this passenger?
Well, because we have access to self, where self is the individual flight,
we can get at the relevant flight ID by doing self.id,
because self is the flight and self.id is whatever ID that current flight has.
And so, P equals Passenger, whose name is Name
and whose flight ID is whatever the ID of the current flight is.
After that, we db.session.add(P) to say, insert this new passenger
into the database.
And now, commit those changes.
So now, by adding this method to the Flight class that wasn't here before,
I have given the flight the ability to add a new passenger.
I've given it the instructions for how to take a name
and actually add a passenger to the database.
And so before, while in my application.py code,
I needed to explicitly create a new passenger, add it to the database,
commit those changes, now inside of application.py--
if I go down to this Book function, which
is what happens when the user types in their name and says,
I want to book a flight now.
After we verify that the flight exists, all we need to do,
after we verify the flight exists, to actually add the passenger,
is this line here on line 33--
Flight.add_passenger(name).
Name is that name of the passenger we wanted to add.
And flight.add_passenger(name) take care of everything we need to do to add
a new passenger to this flight.
Because the Add Passenger method that we wrote
knows how to take the ID of the flight and associate it
as the flight ID of the passenger.
And it knows how to add that passenger to the database,
if I had db.session.add.
And it commits those changes for us.
So this line alone is all we need to actually do
in order to add that new passenger to the flight.
So if we try it out, go to Flask run, go back to this page.
Let's make sure-- oh, go into Airline3 first, Flask run.
And I try and register Bob for the flight from Paris to New York.
I've now booked that flight.
If I now go into flights and click on Paris to New York,
Bob now appears, because I called that Add Passenger
method from application.py.
Questions about how or why that worked?
OK, let's look at one more particularly powerful and compelling feature
of ORMs.
And then we'll take a short break.
And this is going to be the concept of relationships.
And so, relationships in SQLAlchemy are going
to be an easy way of taking one table and relating it
to another table to give them some way of referring to each other.
So what is that going to look like?
Let's go into the Airline4 and take a look at models
and take a look at this Flight class.
Everything about this is the same as the flight class that we just saw,
with one key exception.
In addition to an ID, origin, destination, and duration,
I've added a property to my flight class called Passengers.
Now, Passengers is not a column on my Flights table.
Recall, my Flights table has an Origin column, a Destination column,
a Duration column, and an ID column that keeps track of what number flight
it is, but nothing else.
This Passenger, as a property of my flight,
is just something that exists inside of my Python code.
And in particular, I'm defining it to be a relationship, a relationship that
is going to connect multiple tables together.
And in this case, that relationship is going
to be a relationship between the Flight table and the Passenger table.
And what this is going to do is give me a way such that,
if I have a flight object, I can use this passenger's property
to extract all of the passengers that are on that particular flight.
Backref=Flight is going to do something else.
And it's going to give me a relationship in the opposite direction.
In particular, if I have a passenger-- and let's call that passenger P,
for example--
and I want to access the flight that that passenger is associated with,
rather than say, as we would have needed to before,
p.flight_id is the ID of the flight the passenger is associated with--
so if I wanted to extract that flight, I would need to do syntax like
Flight.query.get(P.FlightID), which is the ID of the flight.
Instead, I'm just going to be able to use
the keyword Flight to access the flight information for that passenger.
And then finally, this lazy=true says that I want this to lazy evaluate.
In other words, if I don't ever try to access the passengers of a flight,
there is no reason to waste time and operation on the database
by trying to actually extract all those passengers from the database.
Lazy=true says, be lazy about it.
This passenger's property exists.
But don't worry about fetching that information, unless I actually need it.
When I try to use the passenger as property of a flight, then-- yeah-- now
I need it.
Now, go ahead and extract it from the database.
But until I do, don't worry about trying to get access to that information.
So that's just a little efficiency bonus,
in case we don't need to use that property.
So how is this useful?
What does this allow us to do?
Well, let's take a look.
Recall that before, inside of Airline3 inside of application.py,
when we were trying to extract all of the passenger information
for an individual flight, to list out all the passengers on that flight,
the line looked something like this--
passengers=Passenger.query.filter by(flight id=flight id).all.
I was extracting all of the passengers that had this particular flight ID.
And I was getting all of those passengers.
So how might I do this now that I have relationships?
Well, inside of Airline4's application.py,
the line looks like this.
On line 54, when I want to get all the passengers, all I need to say
is, I have this flight already, this flight object,
which I extracted from the database.
And now, Passengers is just going to be equal to flight.passengers.
I added that passenger as property to my Flights table.
And so now, just by using that dot notation,
I can extract out all of the passengers that are on that flight
and then pass it into the template.
So that helps to allow me to be much more concise with my queries.
Rather than that long thing I had before,
I can just say flight.passengers to get me all of the passengers that
were on that flight.
So let's see an example of that in action.
If I go back to this main URL and I want to register someone on the flight from
Paris to New York-- we'll say, Chad--
go ahead and book that flight.
And now, when I go to the Flights list, when I click Paris to New York,
that is going to access that passenger's property of the flight that we created.
And now, I see my three passengers listed in my passenger list.
And that was all because that relationship
allowed us to simplify the syntax.
So ultimately, what SQLAlchemy is going to be valuable
for is making it easier to do more complicated things with your database.
You're probably finding, as you're working through project 1,
that there's a lot of queries involved, a lot of selecting, a lot of making
sure you're selecting where certain conditions are true,
a lot of looking at relationships between different tables potentially.
And what SQLAlchemy is good at is making it easier
to relate those tables together, making it easier to extract information
from related tables and so forth.
Questions about any of that so far?
OK, we'll take a short break.
And when we come back, we'll dive into taking a look at APIs.
OK, welcome back.
I thought we'd wrap up our discussion about ORMs and then dive
into talking about APIs and how to use them and how to build them.
But I just want to do a quick wrap-up on relationships
and what that, ultimately, is going to mean,
in terms of how you can make your syntax a little bit easier.
And so, recall that before, when we had syntax like Select star from
passengers, where flight ID equals 1, if we have a particular flight,
like flight.query.get(1), using relationships now--
using that passenger as property that we've added to our Flight class--
we can now get at those individual passengers just by saying
flight.query.get(1).passengers to simplify the process of extracting
the passengers.
And that becomes all the more compelling,
if you look at syntax like this, right?
This was if I wanted to select Select star from flights join passengers
on flights that ID equals passengers.flight_id where
passengers.name equals Alice, where I'm joining the flights and passengers
together, getting Alice--
wherever Alice's name is in the passengers--
and getting whatever flight that Alice is on, for example.
If I wanted to simplify that query using SQLAlchemy,
it might look something like this--
passenger.query.filter_by(name=Alice).
This first line is just saying, query my Passengers table.
Get me a passenger whose name is Alice.
And just get the first one, assuming there's only one Alice in our table.
And then .flight will get me all of that flight information such that I can then
access wherever Alice is coming from, where she's going to, and how long her
flight is.
And so these relationships can definitely
help to simplify that process, using the technology built into ORMs.
Just to answer a question a couple of people have given me--
for project 1, the one about the books, we're
going to ask you actually not to use the ORM-like syntax
and just to stick to using the raw SQL.
Because there's a lot of value in understanding these Select and Insert
and Update queries and being able to use them.
But from here on out, starting in project 2 and going
forward, if we're interacting with databases,
feel free to use this syntax as well, because it can often
be more concise, more efficient, to be able to interact
with classes in the way that we have been in the examples
that we've been looking at so far.
But now, I wanted to take a moment to transition
to talking about our next topic, which is going to be APIs or Application
Programming Interfaces, which you can think of as, effectively, protocols
for communication between either different web applications
or different parts of the same web application,
where oftentimes different components of applications or different applications
altogether will want to be able to share information with each other
or will want to be able to perform actions in other spaces.
And APIs offer us a way to allow different components of web
applications to be able to communicate with each other.
And so, in order to do this, we will often
want to have a standard language for how different parts of our web application
will communicate with each other.
And so, in particular, the language we're going to look at here
is called JSON, or JavaScript Object Notation, which
is just a simple way of representing information
in a way that is both human readable and computer readable, such that it will be
easy to use JSON information to pass data from one part of one application
to another part of another application, for example.
So if I wanted to represent information about a particular flight,
using JSON notation, it might look something like this.
We've wrapped it in curly braces to enclose everything
in what's called a JSON object.
And I have divided the contents of this JSON object
into what are called Key Value pairs.
So I'm defining information about this flight
by specifying what its origin is.
The origin is Tokyo.
The destination is Shanghai.
And the duration is 185.
So this sort of notation is easy for us to read.
But in particular, it's also easy for a computer to read.
And why is that important?
What might we then want to do with an object like this?
Any ideas?
Yeah?
AUDIENCE: You could render it in your browser [INAUDIBLE]..
BRIAN YU: You could render it under browser-- exactly.
So other web applications, if they are able to receive data in this format,
in some standardized format, where they know to expect a JSON object that
will have an origin and a destination and duration,
those applications can take that data and do something meaningful or useful
with it-- extracts the origin and display the origin in a browser,
or do other useful tasks, using the data that's provided to us in JSON.
And JSON also supports the ability to nest within it other containers
or collections of other items.
So maybe, in our JSON object for representing what a flight is,
we might also want to represent the passengers in the flight
and who is on the flight.
And so that might look something like this
where, in addition to having an origin and a destination and duration,
I also have this list of passengers that has a name Alice and a name Bob
inside of a list, so that now, when I try and get
information about the flight, if my Application Programming
Interface, my API, gives back a JSON object that looks something like this,
now whatever computer is receiving that information
can process this list of passengers.
What if I wanted to add more sophisticated information?
What if I wanted to, in addition to having the origin being Tokyo,
I want to also specify, OK, the origin is Tokyo,
but I also want to give you that three-letter airline
code that corresponds to this particular Tokyo airport?
What might be a good way to do that, using JSON?
Using this notation of trying to represent information
using keys and values like this?
What would be a way to represent that information if I wanted to,
in addition to this information, include information
about Tokyo's airline airport code and Shanghai's airport code?
AUDIENCE: You could have two more key value pairs.
BRIAN YU: Yeah, exactly.
You could have two more key value pairs.
I could add in origin, airport code key that is whatever Tokyo's airport
code is, and a destination airport code that
is going to be whatever Shanghai's airport code is.
And that would certainly work.
And it would be a way to represent information in JSON format.
And if I was communicating with an application
and I knew that that was going to be the format that it would be in,
then I would be able to extract that information out of the application.
But one common paradigm you'll see in JSON notation
is the idea of actually taking these objects
and nesting them within each other, just to give
some sort of organization and hierarchy to the way this data is structured.
And so you might see something that looks something more like this,
which looks a little more complicated.
But really, all this is is the origin, now,
is itself a JSON object that contains a city and a code.
And likewise, the destination is itself a JSON object
that contains a city and a code.
The duration is still an integer.
The Passengers list is still a list of names.
And so, by nesting information in JSON objects
as the values of other keys inside of my JSON object,
you can begin to represent more complex--
more sophisticated information in a really organized way.
And now, this becomes easy for a computer to be able to read.
Because if the computer wants to get at, what
is the airport code of the destination of this flight, it just needs to know,
go to the destination.
And inside of that, go to the code.
And likewise, for the origin, go to the origin and then go to the code.
And you'll find that programming languages like Python and JavaScript
have very easy ways of interacting with objects like this
and extracting out information-- extracting the values from this object
in a way that's useful and meaningful.
So how are we going to do that?
How are we going to take our web application's API and interact
with, either, our own web application API or some other one that
exists elsewhere on the internet?
Well, oftentimes, it's going to happen through the URL
where, inside the URL of the web page, we're
going to specify what particular information we want to access.
So for instance-- these should be flights--
going to /flights, for instance, would give you a listing, in the API format,
of all of the flights, for instance.
And going to /flight/28, would give you an API JSON response of information
about the flight with ID number 28.
And if I wanted to get at the passengers on flight 28,
I might go to /flight/28/passengers to get the information about those
passengers.
And likewise, if I knew that a particular passenger had ID number 6
and I wanted to get back a JSON object with information about them,
I might go to /flight/28/passengers/6.
And this URL syntax might vary from one application to another.
But it's fairly conventional to use this nested URL structure
to describe different endpoints, so to speak,
different URLs that you can access in order
to get at particular types of information inside of the API.
So questions about any of that so far, before we
move on to some of the more implementation
details of how these things work?
And we'll see an example of this also.
OK, one other useful thing to note is that, when dealing with APIs,
we'll often want to do different things with our API.
Maybe we want to have a way in our API to say,
get me information about passenger number 28--
so just extracting information.
We might also want a way to say, by an API,
I want to register a new passenger for a particular flight
such that, instead of needing to go to a website
and type in a name and a flight, I could just programmatically,
using a Python program, register for a flight
by using a flight's API that we would build out, for instance.
And so, maybe, I want to add information using the API as well.
I might also want to update information, like,
maybe if I want to change my registration information for a flight.
And so, oftentimes when dealing with APIs,
the HTTP request method that we use will correspond to the type of action
that we want to perform.
And this is just a convention that many APIs follow.
It's not that this is the only way to do things.
It's just that this happens to be a particularly common conventional way.
We've already seen Get and Post requests before,
in the context of Flask applications, where usually a Get request means,
I want to access a page, and a Post request
was when I was submitting data to a page.
These are some common HTTP requests methods that are frequently
used when dealing with APIs.
Get is usually the request method we use when
we want to retrieve a resource, where a resources is just
information about a flight, for instance, or information
about a passenger.
Post is typically the request method we use
when we want to create a new resource, register for a new flight,
create a new passenger.
Put is generally if we want to replace a resource.
Patch-- for updating an existing resource.
And Delete-- for deleting a resource.
And these are all just different HTTP request methods, different ways
of making requests to some web server.
So how do you actually make those requests programmatically?
We have been able to make Get requests already just by going to a browser.
Any time you go to a browser, type in a URL, and press Return,
you are making a Get request to that website.
But how do we do that in a program?
Inside of Python, for example?
Well, to do that, we'll take advantage of a library
in Python known as the Requests library.
And what the Requests library does-- this is the library written in Python
that will make it easy for us to make all of these various different HTTP
requests to different web servers.
So let's take a look at the Requests library in action.
So we'll first take a look at Google.py.
And so this is just a very simple program that is going to--
after we've imported the Requests module into Python--
inside the main function, say res-- which just stands for response.
Our response is going to be a variable equal to request.get.
Requests.get is a function that is going to take, as its input, a URL--
just some URL.
It's going to make a Get request to that URL
and then get me back the HTTP response.
So effectively, think of this as Python's equivalent
of typing in HTTPS://google.com, pressing Return, and giving you back
whatever that response is.
So that response is saved in a variable called res-- a response.
And we're printing out response.text--
so just the text of whatever came back from making a Get request
to google.com.
What do we think that's going to look like?
What's going to happen when we run this program?
What will we see?
Yeah?
AUDIENCE: I think you would see exactly what was on Google.com.
BRIAN YU: Yeah, we'll see exactly what's on google.com.
In particular, we'll see whatever we would get back
when we make a request to google.com.
Now of course, this doesn't look much like Google.come
as it sounds right now.
But this is just the HTML contents of whatever is on google.com.
And our web browsers, like Safari or Chrome, know how to take this HTML
and then actually render it.
So if we look around, we can probably find, like, a--
yeah.
So here, somewhere in the HTML is the I'm Feeling Lucky button on Google.
And our web browsers know how to take all this information and render it.
So using just this code, we've been able to make a Get request
to a website-- in this case, google.com--
and we've been able to print out the text of that response
by using response.text, to be able to access that information.
So how is that going to be useful to us?
Well, in particular, request.get is not the only request method
that we can use.
Just as we had request.get to make a Get request,
we can make all sorts of different HTTP requests
just by using the various functions that are built into the request module.
So we can do request.post to make a Post request;
request.put to make a Put request; and then a Patch request and a Delete
request respectively, so that all of those things we wanted to do with
APIs--
if there's a way to do them using a particular request method,
there is a way to do that using requests in Python.
So let's try and use this to actually take advantage of an existing API
and do something interesting with it.
So one API will take a look at is going to be fixer.io.
This is a API that already exists elsewhere on the internet.
I had nothing to do with it.
And we're just going to try and use it to do something useful-- to write
a program that takes advantage of it.
What Fixer is is it's a foreign exchange rate API.
So it's going to, every day, have stored somewhere the latest exchange
rates for currency.
And it offers an API that makes it easy for us to access those exchange rates
and do something useful with it, in a format that's readable by a computer.
So if I were to read through this usage section,
I would notice that, in order to use this API, I have to go to API.fixer.io.
And then I could, say, specify a base currency and then other symbols.
So if I go to, for instance, API.fixer.io/latest?base=USD,
for US Dollars, &symbols=--
we'll do GBP for British pounds.
And I'll press Return on that URL here.
So I'm going to that particular API.
And the only reason I know what to put in the URL
is because I looked at the documentation on the other page
to say, what should I put in the URL to get back the result that I want?
So if I type that into the URL and press Return,
then this is the response that I get back.
I get back, ultimately, a JSON object.
It's not formatted as prettily as the ones
that we saw in the slides a moment ago.
But it's just surrounded in curly braces.
And it's got a bunch of keys and values.
It's got the base, which is US dollars; the date that this was extracted on--
February 26-- and then rates, which is, inside of it,
another JSON object that contains British pounds;
and then 0.71282, which is the current exchange rate
between US dollars and British pounds.
And so this is not super nice for a human to look at.
We can read it and parse out what the stuff means.
But because this is in JavaScript Object Notation--
JSON-- format, this is now very easy for a computer then
to be able to understand and manipulate.
So what would that look like?
And how would that work?
How could we write a computer program in Python
to be able to take advantage of the fact that we have this API elsewhere
on the internet that gives us access to currency exchange rates?
Let's take a look now at currency0.py.
What's going on in here?
So inside of our main function, after we import request,
the first thing we do is call request.get and then this URL--
api.fixer.io-- latest base is USD.
Symbol is the euro, in this case.
So basically, the same URL syntax that I used before.
Because I know now how fixer.io's API works.
And I'm saying, if the status code that came back from this request is not 200,
raise an exception.
Raising an exception in Python is, basically, a way in Python to say,
something bad happened.
Quit the program.
There should be some sort of error at this point.
And so I'm saying, Error, there was some problem with the API request,
if it didn't successfully return as 200.
But then, what's going on here on line 7, is I'm getting the response.json.
And this .json method is a way of taking the result of a request and extracting
that JSON data-- that JavaScript Object Notation data--
and save it inside of a variable called Data.
And now, I'm going to print out that data.
So what's going to happen when I try and run this program?
I'm going to run Python currency0.py.
And when I get back, printed to my screen,
is that same JavaScript object that I saw in the web browser a moment ago,
except this time with euros, where I have the base currency as US
dollar, the date, and the currency exchange
rate between the dollar and the euro.
Questions about how that worked?
Before we move on, I'll do a brief note about status codes.
So we saw status code 200 just now, which
meant that everything worked out fine.
If the status code from an HTTP response is the number 200,
that generally means things are OK.
And every status code has a slightly different meaning.
And some common ones you'll see an APIs would be-- so 200 OK
just means the request was successful.
So very often, you'll see, when you're writing code that uses APIs,
you may want to check, was the response a 200?
And if it was, then probably all was fine.
But there are also other response codes that are used for various purposes.
201 is also a success-related response code
that generally means something was just created.
So if I made an API request to create a new passenger on a flight,
for instance, the response might conventionally be a 201,
meaning, that new passenger was created successfully.
So 2 in the hundredths place generally stands for a successful request.
And a 4 in the hundreds place generally stands for some sort of error.
400 generally means it was a bad request.
It was ill-formatted or there was something about the request
that you made that we couldn't really understand.
403 means forbidden.
If you're trying to access a resource that,
maybe, you need special permission to access.
You need to be logged in or something to access.
You might get that.
404 Not Found-- it's frequently happening
whenever you make an API request for something that doesn't exist.
So if I request flight number 28, but I only have 25 flights in my database,
well there is no flight 28.
And so I'll very frequently get a 404 response from that.
405-- method not allowed.
Any ideas what that one might be?
AUDIENCE: Get and Post [INAUDIBLE].
BRIAN YU: Yeah, so Get and Post were the ideas of the request methods
we had before.
And so, if I have an API route that only accepts Get requests,
for instance, if I try and make a Post request to it,
I might very well get a 405 error, Method not Allowed.
Because you can't make a Post request if the API is only accepting
Get requests, for instance.
And 422, Unprocessable Entity is slightly less common,
but is often used if there is something about the API request
that we were able to understand the API request,
but you still had a bad request in some way.
Maybe in the currency example, for instance,
I requested a currency symbol that doesn't exist.
That might be an example of a time when I might get back a 422.
And there are other codes as well, but just wanted
to give you a sense for what common success and error codes actually
look like.
But going back now to the currency example
where we had this code that was going to print out the result of the API
request, and print out that data in JSON format, what could be
improved about this program right now?
Just from a user interface perspective, what's not so great about the fact that
I ran Python currency0.py and this is what comes back to me?--
from the perspective of someone using the application.
Yeah?
AUDIENCE: You can parse out that information that printed line-by-line.
BRIAN YU: Yeah, the part the user really cares about is this part, right?
So this is the exchange rate that I want to display.
And it's sort of annoying for the user to have to look at this information
and understand what the keys and values are.
That really should be the job of the computer.
So let's take a look now at currency1.py and see how we did this.
And recall that this, right here, is what the response looks like.
So if we go into the rates key, we get back this object,
where the key of that object is the currency code,
and the value of that object is whatever the exchange rate actually is.
So what we can do now, inside of currency1.py, is do something similar.
Everything is the same up until this line where we extract the data in JSON
format.
But the nice thing about the JSON format is that it's very machine-readable.
It's very easy to tell a machine to access specific information
from that API response.
So I can now have a line like rate=data[rates--
because rates was that key that I wanted.
And then the euro was the one that I wanted to look up.
Save that inside of a variable called Rate.
I was just extracting information from the JSON object in this line.
And now, on line 9, I can print out one, US dollar is equal to--
plug-in the rate here--
euros.
So now, when I run this program, rather than just see
that JSON object printed out to the screen, which
wasn't so great, I can run Python currency1.py and get, $1 US
is equal to 0.81169 euros.
And the great thing about this is that my program doesn't need to change.
Tomorrow, if I run this again, it's going
to be the new latest exchange rate for between the US dollar and the euro.
And I didn't need to worry about, how do you calculate the exchange rate?
Where are we getting the exchange rate's information from?
As it turns out, this API is getting that info
from the European Central Bank.
But I don't need to know that.
I just need to know what request to make in order to get at that information
and let the API handle the rest.
And then I can use that information and meaningfully
represent it in the output.
Questions about how that worked and how we're able to extract information out
of the JSON object just them?
AUDIENCE: I have a different question on this [INAUDIBLE]..
BRIAN YU: About the--
AUDIENCE: About, like, how do I know that I'm going to [INAUDIBLE] out?
And how about HTML or XML objects?
BRIAN YU: Great question.
How do I know, is the question, that I'm going to get back
the JSON object as a response?
What if I just got back an HTML page, like we saw in Google,
or an XML, which is a different file format for representing data?
Ultimately, it depends upon the API's documentation.
So generally, when an API is made public on the internet, whoever
provided it will provide instructions for how to use it,
describing-- here are the routes you can access.
Here are the request methods you should use in order to access them,
like Get or Post or Put or Patch.
And they will also describe what the response looks like,
what the keys are in the response, so that you know,
is the response going to be a JSON format?
If it is, what are the keys and values?
And if you can reliably know that that API won't change,
then you're able to use that information.
So we only know that it's a JSON response because the website tells us
it's a JSON response and because we've experimented with it.
AUDIENCE: [INAUDIBLE] in order to support the XML, JSON,
and any other format?
Like, I am going to use the same quote, simple JAVA.
Site might be supporting JSON or XML or anything else.
And--
BRIAN YU: Great question.
So--
AUDIENCE: [INAUDIBLE]
BRIAN YU: Yeah.
So the question is, can the request modules
support things that are not JSON?
The answer is yes.
If you just use the .text, like we saw before, you can get the raw text.
And then you can parse that in any way.
And there are ways, in Python, to be able to parse XML strings,
for instance, and so on and so forth, with other file formats.
And so you can definitely do that as well.
Anyone have ideas for things we might want
to do to improve this currency converter program?
To make it a little more interesting and more useful?
AUDIENCE: Put it on a website.
BRIAN YU: You might want to put it on a website-- certainly.
And so, on a website, you would be able to do the same thing.
Using this Request library, if you were writing this in a Flask application,
we might be able to put it into a website,
allow you to type in a currency, type in the currency you want to convert it to,
and just convert it.
And that would allow us to do that conversion.
We'll take a look at a website in just a moment, when we make the return back
to our own Flights program.
For now, let's just do this on the Command line though, and look at,
how might we allow us to convert any two currencies, instead of just US
dollars and euros?
So let's take a look at currency2.py, which is a little more complicated
but, ultimately, is based on the same general ideas.
So the first thing we're doing here on line 4
is asking for input from the user.
Type in the first currency.
Save that as the base.
Type in the other currency, the second currency.
Save that as Other.
Then use request.get.
And in particular, I'm going to api.fixer.io/latest.
But this time, the base and symbol information
that I provided as arguments or parameters to the URL last time--
I don't yet know what those are going to be.
And so, I'm adding this additional argument, Params,
to request.get, which lets me specify additional information
to parameterize this URL in the Get request-- to pass in the information
that I want to pass into the Get request.
And likewise, with request.post and a bunch of others,
you can pass in, like, a data attribute, to pass
in information data about that request.
But in this case, I'm saying, go to the fixer.io API.
Pass in the Base as the base currency and Other as the symbol
that I want to extract.
And I only know that these are called Base and Symbols
because, when I went to the API website, it told me, call your parameters base,
and call your parameters symbols.
I make that Get request.
If the status code response is not 200, raise the exception.
Then extract the JSON data on line 10.
On line 11, I want to go into that JSON object, extract the rates information.
And the name of the key is just going to be the name of that currency--
so Other in this case, which is whatever the second currency was--
and then print out one of this-- base currency
is equal to whatever the exchange rate is of the other currency, for instance.
And so now I can use this to begin to convert between any currencies.
If I run currency2.py and I type in US dollars as my first currency
and Japanese yen as my second currency, for example,
I get $1 US is equal to 106.82 Japanese yen.
And so I can start to build functionality on top of this API
and use APIs to enhance my own existing applications.
And that's ultimately what APIs are going to be useful for us
for in the upcoming project where, in project 1,
you'll use the API of a book review website to be able to say,
there are all these reviews for books out there on the internet.
Let's extract some of them and get that rating information
so that we can display it on our own website,
using APIs built by others in order to add to or enhance our own website.
Questions about how we can use APIs written by others?
OK, so what we'll move on to now is our final point,
which is going to be about how to design our own APIs.
So this might be useful, for instance, in our Airline application,
if we wanted to allow for ways for other people
to be able to write APIs that interacted with our Flight website.
Maybe you want to allow your Flight website
to be accessed by APIs so that other people can develop
phone applications that are able to talk to the Flight website
and extract information about flights or build other websites that
are able to talk to our website and get information about flight data.
And that's only possible if there exists some APIs, some protocol, that
allows for multiple different applications to talk to each other,
using some shared language, using some JSON object that follows
some predefined standard format.
So how might we go about doing that inside of our web application?
Well, let's take a look at an example.
So I'll go into Airline5.
And so, inside of application.py, this is very much
the same as what we've already seen before.
But the only difference that we're going to add
is we're going to add another route at the bottom of this file, which
I've just called Flight API for now.
And how is this working?
Well, this Flight API route inside of our application.py file inside
of our web application is going to be the route /api/flights/ some integer.
So I'm just going to build one API route for now that
is going to let us extract information about a particular flight.
In particular, if you go to the URL of my website, /api/flights/28,
for instance, you will get back information about flight number 28,
according to my API.
And I would probably document this if I wanted
to make it available to the public for them to be able to use.
So what happens when I go to this API route
and I pass in a particular flight ID?
Well, what might happen is, first, I want
to make sure the flight actually exists.
So flight=Flight.query.get(Flight_id), just trying to extract it from
the database using SQLAlchemy, like we saw before.
If Flight is None-- in other words, if there is no flight that got returned
from flight.query.get--
what I'm going to return is a JavaScript Object Notation, JSON,
object that I'm going to wrap inside of this JSONify function.
And what this is going to do is take a Python dictionary,
the things surrounded in curly braces, and convert it into a JSON object
and put all the required HTTP headers on it
that will let it go back as a successful JSON response.
This is built into Flask.
You can do, from Flask, import JSONify, to import this function.
And that will allow you to do this thing.
And so what I'm going to do here is, if there is no flight,
I'm going to return a JSON object that says, Error is the key.
And the value of that key is just going to be Invalid Flight ID--
some description of what went wrong.
And then, when I return it, I'm also going to-- as Flask lets me do--
pass back an Error code.
We haven't done this before when we've returned things in our applications
because, by default, when Flask returns something,
it's just going to return status code 200, meaning everything was OK.
But if I want to pass back a different status code,
I can pass it back as just ,422, for instance.
And so here, we're passing back this object that says,
Error Invalid Flight ID, with status code
422, which means there was some problem with the response.
We understood it, but there wasn't anything there.
You might also pass back a 404, like, Not Found.
We couldn't find the flight.
So different APIs follow different conventions.
And it's just good to know, in the documentation for an API,
to understand what a particular API is doing.
So once the flight does exist, what can we actually do with it?
We can now get all the passengers.
Recall that, due to that relationship syntax that we saw in our classes
before, to get at the passengers for a flight,
we just need to do flight.passengers and save it
inside of a variable called Passengers.
And now, let's maintain a list of the names
of all the people inside of our flight.
We can loop over every passenger in my list of passengers
and add to this list of names whatever the passenger's name is.
And then, finally, at the bottom here, what we're going to do
is return another JSON object that has an origin, that has a destination,
has a duration, and a list of passengers.
And so, by doing this, and with no status code specified--
that's, by default, just going to be 200--
we have now built an API route that is going to return information
about a flight in JSON format, in a way that is now
machine-readable by some other person writing some other application that's
going to talk to or communicate with our own API.
So let's try it out.
And we'll do Flask run.
And we started up the application.
If I just go to my URL /flight/1, this is what that page looked like.
Recall, it just had details, origin, destination, duration,
and a list of passengers.
And technically, this has all the same information that my API had.
But why then might we still want an API?
Even if we already have this page that we already built before?
Yeah?
AUDIENCE: It's machine-readable.
BRIAN YU: It's machine-readable-- exactly.
This would be possible, but likely more difficult,
for a machine to be able to parse and extract the origin and the destination
and the list of passengers.
But if I go into the URL now and change /flight/1 to add just /api/flight/1,
because that was the route that I decided on before,
now what I get back is something that looks a little less aesthetically
pleasing to the human but definitely more machine-readable.
I have this JSON object that has the destination.
It has the duration, the origin, and a list of all of the passengers
that are on this flight.
And so I've now built this API route that I can use.
And if I go into Python, I can use the Request library
to be able to extract this data, for instance.
So if I just open up Python and say, import the Request library--
and let's do response=request.get, this local URL /api/flight/1,
now I can do response.json.
And I get back this object that contains within it all the relevant information.
So if I save it inside of a variable called Data,
now I can do data, passengers.
And I get back a listing of all of the passengers that are on the flight.
Because this is in a machine-readable format, that becomes much easier.
If I try to access not flight/1 but flight/100 when there is no flight/100,
what's going to happen?
What I get back is this JSON object with Error, Invalid Flight ID,
which is exactly what I expected to happen, because inside of my API route,
when I had an application.py--
just make sure the flight actually exists--
if no flight got returned, I was returning this JSON object
that said Invalid Flight ID.
And so even this is still relatively machine-readable,
because if I want to extract the error message,
I just go inside the JSON object, get at what the error is.
And now I have a string that represents what the problem is,
what went wrong in that process.
Yep?
AUDIENCE: So you get the status code from that [INAUDIBLE]??
BRIAN YU: Yep, I do, res.status_code.
And then I'll give him back the status code that came back
from that particular response.
Good question.
All right, so that was building out our own API.
And so one thing that you'll do, when you
move into working on your own project, is you'll build an API of your own,
some way for people to be able to access the rating data that people have
submitted into your own application.
And as APIs get more complicated, there are other things
that might get involved in well.
I'll just talk briefly about API keys.
You'll often see that, with larger APIs, you'll
want to do what's called Rate Limiting.
You don't want anyone just make as many requests as you want to an API,
because it might overload the API.
And it might make it harder for other people to be able to access it.
And so oftentimes, what will happen is that APIs
will restrict access to an API.
You'll have to first get an API key, which
will just be a long string typically.
And any time you make an API request, you'll
need to provide, in that API request, your API key to the API request.
So when you deal with project 1 and use the API for books,
you'll need to do something to this effect, in order to create an API key
and use it in order to make requests.
And this is often used just to track, what API routes are people going to?
Who's going to them?
And it also lets you limit people.
So if you want to say, nobody can make more than 100 API requests per hour,
then you can enforce a limit like that by just keeping track.
Every time you get a request from a particular API key,
you can make a tally inside of a database somewhere of,
they just made an API key usage.
And that's one that's been cut from their total allocation
for that particular hour.
You don't need to worry about that too much for this project.
No need to do any rate-limiting of your own in the API that you build.
But know that, as you go exploring other APIs that are out there--
and there are APIs for so many things nowadays--
that many of them will have API keys that you'll
need to first register for and then use when you go about making requests.
But questions about anything about APIs?
Or ORMs?
Or SQL, in general?
Or anything that we've talked about so far today?
Yeah?
AUDIENCE: In general, when do we use regular database and JSON?
It seems to me that they are pretty similar to different things.
BRIAN YU: Great question.
So the question is, when would you use the database, as opposed
to using JSON, this object notation that we saw?
Oftentimes, they'll go very hand-in-hand.
In our Flights example, for instance, we have
our database that has a Flights table and a Passengers table.
But we don't want to let anyone in the world
just access those tables directly, because they might delete things
that we don't want them to delete or look at sensitive information
we don't want them to look at.
And so oftentimes, access to the database is controlled through the API
so that, if you want to get information about flight number 1,
you can't just ask the database for information about flight number 1.
You use the API to say go to /api/flight/1.
And then you get back a JSON object representing the information
about flight number 1.
But that data ultimately came from the database.
It's just that the application stands in the way of making sure
that the person making the request is only getting access to the things
that we want them to be able to access.
And so, very often, JSON object data and our database
will go together very frequently.
And you'll see that as you work on project 1.
All right, so we'll wrap up here for today.
And next week, we'll dive into a brand new programming language--
JavaScript-- and explore how we can use that to make
our front-end websites even more interactive and more interesting.
Thank you.