back to index

FastHTML advapp.py walkthru - - Answer.AI dev chat #6


Whisper Transcript | Transcript Only Page

00:00:00.000 | [AUDIO OUT]
00:00:05.360 | OK, hello.
00:00:06.320 | I'm Jeremy from Answer.ai.
00:00:08.360 | And Hamel, who as of today--
00:00:11.880 | also, where are you from, Hamel?
00:00:15.880 | Where am I from?
00:00:17.880 | I'm from Answer.ai.
00:00:18.920 | Where are you from as of today?
00:00:20.840 | Oh, Answer.ai, yes.
00:00:22.080 | Oh, there you go.
00:00:23.480 | Nice.
00:00:23.980 | Yeah.
00:00:27.760 | And since you just started today,
00:00:31.120 | I am going to help you understand first HTML.
00:00:36.160 | Is that the plan?
00:00:37.760 | That's great.
00:00:39.560 | And specifically, we're going to look
00:00:41.560 | at the idiomatic CRUD app, advapp.py.
00:00:52.760 | What's special about this app is that it's really
00:01:00.880 | designed to go through everything
00:01:05.520 | you might come across in a normal CRUD database app.
00:01:12.880 | So if you understand every line of code here,
00:01:17.680 | then nothing should be too mysterious
00:01:20.880 | for this style of application.
00:01:22.740 | So maybe let's start by running it.
00:01:30.000 | So when you run a fast HTML application,
00:01:36.680 | you just run Python and the name of the application.
00:01:39.080 | And then it says link.
00:01:40.360 | And it pops up a link.
00:01:41.600 | So you can click on that.
00:01:44.720 | And so we've got a username and password.
00:01:52.000 | And so we've got here a application
00:01:55.000 | where I can add a to-do.
00:02:00.240 | I can edit the to-do.
00:02:01.520 | You can view it.
00:02:09.120 | You can see it's using markdown.
00:02:13.360 | You can mark it done.
00:02:16.480 | You can drag it to be a different priority.
00:02:20.400 | You can delete it.
00:02:23.440 | You can log out.
00:02:29.080 | OK, so that's the app, right?
00:02:30.680 | So it covers quite a lot of behavior.
00:02:34.960 | So I recommend, ideally, Cloud projects
00:02:46.520 | are the best for this, creating a Cloud project in which
00:02:49.120 | you grab the llms-ctx.txt file and dump it into this project.
00:02:59.080 | You don't have to remember that.
00:03:04.200 | It's here, linked to it.
00:03:09.720 | And you can basically ask anything at that point.
00:03:11.920 | So can you give me a basic skeleton fastHTML app
00:03:18.920 | to get started with?
00:03:21.960 | That's a bit more than a skeleton, but that's cool.
00:03:32.120 | Yeah, so that can make life a lot easier.
00:03:39.400 | And one of the inputs to that context file
00:03:44.240 | is actually the application we're looking at.
00:03:48.280 | OK, so we follow the same pattern
00:03:54.760 | that, actually, the fastHTML standard library itself
00:03:57.560 | does for UI toolkits, which is just to import star.
00:04:01.840 | That's actually what the official Python docs
00:04:04.040 | do for the Python TK module.
00:04:06.880 | And we've been pretty careful to make sure
00:04:08.640 | that only the stuff you actually need are going to appear.
00:04:13.000 | You can also import everything manually
00:04:15.800 | if you really wanted to.
00:04:16.960 | So there they all are.
00:04:19.920 | OK, so a to-do list needs to store the to-do somewhere.
00:04:28.280 | We use SQLite and--
00:04:32.400 | What is compareDigest?
00:04:35.840 | We haven't got to that bit yet.
00:04:37.920 | So I'll show you when we get there, if that's OK.
00:04:42.600 | So for SQLite, we do most things using this tiny little library
00:04:47.720 | called fastLite, which is a thin wrapper over really
00:04:50.760 | most of the good work.
00:04:51.800 | This is done by Simon Willison, who created something
00:04:53.960 | called SQLiteUtils.
00:04:56.400 | But basically, it kind of makes your database
00:04:59.640 | look a lot like a dictionary.
00:05:04.880 | So there's a few different ways to use it.
00:05:09.760 | Actually, there's a way I kind of prefer nowadays,
00:05:16.600 | which people might find a bit less weird.
00:05:18.720 | So maybe we'll use that.
00:05:20.800 | So fastHTMLExample is a good place
00:05:22.960 | to see lots of examples, as is gallery.fastHT.ml.
00:05:27.400 | Yeah, so-- and there's a to-dos here, as well.
00:05:45.120 | I kind of prefer this one.
00:05:52.080 | So basically, anyway, either way,
00:05:54.360 | you're going to have to say, this is where my database lives.
00:05:56.900 | So here it is.
00:06:00.100 | Here's where my database lives.
00:06:03.780 | And then-- so users contains name and password,
00:06:17.460 | and to-dos contains ID, title, done, name, details, parity.
00:06:25.820 | Cool.
00:06:27.540 | So by default-- so basically, you say,
00:06:30.860 | OK, this is the definition of my user table.
00:06:34.100 | This is the definition of my to-do table.
00:06:36.940 | By default, the primary key is assumed to be called ID.
00:06:39.660 | So if you want something else, you just have to say--
00:06:42.820 | does that make sense so far?
00:06:46.420 | OK, user-- OK, so ID is always the primary key,
00:06:53.140 | unless you say otherwise.
00:06:56.700 | User table doesn't have an ID field.
00:07:07.220 | That's right, so it says PK equals name.
00:07:09.580 | OK, I see.
00:07:13.740 | All right, so to basically get started,
00:07:24.280 | you need a fastHTML object.
00:07:29.620 | There's two ways to create it.
00:07:31.020 | One is with fast_app.
00:07:33.660 | One is called fastHTML.
00:07:37.380 | It can be quite helpful to just look at the code
00:07:41.660 | to see the difference.
00:07:43.300 | So basically, fast_app, all it does is--
00:07:55.020 | this is actually fastHTML.
00:07:56.300 | So it calls fastHTML, just passes everything across to it,
00:08:02.060 | adds static routes, and returns.
00:08:07.140 | So they're basically the same thing.
00:08:08.700 | So we're going to use fastHTML.
00:08:10.740 | And all right, so--
00:08:19.420 | All right, I don't know if I followed the fastHTML
00:08:22.900 | versus fast_app.
00:08:24.620 | Yep, basically the same thing.
00:08:26.140 | fast_app's just three lines of code that calls fastHTML.
00:08:31.580 | All it does is--
00:08:32.820 | Why would you want to use fast_app?
00:08:35.460 | Because you want static routes, which you normally do.
00:08:38.420 | So if you want a .txt, or a favicon.ico, or whatever,
00:08:43.540 | you want your application to respond by serving up
00:08:47.300 | those files, or GIFs, or JPEGs.
00:08:51.420 | So basically, all web apps, pretty much,
00:08:53.180 | you want to serve up those files.
00:08:54.940 | OK, but you're using-- in this example,
00:09:02.300 | you're not using fast_app, using this?
00:09:06.700 | Yeah, so we kind of try to show everything
00:09:09.260 | manually for this example, just to show people--
00:09:12.380 | Oh, I see, OK.
00:09:13.500 | --everything, the kind of--
00:09:16.900 | yeah, otherwise, we wouldn't get people to show--
00:09:18.980 | have a reason to show people how to do static routes,
00:09:21.540 | for example.
00:09:22.860 | I see.
00:09:23.380 | We're doing things a bit more manually.
00:09:24.980 | So yeah, that's reason one.
00:09:26.340 | Reason two is it kind of gets the route for you.
00:09:29.900 | It saves two lines of code.
00:09:32.100 | I'm pretty lazy.
00:09:35.420 | Yeah.
00:09:37.380 | OK, so one thing that happens, then, in our to-do list
00:09:52.140 | is, if I try to go to slash, I'm not logged in.
00:09:56.260 | It needs to redirect to login.
00:09:58.420 | So you can basically specify that some code runs
00:10:04.860 | before every request.
00:10:06.460 | In this case, I want to run some code
00:10:08.140 | to make sure that they're logged in,
00:10:10.180 | and if they're not, to redirect to login.
00:10:13.980 | So code that runs before every request is called before_where.
00:10:19.020 | And so that's a class.
00:10:21.500 | And the thing you pass to before_where
00:10:23.340 | is one or more functions to call before your request runs.
00:10:30.660 | So here's our function.
00:10:32.540 | So it runs before the request runs.
00:10:37.860 | And so before_where and basically every other type
00:10:45.500 | of handler in FastHTML, you just write
00:10:49.900 | what you want to get access to in these parentheses here.
00:10:54.140 | And FastHTML gives it to you automatically,
00:10:57.660 | just kind of similar to how FastAPI works,
00:11:00.220 | although FastHTML does a lot more of these
00:11:02.740 | than FastAPI does.
00:11:04.940 | So there's a session provided for free.
00:11:08.140 | The session is just a signed cookie, basically,
00:11:12.260 | with a dictionary in it.
00:11:15.020 | So we want to see if somebody's logged in.
00:11:17.420 | So we're going to be saving whether they're logged in or not
00:11:20.340 | into a auth key in the session.
00:11:25.260 | So we basically say, OK, are they logged in?
00:11:28.980 | And if they're not, then we return redirect to slash login.
00:11:36.380 | Does that make sense?
00:11:38.140 | Yeah.
00:11:39.020 | Cool.
00:11:40.220 | Otherwise, this happens, which we'll leave for now.
00:11:42.700 | OK, so that's how come we can see the login screen.
00:11:45.500 | Do you say just to ignore the todos.extra right now?
00:11:52.420 | Yeah.
00:11:55.860 | The styling comes from Pico CSS.
00:12:00.060 | You can use any styling you want.
00:12:03.340 | But this is a nice, lightweight, simple one
00:12:08.660 | that kind of works pretty well.
00:12:10.140 | And it comes in by default. All you have to do is say headers.
00:12:13.940 | So this is the stuff in the head section of your HTML.
00:12:17.500 | And picolink is just a predefined variable,
00:12:19.700 | which brings in Pico.
00:12:27.140 | And the nice thing is, because these are just
00:12:32.380 | normal HTML applications, you can always just use source
00:12:35.060 | and see it.
00:12:36.180 | So you can use JavaScript.
00:12:46.100 | And so we saw some JavaScript like this, right?
00:12:53.540 | This is JavaScript.
00:12:56.220 | That JavaScript comes from this library called sortable.js,
00:13:01.460 | which does drag and drop.
00:13:03.820 | It's pretty cool.
00:13:04.540 | And so in this app, I'm showing two ways of doing JavaScript.
00:13:12.820 | One is by things that are predefined.
00:13:15.580 | And one is by things that we define ourselves.
00:13:19.540 | So sortable.js, it's really kind of there as an example.
00:13:25.260 | So there's a fasthtmljs.py.
00:13:30.260 | And so if I search for sortable, this is the whole thing, right?
00:13:37.420 | This is all it took to add sortable.js to fastHTML.
00:13:41.660 | That's all the code.
00:13:43.740 | And it's just basically a case of just returning a script
00:13:47.660 | tag with imports sortable.
00:13:53.100 | So it's really easy to add JavaScript.
00:13:59.580 | And so you can see that in the app.
00:14:06.020 | There's that code we just saw.
00:14:07.500 | And we have the same thing for markdown.
00:14:16.540 | But just to show how to do it yourself,
00:14:25.900 | we've done this one manually.
00:14:27.340 | So here you can see markdown.js is just a string of markdown.
00:14:31.020 | You can also put it in an external file.
00:14:33.260 | And that's just a script as well.
00:14:35.260 | So these all end up in the head, like so.
00:14:44.600 | So first route we're going to add is login.
00:14:53.260 | So the easiest-- well, there's lots of different ways
00:14:56.980 | to do routes.
00:14:58.060 | And a lot of it's just whatever you prefer.
00:15:01.620 | But app.route is one convenient way to do it.
00:15:05.380 | And I use it so often that I normally just give it a name.
00:15:08.220 | So rt.
00:15:09.580 | So this is app.route.
00:15:11.540 | And then you just say what path.
00:15:14.100 | And then here for the name, you say what HTTP verb.
00:15:17.980 | So this is going to be a get on slash login.
00:15:23.180 | And we're going to need a form.
00:15:29.140 | So again, you can just see it.
00:15:30.580 | So we're going to need a form with an action, a method.
00:15:35.380 | So this is all just plain HTML, HTTP, whatever.
00:15:39.820 | So rather than having to write to a form, slash form,
00:15:45.460 | you just use a function with the exact same name,
00:15:49.220 | but with a capital letter to start with.
00:15:51.140 | So here's my inputs.
00:15:53.660 | Here's my button.
00:15:54.660 | Here's my action.
00:15:55.460 | So this just maps directly to this.
00:15:58.980 | We use that type by default so I didn't have to type it in.
00:16:02.260 | And then the other--
00:16:06.940 | >> You are using this--
00:16:08.580 | there's different ways of defining the routes.
00:16:10.580 | And you're using this kind of--
00:16:12.820 | >> I'm using rt.
00:16:14.660 | >> rt, yeah.
00:16:16.940 | Sometimes I use the app, the other pattern,
00:16:20.300 | because I discovered that in cursor,
00:16:24.420 | it wants to anchor on the name of the function in a file.
00:16:27.820 | And it gets confused.
00:16:29.700 | And there's multiple functions in the same name.
00:16:34.740 | >> Yeah.
00:16:37.060 | Yeah, there's lots of different ways of doing it.
00:16:39.060 | They're all fine.
00:16:39.940 | Different people have different preferences.
00:16:41.900 | Yeah, so actually, you could just do--
00:16:47.780 | that would also do the same thing.
00:16:53.420 | By default, it adds both a get and a post method.
00:16:56.660 | >> Oh, I didn't know that.
00:16:58.660 | >> Yeah.
00:17:00.260 | >> Interesting.
00:17:02.220 | >> There's something for everybody.
00:17:05.900 | Also, in this form, we want the title
00:17:08.900 | so that it appears in the tab correctly.
00:17:14.060 | So that comes from the title.
00:17:15.700 | So this little function here just basically
00:17:21.980 | causes two things to happen.
00:17:23.980 | It causes the form to be returned.
00:17:26.140 | And it causes the title to be returned.
00:17:29.420 | And also, it causes an H1 with the title to be added.
00:17:34.780 | So that's all that is.
00:17:36.220 | And again, it's these things that just--
00:17:39.500 | it's good just to look at them so you can just
00:17:41.460 | see how tiny they are.
00:17:43.020 | >> I did look at the source code for that function today.
00:17:46.260 | >> Yeah, so there it is.
00:17:47.300 | Yeah, so it's just a tuple of the title, a main, H1.
00:17:54.740 | The reason we use a main with this class
00:17:56.700 | is because that's what Pico expects.
00:17:58.580 | >> Cool.
00:18:06.140 | >> So happy with login, Hamel?
00:18:08.500 | >> Yeah, it looks good.
00:18:09.500 | >> OK, so after we log in, it's going
00:18:11.300 | to do a POST request to /logins.
00:18:14.540 | This one's a GET request.
00:18:16.340 | So here's the POST request.
00:18:20.860 | So when we do the POST request, it's
00:18:22.860 | going to be receiving a form with a name.
00:18:26.820 | >> The action is part of the form, right?
00:18:29.580 | Because the indentation was a little bit--
00:18:33.340 | >> This indentation, sorry, this indentation or--
00:18:35.420 | >> OK, no, the one in the code.
00:18:37.540 | >> Oh, the code.
00:18:40.420 | Yeah.
00:18:42.740 | Yeah, exactly.
00:18:51.860 | >> I'm wondering, maybe I should--
00:18:53.820 | I've just switched to VS Code from Vim
00:19:02.180 | because it'll be more familiar, and also because I've
00:19:04.300 | set up this thing called Blockman, which is nice.
00:19:06.340 | So it highlights the current section.
00:19:09.660 | So you can see what goes--
00:19:11.380 | >> Oh, is that a plug-in?
00:19:13.060 | >> Yeah, it's a really nice plug-in.
00:19:15.180 | And even when you're not selected it,
00:19:16.700 | you can still see the selected bits.
00:19:21.500 | It's great.
00:19:24.180 | It's very helpful for--
00:19:25.780 | >> It works like by parentheses and stuff?
00:19:28.100 | >> Yeah, it just works by parentheses, exactly.
00:19:31.660 | So for stuff like this, it's nice to be able to see.
00:19:39.580 | >> Is there anything else like that?
00:19:47.820 | >> In the world?
00:19:48.700 | There's lots of good things in the world.
00:19:50.380 | >> What's up for your VS Code plug-in?
00:19:52.060 | >> I don't know.
00:19:54.060 | This will do for now.
00:19:56.860 | Blockman, yes.
00:19:59.420 | Really cool.
00:20:01.060 | Make you feel like a Lisp programmer.
00:20:03.020 | OK, so when I log in, those two fields
00:20:11.700 | are going to be passed to my post handler, which is here.
00:20:20.140 | And so we've added lots and lots of comments to all of these,
00:20:25.860 | so we won't go through them all today.
00:20:27.580 | But you can see exactly what everything is
00:20:30.380 | and why it happens and so forth.
00:20:31.940 | So we saw that there's going to be a name and a password.
00:20:37.300 | You could just type in here name colon str comma password
00:20:42.780 | colon str.
00:20:45.020 | That's one approach.
00:20:47.740 | Although I'm showing the slightly trickier thing here,
00:20:49.980 | which is where you can create a class with those things in it.
00:20:54.500 | And it just automatically creates
00:20:55.940 | an object of that class.
00:20:59.060 | Does that make sense?
00:21:00.540 | >> Yeah, any benefits to doing it this way?
00:21:06.540 | Is there any kind of--
00:21:07.500 | >> The benefits will come when we get to some of the other stuff,
00:21:12.860 | to be honest.
00:21:14.100 | There's not a huge benefit here.
00:21:17.540 | In fact, maybe we should just--
00:21:21.180 | well, I don't know, whatever.
00:21:25.980 | Yeah, OK.
00:21:27.020 | So we want to see if this user exists.
00:21:32.500 | So in Fastlight, or--
00:21:36.540 | >> Just to be clear, the thing that's
00:21:38.540 | submitting the POST request is sending a login type?
00:21:43.580 | >> It's not sending a login type.
00:21:45.460 | It's sending a form.
00:21:47.180 | And the form contains a field called name
00:21:54.140 | and a field called PWD.
00:21:55.700 | So we can see that it's actually a good idea
00:22:03.900 | to look at these things happening.
00:22:05.340 | So this is the best way to understand what's going on,
00:22:08.380 | is open up your thing here, click Login.
00:22:11.980 | And then you'll see here's Login.
00:22:15.220 | And you'll see it's got this form data.
00:22:20.380 | Does that make sense?
00:22:21.340 | And so the result was a 303c other, so it's a redirect.
00:22:30.820 | And the reason we got a redirect there is--
00:22:36.540 | well, we'll see in a moment.
00:22:38.140 | So yeah, so it got posted to here.
00:22:41.820 | We got received a name and a PWD.
00:22:45.020 | And because we requested a login object,
00:22:47.700 | it constructed it by passing in those parameters
00:22:51.780 | to their same named places.
00:22:54.380 | >> I see.
00:22:54.900 | >> So now we've hopefully got a login name.
00:23:00.740 | Hopefully we've got a login password.
00:23:02.140 | If they didn't fill it in, then obviously they can't log in.
00:23:04.660 | So we go back to the reader.
00:23:09.340 | OK, so then we try to see whether the user exists.
00:23:20.500 | So I think it's quite helpful, or at least I
00:23:22.540 | find it quite nice to just have a data--
00:23:38.460 | just have a little notebook I can fickle around with.
00:23:42.300 | So actually, we've got it here.
00:23:55.060 | So we can grab our tables, users,
00:24:04.060 | there's a name and a password.
00:24:06.860 | So Fastlight basically lets you search by primary key
00:24:10.740 | by just using square bracket notation.
00:24:13.460 | So JPH exists, JPHK doesn't exist.
00:24:24.700 | Make sense?
00:24:26.340 | >> Wait, when you had square brackets without anything in it,
00:24:30.860 | you had some completion thing that
00:24:33.540 | showed all the fields or something?
00:24:35.660 | What was going on?
00:24:38.100 | Or was it just imagining that?
00:24:39.500 | >> I think you're just imagining that.
00:24:41.900 | >> OK.
00:24:43.180 | >> There's a dot, and specifically dot C gives me the--
00:24:49.540 | >> OK.
00:24:51.260 | Let's see.
00:24:52.180 | >> And T gives you the tables.
00:24:53.580 | >> That's awesome that it's available like that.
00:25:00.580 | Yeah, that's really good.
00:25:02.020 | >> Yeah.
00:25:03.060 | Yeah, yeah, it's super nice.
00:25:04.860 | I like it.
00:25:06.500 | This is all in the Fastlight documentation.
00:25:08.700 | See how to do all that stuff.
00:25:10.740 | Yeah.
00:25:12.500 | So basically, I try--
00:25:14.460 | actually, Jono recently added a slightly nicer way
00:25:17.540 | to do this, which is instead you can say if login.name
00:25:34.060 | not in users colon.
00:25:41.220 | You could do that way nowadays instead of using try except.
00:25:49.620 | Anyway, so in this kind of simplified thing,
00:25:55.220 | the logic we have is just that if a user doesn't exist,
00:26:00.460 | there's no separate registration key screen.
00:26:03.020 | We just create them there and then.
00:26:06.860 | So that's how we create new users in the system.
00:26:11.260 | OK, so now we're up to your question from earlier,
00:26:13.340 | like what's compareDigest.
00:26:15.380 | So at this point, we have a user.
00:26:17.740 | We just created one.
00:26:18.780 | And so one nice thing about Fastlight
00:26:20.380 | is all the things like insert and update and stuff
00:26:22.540 | always return the new object.
00:26:26.060 | So this has returned the user that's been inserted.
00:26:31.300 | So we want to check whether their password is correct.
00:26:34.300 | You could just check with the equals equals
00:26:36.740 | to see if they're correct.
00:26:37.820 | But there's a technical security issue there,
00:26:41.220 | which is depending on the length of the password,
00:26:44.500 | for example, the amount of time that it
00:26:47.300 | takes to finish calling equals equals varies,
00:26:51.300 | which actually basically gives some information
00:26:55.780 | to an attacker about your password.
00:26:59.020 | So this is a more secure way of comparing two passwords.
00:27:02.820 | That's all.
00:27:04.340 | So yeah, if the passwords are not the same,
00:27:07.220 | then again, return back to the login screen.
00:27:09.180 | So yeah, by this point, we've got a login.
00:27:17.620 | So we store it away in our session.
00:27:19.100 | Auth is a special session name.
00:27:24.860 | It's a good place to put your login,
00:27:26.380 | just because basically you can then--
00:27:29.020 | if you put auth as a parameter, you'll be passed--
00:27:32.860 | VICTOR: What made you know about this Python security thing?
00:27:40.460 | Is it something you already knew about?
00:27:42.040 | Or is there some other thing that you
00:27:43.620 | recommend reading to know about different things like that?
00:27:46.860 | Well, for FastMail, I had to know all that stuff.
00:27:49.580 | And then originally, I didn't bother with it
00:27:52.420 | in this application because I thought it's
00:27:54.940 | kind of confusing.
00:27:57.500 | But then somebody raised an issue
00:28:00.020 | saying you shouldn't teach people
00:28:01.340 | how to create insecure web apps.
00:28:03.220 | So I put it in.
00:28:05.700 | Is that fair enough?
00:28:07.380 | OK, you were bullied into it.
00:28:11.100 | You know me.
00:28:11.700 | I'm easily led.
00:28:15.660 | So if that was all successful, then now we
00:28:17.780 | can go ahead and redirect to Slash.
00:28:20.620 | And that's what you saw.
00:28:22.500 | Here, we've got a C other redirect.
00:28:48.420 | OK, so what happened when we went to Slash?
00:28:52.500 | When we went to Slash, we call this one, our get.
00:29:03.700 | And just to show off, I wanted to say jph is to-do list.
00:29:08.060 | So remember I mentioned we can use auth
00:29:09.780 | as a special parameter name.
00:29:11.540 | So that gets it from the session.
00:29:13.660 | And the session's signed, so somebody can't
00:29:15.620 | pretend to be somebody else.
00:29:17.260 | So that's all that is.
00:29:24.260 | What is in auth?
00:29:25.100 | All this is in comments.
00:29:28.660 | Current username.
00:29:30.340 | Is it just-- yeah, what is auth?
00:29:33.140 | Is it--
00:29:33.660 | It's a thing we stored in SysAuth.
00:29:36.420 | Oh, OK.
00:29:42.380 | On the other hand, when you click Logout,
00:29:49.500 | it's just href slash logout.
00:29:51.260 | So that's just going to call get on logout.
00:29:53.540 | So it just deletes auth from the session.
00:29:57.780 | Takes you back to the login screen.
00:29:59.220 | So I mentioned earlier that, for example,
00:30:10.220 | you want to be able to see the Australian flag
00:30:12.060 | for the favicon.
00:30:13.380 | That's not going to work unless you have a static file
00:30:15.620 | handler to recognize.
00:30:16.860 | Like a .ico file should be just passed directly.
00:30:23.300 | And so this is a bit of a long way of doing it.
00:30:26.300 | This is the most complete way of showing how
00:30:31.580 | to do a static file handler.
00:30:33.500 | So we're addling a root.
00:30:35.020 | And basically, this is all Starlet syntax.
00:30:38.220 | So if you look up Starlet, it'll teach you what this means.
00:30:41.140 | But this basically says, in the path, one of the bits
00:30:45.940 | is called the filename.
00:30:49.220 | And then another bit we're going to call ext.
00:30:52.180 | Do you see they just get passed in here?
00:30:55.540 | And this, as it mentions in the notes here,
00:31:00.180 | is just any one of these extensions.
00:31:03.740 | So you can change that to other things.
00:31:07.020 | And so, yeah, and if you get that,
00:31:08.820 | we return a file response, which means
00:31:10.940 | the contents of the file.
00:31:14.980 | If you use FastApp, you don't have to add this.
00:31:16.980 | It's just done automatically.
00:31:19.700 | I see.
00:31:21.500 | OK, that makes--
00:31:23.180 | So yeah, here's our home page.
00:31:26.420 | So title.
00:31:29.620 | OK, so then along the top, we've got JPH's to-do list, logout.
00:31:34.340 | So I just used a grid, h1 title, logout.
00:31:40.020 | Like, here's grid, right?
00:31:45.580 | It's just a div with class equals grid.
00:31:55.740 | Actually, maybe even easier.
00:31:56.940 | It's just to see it here, inspect.
00:32:01.500 | It's just a class equals grid.
00:32:03.860 | That's just how Pico does it.
00:32:05.100 | What was the other HTX thing?
00:32:10.220 | Basically--
00:32:15.340 | FDHX, sorry.
00:32:16.860 | I don't know why I wrote it this weird way.
00:32:21.180 | It should basically just say div.
00:32:23.140 | Like that.
00:32:30.980 | That's just a long form.
00:32:33.380 | Pretend it says that, because that's--
00:32:35.380 | I don't know why I wrote it the weird way.
00:32:37.140 | Yeah, so you can see it's all just generating HTML, right?
00:32:44.500 | That's all.
00:32:47.380 | So then we've got this new to-do here with an Add button.
00:32:51.540 | So that's the input.
00:32:54.940 | New to-do is the Add button.
00:32:57.940 | And to make them look nicely together like this,
00:33:08.180 | Pico has this group thing.
00:33:11.940 | Again, you can just see it.
00:33:15.060 | What's a group?
00:33:16.500 | It's just field set row equals group.
00:33:20.540 | So you have input.
00:33:21.700 | OK, it's just horizontally grouping it?
00:33:26.700 | Yeah, yeah, just you can read the Pico docs
00:33:29.420 | to see what Pico can do.
00:33:32.020 | Yeah, basically, no one's going to write all of this CSS
00:33:36.500 | by hand unless they're crazy.
00:33:38.220 | So just pick a framework and stick to it for a while
00:33:42.700 | until you know it.
00:33:43.580 | And I would say just use Pico for a while, because it's fine.
00:33:48.060 | It's not amazing, but it's fine, which
00:33:49.780 | is exactly what you want, right?
00:33:52.020 | Yeah.
00:33:54.420 | OK, so then we want to get a list of to-dos.
00:34:02.260 | So to get a list of to-dos, you just
00:34:05.900 | write to-dos, parenthesis, parenthesis, like so.
00:34:10.580 | So that's how you return the contents of your table.
00:34:13.580 | However, I need to make sure that I
00:34:20.140 | don't have players to-dos.
00:34:23.900 | I only want my to-dos.
00:34:26.620 | So we have a really convenient thing in--
00:34:29.460 | All right, just a follow up.
00:34:31.220 | The to-dos, is that the SQLite?
00:34:34.260 | This is Fastlite, yeah.
00:34:35.380 | That's the table or the Pathlite.
00:34:37.140 | OK, OK, OK, yeah.
00:34:38.980 | OK, I got it.
00:34:42.260 | So there's a few-- you can pass in a WHERE clause
00:34:46.580 | and arguments for that, ORDER BY, LIMIT, OFFSET, SELECT.
00:34:50.580 | So it's just like a very, very simple one-table query
00:34:55.060 | that, by default, just returns the whole table.
00:34:56.980 | Because I'm kind of terrible at remembering all the security
00:35:02.300 | details, and I think everybody else is as well,
00:35:04.620 | we've tried to make that really pretty automatic.
00:35:07.100 | So this is where this comes in.
00:35:12.740 | If you call this on your table, then it
00:35:17.580 | will add this criteria to all of your queries,
00:35:20.740 | both data manipulation and select.
00:35:25.340 | So to-dos.extra.jph-- sorry, you've got to say name equals
00:35:39.740 | And you can see now it's only displaying my to-dos.
00:35:46.020 | So that's just automatic at that point.
00:35:48.700 | That's great, yeah.
00:35:49.660 | I was needing this earlier.
00:35:51.980 | I didn't find it, but it makes sense.
00:35:54.940 | This app isn't multi-user, I should mention.
00:36:03.620 | That's stored as a global.
00:36:07.620 | So in practice, you would actually
00:36:11.380 | probably want to take this out and put it
00:36:13.380 | inside each method, each handler.
00:36:17.780 | But this is just an example.
00:36:20.460 | OK, so now we want to order them by priority, which--
00:36:31.340 | just write order by priority.
00:36:32.900 | So there you go.
00:36:33.860 | OK, ready 0, 1, 2, 3.
00:36:35.820 | So I find it much easier to write these kinds of things
00:36:41.020 | if you have a little notebook or REPL attached to the database
00:36:44.500 | you can play with.
00:36:47.900 | So all of the fast HTML tags, we call them fast tags or FT,
00:36:54.020 | always take zero or more children.
00:36:57.940 | Oh, wait, that thing that was global, the extras,
00:37:05.020 | if you didn't want it there, would you
00:37:06.700 | have to add it to all the routes?
00:37:09.180 | Yeah, what would you--
00:37:10.900 | OK, is there a way to not add it to all the routes,
00:37:15.180 | to somehow add it to all the routes
00:37:17.340 | at once without being global or whatever that you recommend?
00:37:24.460 | You know what I'm trying to ask?
00:37:25.780 | I do know what you're trying to say.
00:37:27.260 | I'm going to have to think about it.
00:37:31.420 | Decorator would probably be your best option, I think.
00:37:39.560 | Yeah.
00:37:44.520 | OK, yeah, so here's the list of to-dos.
00:37:51.460 | All tags, or FT, or fast tags as we call them,
00:37:54.540 | always take zero or more children
00:37:56.380 | as the positional arguments.
00:37:58.220 | So that's why I use star here to say these
00:38:00.420 | are all of my children.
00:38:02.420 | So it looks really weird here that we're
00:38:04.620 | putting database objects as arguments to a tag.
00:38:11.420 | Like, what the hell does that mean?
00:38:13.420 | And the answer is that FastHTML automatically
00:38:18.460 | calls a special method called DunderFT
00:38:22.540 | on anything that's inside a tag.
00:38:26.620 | And so we actually added a DunderFT to our to-do class.
00:38:32.140 | So this is what's called to automatically turn
00:38:41.860 | a to-do into HTML, basically.
00:38:49.500 | So when a to-do is displayed, it's
00:38:56.140 | displayed like so as a list item.
00:39:00.020 | So it returns a list item.
00:39:04.300 | And it contains a hyperlink with the title,
00:39:11.500 | a hyperlink with the word edit, optionally a tick if it's done.
00:39:17.820 | And then we also add hidden fields
00:39:24.020 | for the priority and the ID.
00:39:25.740 | Yeah, so that's the list item.
00:39:31.300 | So this is--
00:39:33.140 | - I'm just watching, but I'm not familiar with FastCore.
00:39:38.260 | And I want to explain what that patch is doing.
00:39:42.380 | - Yeah, so it just adds this method to this class.
00:39:46.900 | Although, if you use the newer style approach I described,
00:39:55.620 | you can just add it directly to the class.
00:39:58.220 | So you don't have to use patch if you don't want to.
00:40:01.620 | - The what file approach, sorry?
00:40:03.780 | - The newer style approach that I showed you earlier.
00:40:09.340 | Where did we have that?
00:40:10.820 | Let's do it.
00:40:16.820 | So you can just go dot, dot, dot, dot, dot, dot, dot, dot.
00:40:20.900 | Paste.
00:40:22.740 | So here, we want to add ThunderFT to that.
00:40:29.780 | So we could just move that and put it just straight inside
00:40:55.660 | the Judo, like so.
00:41:01.420 | You don't need that anymore.
00:41:02.900 | Yeah, either is fine.
00:41:08.020 | Just another reason this is kind of nice approach.
00:41:24.140 | So all right, so yeah, this is kind of cool
00:41:28.220 | that you just put your to-dos directly into your form.
00:41:30.420 | And then the form, we can give it an ID, a class.
00:41:36.620 | The form has a post, which we'll come back to why that's there.
00:41:39.500 | - I have a question.
00:41:45.940 | When you're doing something like that to-do component,
00:41:49.220 | or whatever the list, the to-do list, or the thing,
00:41:56.860 | did you do that part in a notebook?
00:41:59.660 | And then copy and paste it?
00:42:01.060 | Or did you do it directly in the editor?
00:42:03.060 | And what is your approach?
00:42:06.900 | - Honestly, there's so little to it,
00:42:08.340 | I think I probably just did it directly here,
00:42:10.220 | once you remove the comments.
00:42:11.380 | There's no logic in it.
00:42:14.780 | So generally, I actually only use notebooks and stuff
00:42:17.180 | for stuff where there's logic.
00:42:18.300 | There's no logic here.
00:42:19.140 | This is just HTML.
00:42:20.380 | But you certainly can, like if you--
00:42:27.260 | - Like if you wanted to fiddle around with the way it looks,
00:42:39.980 | I guess, you just kind of hot reload and just--
00:42:43.220 | - Yeah, so you can use fast tags directly in a notebook,
00:42:47.540 | and they'll render as that.
00:42:50.500 | Or if you want to see what they're actually
00:42:52.300 | going to look like, you can just work a show on them,
00:42:55.540 | like that.
00:42:58.060 | But it does depend on having the style sheets available.
00:43:05.940 | I think the easiest way to really see what it's actually
00:43:08.180 | going to look like with the styling and stuff
00:43:10.140 | is just to run your app.
00:43:12.140 | And we have live reloading, so as soon as you edit your app,
00:43:15.780 | you'll see the results.
00:43:16.740 | Yeah, again, lots of comments here explaining exactly what's
00:43:39.220 | going on with all that FTF whatever stuff.
00:43:43.220 | Yeah, so as before, we've got the title,
00:43:45.460 | and then we return that stuff.
00:43:50.420 | So I guess, yeah, what happens when you add a new one?
00:44:01.740 | It's going to be clicking the Add button inside the form.
00:44:07.780 | It's going to post a slash.
00:44:12.860 | So here's the post to slash.
00:44:19.460 | And so this is kind of cool, because the form has a title,
00:44:35.020 | and we say we want to get a to-do passed to us.
00:44:42.820 | So it's going to construct a to-do for us
00:44:45.020 | and give it the title that's in the form.
00:44:47.620 | So that's how we can have a to-do automatically.
00:44:51.180 | Can you talk a little bit about when to use async
00:44:54.100 | and when not to use async?
00:44:56.300 | Oh, I don't know why any of these say async.
00:44:58.860 | You don't need async.
00:45:00.820 | The only time you need async is if you ever
00:45:02.940 | find that you need to type the word, oh, wait,
00:45:04.860 | and if you ever type the word, oh, wait, and don't have async,
00:45:07.900 | Python will yell at you.
00:45:09.460 | So don't type async unless Python yells at you.
00:45:14.100 | No, it's the short version.
00:45:16.180 | So yeah, unless you know you need async,
00:45:17.940 | you don't need async.
00:45:19.860 | I don't know why this is async.
00:45:21.380 | None of these are async.
00:45:28.780 | Why does it--
00:45:29.820 | you mean, it still works the same?
00:45:33.980 | I mean, it isn't doing--
00:45:34.980 | Yeah, it still works the same.
00:45:37.020 | Yeah, you don't need async.
00:45:38.140 | OK, I'll just forget that I saw it.
00:45:43.500 | Yeah.
00:45:44.020 | Which is hard for me to do sometimes.
00:45:46.020 | It works if it's there.
00:45:47.140 | It works if it's not there.
00:45:48.820 | OK, so we've got a to-do now.
00:46:03.020 | So we can insert it.
00:46:08.180 | And remember, I mentioned that when you insert something,
00:46:11.820 | it also returns the thing that was inserted.
00:46:15.940 | And after it's been inserted, it's now got an ID added.
00:46:19.420 | This automatically gets an ID.
00:46:22.900 | And then the other thing that's going to happen when we--
00:46:26.380 | so when I click Add, two things should happen.
00:46:29.060 | This to-do should appear in the list,
00:46:31.140 | and this input box should disappear,
00:46:34.780 | which is what happens.
00:46:36.180 | So the reason why is because we return the new to-do.
00:46:39.460 | And that gets rendered with Dunder FT automatically.
00:46:42.660 | And we also return a new input box, which is empty.
00:46:50.300 | And it has the same ID.
00:46:52.500 | So it replaces the one that's already there.
00:46:55.220 | So that's--
00:46:55.740 | >>So how does it know to--
00:46:57.540 | I guess, how does it know to do the replacement?
00:47:05.580 | Is that in--
00:47:06.340 | >>That's what OOB means.
00:47:07.940 | OOB means out-of-bounds swap.
00:47:09.620 | It means to replace one that's already there,
00:47:12.540 | and it looks for something with the same ID.
00:47:14.380 | >>OK, and then the to-do's insert
00:47:23.620 | is just like putting it in that element
00:47:26.860 | and just pushing another--
00:47:29.620 | >>No, this is inserting it into the to-do's table.
00:47:31.940 | This is a fastlight.
00:47:33.020 | >>Oh, to-do's table.
00:47:34.220 | OK, sorry.
00:47:34.900 | >>And after-- and then it returns the inserted to-do.
00:47:39.420 | And then it gets automatically rendered as a list item
00:47:42.500 | using Dunder FT.
00:47:43.340 | >>Oh, OK.
00:47:48.140 | >>And it gets added to the end of the list
00:47:51.140 | because when we hear this button, when the button that we
00:47:58.380 | clicked has an HX swap after begin and target to-do list,
00:48:03.620 | so it sticks it at the end of the to-do list
00:48:06.420 | when it's received the new to-do.
00:48:10.380 | So this is like--
00:48:11.620 | I can't teach you all of HTMX.
00:48:13.740 | This is-- so this is just HTMX.
00:48:16.020 | It's not fastHTML.
00:48:17.660 | So you should definitely, definitely, definitely
00:48:20.020 | read the Hypermedia.Systems book, which
00:48:22.700 | is really, really, really good.
00:48:26.500 | >>Media.Systems book, OK.
00:48:28.420 | >>Yeah, this book is--
00:48:29.460 | >>So now if I take some HTMX--
00:48:30.700 | >>It's an absolute must-read.
00:48:32.700 | >>Yeah, you read this book.
00:48:33.780 | Don't just do the HTMX blog post tutorials, whatever.
00:48:38.460 | >>No, no, no.
00:48:39.260 | This is a very good book.
00:48:40.860 | It's a really good book.
00:48:42.580 | There's nothing else like it.
00:48:46.300 | >>Hypermedia.Systems.
00:48:49.300 | And as per usual, build things as you read it, of course.
00:48:55.300 | >>Just to make sure I understand,
00:49:00.700 | the Hypermedia.Systems is related to HTMX?
00:49:04.500 | >>This is the HTMX book, yeah.
00:49:06.460 | So part one describes web 1.0 applications.
00:49:10.820 | So it doesn't even mention HTMX.
00:49:12.300 | It's just using HTML and HTTP, because that's
00:49:15.740 | kind of like understanding that's a prerequisite.
00:49:18.260 | And then part two is HTMX.
00:49:20.740 | Part three, you could ignore it.
00:49:22.140 | It's about mobile.
00:49:24.460 | >>OK.
00:49:24.940 | >>Yeah, so once you've read that book, you know HTMX.
00:49:39.940 | >>So I understand this input--
00:49:43.100 | sorry, I understand that this--
00:49:46.700 | OK, it's going to get this thing back, this to-dos back.
00:49:53.540 | And it's going to swap it out.
00:49:55.940 | But then how does it--
00:49:59.140 | this new input is being--
00:50:03.060 | I know it says swap that one out, too.
00:50:06.060 | But then how does it--
00:50:09.020 | is that a fast HTML thing?
00:50:10.860 | >>No, none of this is fast HTML.
00:50:12.260 | This is all just HTMX.
00:50:13.380 | So it's best just to look at it happening.
00:50:18.660 | So just watch it.
00:50:24.500 | OK, so it did a post to Slash with this payload.
00:50:33.220 | >>OK.
00:50:34.420 | >>And the response it got was two things--
00:50:37.660 | a list item.
00:50:38.660 | So that's the rendered version of the to-do.
00:50:42.140 | And the empty input box with OOB.
00:50:48.740 | So this one, because it says OOB,
00:50:50.380 | is inserted to replace whatever has that ID, which is this.
00:50:57.140 | And this one is inserted wherever the requesting button
00:51:01.900 | asked it to be inserted, which was after the beginning
00:51:18.980 | of the to-do list.
00:51:20.180 | And this is the to-do list.
00:51:24.580 | That's the to-do list.
00:51:31.860 | Anyway, this is all just HTMX.
00:51:36.300 | So we can't hope to explain all of HTMX here.
00:51:41.980 | Yeah, in fact, that's basically it.
00:51:53.740 | Like, there's an Edit screen.
00:51:56.660 | So when you click Edit, again, you
00:51:59.820 | can see what happens when you click Edit.
00:52:04.940 | It calls Edit Slash 0.
00:52:10.100 | And it does that because our ThunderFT says so--
00:52:17.580 | Slash Edit Slash 0.
00:52:20.940 | And so that's going to call this.
00:52:27.700 | And so this is--
00:52:29.300 | again, this is Starlet.
00:52:31.100 | So Starlet says this is how you should
00:52:33.060 | say what special things in the path, variables in the path.
00:52:35.620 | And it's going to get stuck into your parameters.
00:52:39.060 | So here's whatever was in the path.
00:52:42.020 | And we pop up our form with the title, and the save,
00:52:53.740 | and the done details.
00:52:57.220 | And this is the super nifty thing
00:52:58.700 | in FastHTML, which, as it says here, it populates the form.
00:53:03.620 | So it takes the form called res and populates it
00:53:09.700 | with the contents of todo's ID.
00:53:13.300 | And so that's why you can see it's
00:53:16.500 | been populated with the todo.
00:53:17.900 | And so then you--
00:53:24.860 | What's the hidden thing about this hidden element?
00:53:29.540 | Yeah, so you need to-- basically,
00:53:31.380 | all state has to be stored inside the form.
00:53:35.260 | Again, this is just like-- this isn't even HTML.
00:53:37.740 | This is just HTML.
00:53:40.220 | So state has to be stored in the form.
00:53:46.980 | So it needs to know what to do with it.
00:53:54.020 | So this is todo 0.
00:53:57.140 | So that then when we click Save--
00:54:05.500 | I see.
00:54:07.100 | It has to know it's todo 0.
00:54:10.300 | With this title, it's done.
00:54:13.780 | These details-- anyway, so yeah, so I
00:54:31.740 | think we should revisit this after you've
00:54:36.980 | read the hypermedia.systems book and feel
00:54:41.940 | comfortable with hypermedia applications,
00:54:45.820 | because that's kind of a starting point for this.
00:54:51.580 | And this-- you can think of FastHTML
00:54:54.180 | as being an HTMX server almost, really.
00:54:59.460 | OK, I'll read it tomorrow.
00:55:03.140 | But I know you've already built stuff just
00:55:04.940 | by getting a cursor to help you.
00:55:07.140 | So you don't necessarily have to know all these details
00:55:09.540 | when you have a language model at your disposal.
00:55:13.020 | Yeah, I like to know.
00:55:14.300 | Otherwise, I get lost.
00:55:15.660 | Yeah, otherwise, maintenance and debugging and all that
00:55:18.860 | gets confusing.
00:55:21.460 | Yeah, I'm still somewhat skeptical about the ability
00:55:24.860 | for people to actually run businesses on the back of code
00:55:28.180 | if they had a model create and they don't understand.
00:55:32.860 | Can't imagine that really works in real life.
00:55:37.060 | It's considerably faster.
00:55:38.140 | Yeah, I think it's a little bit misleading.
00:55:42.140 | I think even when I'm using-- yeah,
00:55:43.900 | even though I'm using LLMs with FastHTML,
00:55:47.300 | I kind of know a little bit about where to look in a way.
00:55:51.900 | But if I didn't know anything at all, I think I would get stuck.
00:55:56.540 | Like, I kind of need examples and stuff.
00:56:03.180 | And I don't know.
00:56:04.500 | Well, I hope you found that helpful.
00:56:05.980 | For people watching, you found that helpful.
00:56:08.340 | Yeah, it was really good.
00:56:09.340 | Thank you.
00:56:09.820 | I'm going to go and get tea.
00:56:11.140 | Have a good night.
00:56:11.900 | All right, thank you.