home / github

Menu
  • Search all tables
  • GraphQL API

issue_comments

Table actions
  • GraphQL API for issue_comments

13 rows where issue = 1657861026 sorted by updated_at descending

✖
✖

✎ View and edit SQL

This data as json, CSV (advanced)

Suggested facets: created_at (date), updated_at (date)

user 2

  • simonw 12
  • dsisnero 1

author_association 2

  • OWNER 12
  • NONE 1

issue 1

  • Make detailed notes on how table, query and row views work right now · 13 ✖
id html_url issue_url node_id user created_at updated_at ▲ author_association body reactions issue performed_via_github_app
1500608101 https://github.com/simonw/datasette/issues/2054#issuecomment-1500608101 https://api.github.com/repos/simonw/datasette/issues/2054 IC_kwDOBm6k_c5ZcXZl simonw 9599 2023-04-07T20:14:38Z 2023-04-07T20:14:38Z OWNER

Ooh that one's really interesting - very different from the others: ```ruby

app.rb

require "roda"

class App < Roda route do |r| r.root do "Home page" end

r.on "pages" do
  r.get ":slug" do |slug|
    "Page: #{slug}"
  end
end

r.on "news" do
  r.get ":yyyy/:mm/:dd" do |yyyy, mm, dd|
    "News for #{yyyy}/#{mm}/#{dd}"
  end
end

end end

config.ru

require_relative "app" run App.freeze.app ```

{
    "total_count": 0,
    "+1": 0,
    "-1": 0,
    "laugh": 0,
    "hooray": 0,
    "confused": 0,
    "heart": 0,
    "rocket": 0,
    "eyes": 0
}
Make detailed notes on how table, query and row views work right now 1657861026  
1499797384 https://github.com/simonw/datasette/issues/2054#issuecomment-1499797384 https://api.github.com/repos/simonw/datasette/issues/2054 IC_kwDOBm6k_c5ZZReI dsisnero 6213 2023-04-07T00:46:50Z 2023-04-07T00:46:50Z NONE

you should have a look at Roda written in ruby .

{
    "total_count": 0,
    "+1": 0,
    "-1": 0,
    "laugh": 0,
    "hooray": 0,
    "confused": 0,
    "heart": 0,
    "rocket": 0,
    "eyes": 0
}
Make detailed notes on how table, query and row views work right now 1657861026  
1499604822 https://github.com/simonw/datasette/issues/2054#issuecomment-1499604822 https://api.github.com/repos/simonw/datasette/issues/2054 IC_kwDOBm6k_c5ZYidW simonw 9599 2023-04-06T20:48:19Z 2023-04-06T20:48:55Z OWNER

I actually quite like that. I could even use @classmethod and have utility methods defined on that class that both get() and post() could call.

The crucial rule here is NO INSTANCE STATE - that's what makes routing to classes particularly damaging, and encourages code that's hard to maintain.

{
    "total_count": 0,
    "+1": 0,
    "-1": 0,
    "laugh": 0,
    "hooray": 0,
    "confused": 0,
    "heart": 0,
    "rocket": 0,
    "eyes": 0
}
Make detailed notes on how table, query and row views work right now 1657861026  
1499604066 https://github.com/simonw/datasette/issues/2054#issuecomment-1499604066 https://api.github.com/repos/simonw/datasette/issues/2054 IC_kwDOBm6k_c5ZYiRi simonw 9599 2023-04-06T20:47:30Z 2023-04-06T20:47:30Z OWNER

I'm contemplating a new approach: using a class with static methods. Something like this:

```python class TableView(MethodRouter): @staticmethod async def get(request): return Response.text("GET")

@staticmethod
async def post(request):
    return Response.text("POST")

`` So effectively the class is just there to bundle together verb implementations, and to provide aroute(request)` method which knows how to dispatch them to the right place.

It can offer default HEAD and OPTIONS methods too.

{
    "total_count": 0,
    "+1": 0,
    "-1": 0,
    "laugh": 0,
    "hooray": 0,
    "confused": 0,
    "heart": 0,
    "rocket": 0,
    "eyes": 0
}
Make detailed notes on how table, query and row views work right now 1657861026  
1499491258 https://github.com/simonw/datasette/issues/2054#issuecomment-1499491258 https://api.github.com/repos/simonw/datasette/issues/2054 IC_kwDOBm6k_c5ZYGu6 simonw 9599 2023-04-06T19:00:16Z 2023-04-06T20:40:16Z OWNER

I asked GPT-4 to compare URL routing based on path and verb approaches in different languages and frameworks and spit out some examples. Full transcript here: https://gist.github.com/simonw/fa2379b97420404a81b0fcdb4db79657

Initial prompt:

Show me code examples of different web frameworks in Python and JavaScript and Go illustrating how HTTP routing works - in particular the problem of mapping an incoming HTTP request to some code based on both the URL path and the HTTP verb

Don't show installation instructions, just get straight to the code snippets

Flask: ```python from flask import Flask

app = Flask(name)

@app.route('/example', methods=['GET']) def get_example(): return "This is a GET request."

@app.route('/example', methods=['POST']) def post_example(): return "This is a POST request."

if name == 'main': app.run() Express:javascript const express = require('express'); const app = express();

app.get('/example', (req, res) => { res.send('This is a GET request.'); });

app.post('/example', (req, res) => { res.send('This is a POST request.'); });

app.listen(3000, () => { console.log('Server is running on port 3000'); }); Gin:go package main

import ( "github.com/gin-gonic/gin" )

func main() { r := gin.Default()

r.GET("/example", func(c *gin.Context) {
    c.String(200, "This is a GET request.")
})

r.POST("/example", func(c *gin.Context) {
    c.String(200, "This is a POST request.")
})

r.Run()

} Actix-web:rust use actix_web::{web, App, HttpResponse, HttpServer, Responder};

async fn get_example() -> impl Responder { HttpResponse::Ok().body("This is a GET request.") }

async fn post_example() -> impl Responder { HttpResponse::Ok().body("This is a POST request.") }

[actix_web::main]

async fn main() -> std::io::Result<()> { HttpServer::new(|| { App::new() .route("/example", web::get().to(get_example)) .route("/example", web::post().to(post_example)) }) .bind("127.0.0.1:8080")? .run() .await } Koa:javascript const Koa = require('koa'); const Router = require('koa-router');

const app = new Koa(); const router = new Router();

router.get('/example', (ctx) => { ctx.body = 'This is a GET request.'; });

router.post('/example', (ctx) => { ctx.body = 'This is a POST request.'; });

app.use(router.routes()); app.use(router.allowedMethods());

app.listen(3000, () => { console.log('Server is running on port 3000'); }); Django:python from django.http import HttpResponse from django.views import View

class ExampleView(View): def get(self, request): return HttpResponse("This is a GET request.")

def post(self, request):
    return HttpResponse("This is a POST request.")

python from django.urls import path from .views import ExampleView

urlpatterns = [ path('example/', ExampleView.as_view(), name='example'), ] Rocket:rust

![feature(proc_macro_hygiene, decl_macro)]

[macro_use] extern crate rocket;

use rocket::response::content;

[get("/example")]

fn get_example() -> content::Plain<&'static str> { content::Plain("This is a GET request.") }

[post("/example")]

fn post_example() -> content::Plain<&'static str> { content::Plain("This is a POST request.") }

[launch]

fn rocket() -> _ { rocket::build().mount("/", routes![get_example, post_example]) } Elixir/Phoenix:elixir defmodule ExampleAppWeb.Router do use ExampleAppWeb, :router

pipeline :api do plug :accepts, ["json"] end

scope "/api", ExampleAppWeb do pipe_through :api

get "/example", ExampleController, :get_example
post "/example", ExampleController, :post_example

end end Bottle:python from bottle import Bottle, run

app = Bottle()

@app.route('/example', method='GET') def get_example(): return "This is a GET request."

@app.route('/example', method='POST') def post_example(): return "This is a POST request."

if name == 'main': run(app, host='localhost', port=8080) FastAPI:python from fastapi import FastAPI

app = FastAPI()

@app.get("/example") async def get_example(): return {"message": "This is a GET request."}

@app.post("/example") async def post_example(): return {"message": "This is a POST request."}

if name == "main": import uvicorn uvicorn.run(app, host="127.0.0.1", port=8000) ```

{
    "total_count": 0,
    "+1": 0,
    "-1": 0,
    "laugh": 0,
    "hooray": 0,
    "confused": 0,
    "heart": 0,
    "rocket": 0,
    "eyes": 0
}
Make detailed notes on how table, query and row views work right now 1657861026  
1499596941 https://github.com/simonw/datasette/issues/2054#issuecomment-1499596941 https://api.github.com/repos/simonw/datasette/issues/2054 IC_kwDOBm6k_c5ZYgiN simonw 9599 2023-04-06T20:39:35Z 2023-04-06T20:39:35Z OWNER

I think there are two main approaches:

  • Map path to a function, that function then behaves differently on GET/POST/etc
  • Map method AND path to a function - the app.get('/example') v.s. app.post('/example') ones

The decorator approach is calling out to me a bit now.

{
    "total_count": 0,
    "+1": 0,
    "-1": 0,
    "laugh": 0,
    "hooray": 0,
    "confused": 0,
    "heart": 0,
    "rocket": 0,
    "eyes": 0
}
Make detailed notes on how table, query and row views work right now 1657861026  
1499530921 https://github.com/simonw/datasette/issues/2054#issuecomment-1499530921 https://api.github.com/repos/simonw/datasette/issues/2054 IC_kwDOBm6k_c5ZYQap simonw 9599 2023-04-06T19:41:52Z 2023-04-06T19:42:09Z OWNER

Turned this into a TIL: https://til.simonwillison.net/gpt3/gpt4-api-design

{
    "total_count": 0,
    "+1": 0,
    "-1": 0,
    "laugh": 0,
    "hooray": 0,
    "confused": 0,
    "heart": 0,
    "rocket": 0,
    "eyes": 0
}
Make detailed notes on how table, query and row views work right now 1657861026  
1499467703 https://github.com/simonw/datasette/issues/2054#issuecomment-1499467703 https://api.github.com/repos/simonw/datasette/issues/2054 IC_kwDOBm6k_c5ZYA-3 simonw 9599 2023-04-06T18:37:22Z 2023-04-06T18:57:22Z OWNER

I've been hoping to move entirely away from class-based views, but now I'm wondering if that's the right decision given the need to support HTTP verbs.

It is cleaner to have a class that has methods for each verb, or to have a single function that can behave differently depending on the verb?

Or should I have a mechanism for dispatching to separate functions based on the verbs that isn't implemented in a base class?

The trouble with using base classes is that I've already shown that they tempt me to put logic in weird places, which makes code harder to modify later on.

The thing here is that the ONLY thing I want to use the base class for is as an HTTP verb switcher - which makes me doubt that I should risk taking on the other temptations of having a base class.

{
    "total_count": 0,
    "+1": 0,
    "-1": 0,
    "laugh": 0,
    "hooray": 0,
    "confused": 0,
    "heart": 0,
    "rocket": 0,
    "eyes": 0
}
Make detailed notes on how table, query and row views work right now 1657861026  
1499465648 https://github.com/simonw/datasette/issues/2054#issuecomment-1499465648 https://api.github.com/repos/simonw/datasette/issues/2054 IC_kwDOBm6k_c5ZYAew simonw 9599 2023-04-06T18:35:03Z 2023-04-06T18:35:03Z OWNER

There are actually five classes that subclass DataView:

https://github.com/simonw/datasette/blob/8b9d7fdbd8de7e74414cc29e3005382669a812dc/datasette/views/row.py#L16

https://github.com/simonw/datasette/blob/8b9d7fdbd8de7e74414cc29e3005382669a812dc/datasette/views/database.py#L34

https://github.com/simonw/datasette/blob/8b9d7fdbd8de7e74414cc29e3005382669a812dc/datasette/views/database.py#L172

https://github.com/simonw/datasette/blob/8b9d7fdbd8de7e74414cc29e3005382669a812dc/datasette/views/database.py#L215

https://github.com/simonw/datasette/blob/8b9d7fdbd8de7e74414cc29e3005382669a812dc/datasette/views/table.py#L72

I don't think DatabaseView and DatabaseDownload should have subclassed that at all, since they don't return a table of data.

{
    "total_count": 0,
    "+1": 0,
    "-1": 0,
    "laugh": 0,
    "hooray": 0,
    "confused": 0,
    "heart": 0,
    "rocket": 0,
    "eyes": 0
}
Make detailed notes on how table, query and row views work right now 1657861026  
1499462324 https://github.com/simonw/datasette/issues/2054#issuecomment-1499462324 https://api.github.com/repos/simonw/datasette/issues/2054 IC_kwDOBm6k_c5ZX_q0 simonw 9599 2023-04-06T18:31:56Z 2023-04-06T18:31:56Z OWNER

The DataView class does a LOT of work - mostly involving CSV responses.

https://github.com/simonw/datasette/blob/8b9d7fdbd8de7e74414cc29e3005382669a812dc/datasette/views/base.py#L160-L544

It has a redirect() method with some complex logic and CORS handling:

https://github.com/simonw/datasette/blob/8b9d7fdbd8de7e74414cc29e3005382669a812dc/datasette/views/base.py#L163-L172

It uses this method a lot, which has to be over-ridden in the TableView and RowView and QueryView subclasses:

https://github.com/simonw/datasette/blob/8b9d7fdbd8de7e74414cc29e3005382669a812dc/datasette/views/base.py#L174-L175

This method:

https://github.com/simonw/datasette/blob/8b9d7fdbd8de7e74414cc29e3005382669a812dc/datasette/views/base.py#L180

Is the bulk of the complexity, because it knows how to both turn a list of SQLite rows into a CSV file but also knows how to call .data() repeatedly with different pagination arguments in order to stream CSV back for a large table.

The async def get() method for GET requests is also very complicated. It mainly handles format stuff - knowing how to render HTML v.s. JSON v.s. CSV v.s. other formats specified using this plugin hook: https://docs.datasette.io/en/1.0a2/plugin_hooks.html#register-output-renderer-datasette

Plus it catches interrupted queries and returns a special error page for those (and other error messages too): https://github.com/simonw/datasette/blob/8b9d7fdbd8de7e74414cc29e3005382669a812dc/datasette/views/base.py#L381-L408

It adds the time taken to execute the queries: https://github.com/simonw/datasette/blob/8b9d7fdbd8de7e74414cc29e3005382669a812dc/datasette/views/base.py#L410-L411

{
    "total_count": 0,
    "+1": 0,
    "-1": 0,
    "laugh": 0,
    "hooray": 0,
    "confused": 0,
    "heart": 0,
    "rocket": 0,
    "eyes": 0
}
Make detailed notes on how table, query and row views work right now 1657861026  
1499457291 https://github.com/simonw/datasette/issues/2054#issuecomment-1499457291 https://api.github.com/repos/simonw/datasette/issues/2054 IC_kwDOBm6k_c5ZX-cL simonw 9599 2023-04-06T18:26:45Z 2023-04-06T18:26:45Z OWNER

Here's BaseView:

https://github.com/simonw/datasette/blob/8b9d7fdbd8de7e74414cc29e3005382669a812dc/datasette/views/base.py#L56-L145

It has methods for the options, get, post, delete, put, patch and head HTTP verbs, most defaulting to returinng a 405 Method not allowed message in plain text or JSON, depending on this check:

https://github.com/simonw/datasette/blob/8b9d7fdbd8de7e74414cc29e3005382669a812dc/datasette/views/base.py#L71-L81

Also adds CORS headers to anything if CORS mode is on:

https://github.com/simonw/datasette/blob/8b9d7fdbd8de7e74414cc29e3005382669a812dc/datasette/views/base.py#L106-L107

And adds the database_color (weirdly) and the select_templates variables to the template context:

https://github.com/simonw/datasette/blob/8b9d7fdbd8de7e74414cc29e3005382669a812dc/datasette/views/base.py#L112-L122

And has special code for setting the Link: ...; rel="alternate" HTTP header:

https://github.com/simonw/datasette/blob/8b9d7fdbd8de7e74414cc29e3005382669a812dc/datasette/views/base.py#L124-L136

{
    "total_count": 0,
    "+1": 0,
    "-1": 0,
    "laugh": 0,
    "hooray": 0,
    "confused": 0,
    "heart": 0,
    "rocket": 0,
    "eyes": 0
}
Make detailed notes on how table, query and row views work right now 1657861026  
1499457201 https://github.com/simonw/datasette/issues/2054#issuecomment-1499457201 https://api.github.com/repos/simonw/datasette/issues/2054 IC_kwDOBm6k_c5ZX-ax simonw 9599 2023-04-06T18:26:39Z 2023-04-06T18:26:39Z OWNER

These classes - TableView and RowView and QueryView - all subclass DataView which subclassesBaseView.

{
    "total_count": 0,
    "+1": 0,
    "-1": 0,
    "laugh": 0,
    "hooray": 0,
    "confused": 0,
    "heart": 0,
    "rocket": 0,
    "eyes": 0
}
Make detailed notes on how table, query and row views work right now 1657861026  
1499452122 https://github.com/simonw/datasette/issues/2054#issuecomment-1499452122 https://api.github.com/repos/simonw/datasette/issues/2054 IC_kwDOBm6k_c5ZX9La simonw 9599 2023-04-06T18:21:51Z 2023-04-06T18:21:51Z OWNER

I'm going to make notes against the code in the most recent alpha release, ignoring the recent work I did to refactor TableView.

https://github.com/simonw/datasette/tree/1.0a2/datasette/views

{
    "total_count": 0,
    "+1": 0,
    "-1": 0,
    "laugh": 0,
    "hooray": 0,
    "confused": 0,
    "heart": 0,
    "rocket": 0,
    "eyes": 0
}
Make detailed notes on how table, query and row views work right now 1657861026  

Advanced export

JSON shape: default, array, newline-delimited, object

CSV options:

CREATE TABLE [issue_comments] (
   [html_url] TEXT,
   [issue_url] TEXT,
   [id] INTEGER PRIMARY KEY,
   [node_id] TEXT,
   [user] INTEGER REFERENCES [users]([id]),
   [created_at] TEXT,
   [updated_at] TEXT,
   [author_association] TEXT,
   [body] TEXT,
   [reactions] TEXT,
   [issue] INTEGER REFERENCES [issues]([id])
, [performed_via_github_app] TEXT);
CREATE INDEX [idx_issue_comments_issue]
                ON [issue_comments] ([issue]);
CREATE INDEX [idx_issue_comments_user]
                ON [issue_comments] ([user]);
Powered by Datasette · Queries took 23.085ms · About: github-to-sqlite
  • Sort ascending
  • Sort descending
  • Facet by this
  • Hide this column
  • Show all columns
  • Show not-blank rows