[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.
