home / github

Menu
  • Search all tables
  • GraphQL API

issue_comments

Table actions
  • GraphQL API for issue_comments

9 rows where "created_at" is on date 2020-09-15 and issue = 681375466 sorted by updated_at descending

✖
✖
✖

✎ View and edit SQL

This data as json, CSV (advanced)

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

user 1

  • simonw 9

issue 1

  • await datasette.client.get(path) mechanism for executing internal requests · 9 ✖

author_association 1

  • OWNER 9
id html_url issue_url node_id user created_at updated_at ▲ author_association body reactions issue performed_via_github_app
693009048 https://github.com/simonw/datasette/issues/943#issuecomment-693009048 https://api.github.com/repos/simonw/datasette/issues/943 MDEyOklzc3VlQ29tbWVudDY5MzAwOTA0OA== simonw 9599 2020-09-15T22:17:30Z 2020-09-22T14:37:00Z OWNER

Maybe instead of implementing datasette.get() and datasette.post() and datasette.request() and datasette.stream() I could instead have a nested object called datasette.client which is a preconfigured AsyncClient instance.

python response = await datasette.client.get("/") Or perhaps this should be a method in case I ever need to be able to await it: python response = await (await datasette.client()).get("/") This is a bit cosmetically ugly though, I'd rather avoid that if possible.

Maybe I could get this working by returning an object from .client() which provides a await obj.get() method: python response = await datasette.client().get("/") I don't think there's any benefit to that over await datasette.client.get() though.

{
    "total_count": 0,
    "+1": 0,
    "-1": 0,
    "laugh": 0,
    "hooray": 0,
    "confused": 0,
    "heart": 0,
    "rocket": 0,
    "eyes": 0
}
await datasette.client.get(path) mechanism for executing internal requests 681375466  
693010291 https://github.com/simonw/datasette/issues/943#issuecomment-693010291 https://api.github.com/repos/simonw/datasette/issues/943 MDEyOklzc3VlQ29tbWVudDY5MzAxMDI5MQ== simonw 9599 2020-09-15T22:20:55Z 2020-09-15T22:20:55Z OWNER

Should I instantiate a single Client and reuse it for all internal requests, or can I instantiate a new Client for each request?

https://www.python-httpx.org/advanced/#why-use-a-client says that the main benefit of a Client instance is HTTP connection pooling - which isn't an issue for these internal requests since they won't be using the HTTP protocol at all, they'll be calling the ASGI application directly.

So I'm leaning towards instantiating a fresh client for every internal request. I'll run a microbenchmark to check that this doesn't have any unpleasant performance implications.

{
    "total_count": 0,
    "+1": 0,
    "-1": 0,
    "laugh": 0,
    "hooray": 0,
    "confused": 0,
    "heart": 0,
    "rocket": 0,
    "eyes": 0
}
await datasette.client.get(path) mechanism for executing internal requests 681375466  
693008540 https://github.com/simonw/datasette/issues/943#issuecomment-693008540 https://api.github.com/repos/simonw/datasette/issues/943 MDEyOklzc3VlQ29tbWVudDY5MzAwODU0MA== simonw 9599 2020-09-15T22:16:07Z 2020-09-15T22:16:07Z OWNER

I think I can use async with httpx.AsyncClient(base_url="http://localhost/") as client: to ensure I don't need to use http://localhost/ on every call.

{
    "total_count": 0,
    "+1": 0,
    "-1": 0,
    "laugh": 0,
    "hooray": 0,
    "confused": 0,
    "heart": 0,
    "rocket": 0,
    "eyes": 0
}
await datasette.client.get(path) mechanism for executing internal requests 681375466  
693007512 https://github.com/simonw/datasette/issues/943#issuecomment-693007512 https://api.github.com/repos/simonw/datasette/issues/943 MDEyOklzc3VlQ29tbWVudDY5MzAwNzUxMg== simonw 9599 2020-09-15T22:13:30Z 2020-09-15T22:13:30Z OWNER

I could solve streaming using something like this: python async with datasette.stream("GET", "/fixtures/compound_three_primary_keys.csv?_stream=on&_size=max") as response: async for chunk in response.aiter_bytes(): print(chunk) Which would be a wrapper around AsyncClient.stream(method, url, ...) from https://www.python-httpx.org/async/#streaming-responses

{
    "total_count": 0,
    "+1": 0,
    "-1": 0,
    "laugh": 0,
    "hooray": 0,
    "confused": 0,
    "heart": 0,
    "rocket": 0,
    "eyes": 0
}
await datasette.client.get(path) mechanism for executing internal requests 681375466  
693005033 https://github.com/simonw/datasette/issues/943#issuecomment-693005033 https://api.github.com/repos/simonw/datasette/issues/943 MDEyOklzc3VlQ29tbWVudDY5MzAwNTAzMw== simonw 9599 2020-09-15T22:06:58Z 2020-09-15T22:10:58Z OWNER

What if datasette.get() was an alias for httpx.get(), pre-configured to route to the correct application? And with some sugar that added http://localhost/ to the beginning of the path if it was missing?

This would make httpx a dependency of core Datasette, which I think is OK.

It would also solve the return type problem: I would return whatever httpx returns.

{
    "total_count": 0,
    "+1": 0,
    "-1": 0,
    "laugh": 0,
    "hooray": 0,
    "confused": 0,
    "heart": 0,
    "rocket": 0,
    "eyes": 0
}
await datasette.client.get(path) mechanism for executing internal requests 681375466  
693004770 https://github.com/simonw/datasette/issues/943#issuecomment-693004770 https://api.github.com/repos/simonw/datasette/issues/943 MDEyOklzc3VlQ29tbWVudDY5MzAwNDc3MA== simonw 9599 2020-09-15T22:06:13Z 2020-09-15T22:06:13Z OWNER

I'm tempted to create a await datasette.request() method which can take any HTTP verb - then have datasette.get() and datasette.post() as thin wrappers around it.

{
    "total_count": 0,
    "+1": 0,
    "-1": 0,
    "laugh": 0,
    "hooray": 0,
    "confused": 0,
    "heart": 0,
    "rocket": 0,
    "eyes": 0
}
await datasette.client.get(path) mechanism for executing internal requests 681375466  
693004572 https://github.com/simonw/datasette/issues/943#issuecomment-693004572 https://api.github.com/repos/simonw/datasette/issues/943 MDEyOklzc3VlQ29tbWVudDY5MzAwNDU3Mg== simonw 9599 2020-09-15T22:05:39Z 2020-09-15T22:05:39Z OWNER

Maybe these methods become the way most Datasette tests are written, replacing the existing TestClient mechanism?

{
    "total_count": 0,
    "+1": 0,
    "-1": 0,
    "laugh": 0,
    "hooray": 0,
    "confused": 0,
    "heart": 0,
    "rocket": 0,
    "eyes": 0
}
await datasette.client.get(path) mechanism for executing internal requests 681375466  
693004296 https://github.com/simonw/datasette/issues/943#issuecomment-693004296 https://api.github.com/repos/simonw/datasette/issues/943 MDEyOklzc3VlQ29tbWVudDY5MzAwNDI5Ng== simonw 9599 2020-09-15T22:04:54Z 2020-09-15T22:04:54Z OWNER

So what should I do about streaming responses?

I could deliberately ignore them - through an exception if you attempt to run await datasette.get(...) against a streaming URL.

I could load the entire response into memory and return it as a wrapped object.

I could support some kind of asynchronous iterator mechanism. This would be pretty elegant if I could decide the right syntax for it - it would allow plugins to take advantage of other internal URLs that return streaming content without needing to load that content entirely into memory in order to process it.

{
    "total_count": 0,
    "+1": 0,
    "-1": 0,
    "laugh": 0,
    "hooray": 0,
    "confused": 0,
    "heart": 0,
    "rocket": 0,
    "eyes": 0
}
await datasette.client.get(path) mechanism for executing internal requests 681375466  
693003652 https://github.com/simonw/datasette/issues/943#issuecomment-693003652 https://api.github.com/repos/simonw/datasette/issues/943 MDEyOklzc3VlQ29tbWVudDY5MzAwMzY1Mg== simonw 9599 2020-09-15T22:03:08Z 2020-09-15T22:03:08Z OWNER

I'm not going to mess around with formats - you'll get back the exact response that a web client would receive.

Question: what should the response object look like? e.g. if you do:

response = await datasette.get("/db/table.json")

What should response be?

I could reuse the Datasette Response class from datasette.utils.asgi. This would work well for regular responses which just have a status code, some headers and a response body. It wouldn't be great for streaming responses though such as you get back from ?_stream=1 CSV exports.

{
    "total_count": 0,
    "+1": 0,
    "-1": 0,
    "laugh": 0,
    "hooray": 0,
    "confused": 0,
    "heart": 0,
    "rocket": 0,
    "eyes": 0
}
await datasette.client.get(path) mechanism for executing internal requests 681375466  

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 61.58ms · About: github-to-sqlite
  • Sort ascending
  • Sort descending
  • Facet by this
  • Hide this column
  • Show all columns
  • Show not-blank rows