home / github

Menu
  • Search all tables
  • GraphQL API

issue_comments

Table actions
  • GraphQL API for issue_comments

12 rows where "updated_at" is on date 2022-12-03 sorted by updated_at descending

✎ View and edit SQL

This data as json, CSV (advanced)

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

issue 4

  • /db/table/-/upsert API 6
  • ignore:true/replace:true options for /db/-/create API 4
  • `datasette create-token` ability to create tokens with a reduced set of permissions 1
  • Typo in JSON API `Updating a row` documentation 1

user 2

  • simonw 11
  • codecov[bot] 1

author_association 2

  • OWNER 11
  • NONE 1
id html_url issue_url node_id user created_at updated_at ▲ author_association body reactions issue performed_via_github_app
1302815929 https://github.com/simonw/datasette/issues/1855#issuecomment-1302815929 https://api.github.com/repos/simonw/datasette/issues/1855 IC_kwDOBm6k_c5Np2S5 simonw 9599 2022-11-04T00:19:10Z 2022-12-03T07:05:51Z OWNER

Added the tests.

{
    "total_count": 0,
    "+1": 0,
    "-1": 0,
    "laugh": 0,
    "hooray": 0,
    "confused": 0,
    "heart": 0,
    "rocket": 0,
    "eyes": 0
}
`datasette create-token` ability to create tokens with a reduced set of permissions 1423336089  
1336100218 https://github.com/simonw/datasette/issues/1878#issuecomment-1336100218 https://api.github.com/repos/simonw/datasette/issues/1878 IC_kwDOBm6k_c5Po0V6 simonw 9599 2022-12-03T07:02:15Z 2022-12-03T07:02:15Z OWNER

Moved this work to a PR: - #1931

{
    "total_count": 0,
    "+1": 0,
    "-1": 0,
    "laugh": 0,
    "hooray": 0,
    "confused": 0,
    "heart": 0,
    "rocket": 0,
    "eyes": 0
}
/db/table/-/upsert API 1432013704  
1336099588 https://github.com/simonw/datasette/issues/1927#issuecomment-1336099588 https://api.github.com/repos/simonw/datasette/issues/1927 IC_kwDOBm6k_c5Po0ME simonw 9599 2022-12-03T06:58:14Z 2022-12-03T06:58:14Z OWNER

I have not yet documented the new insert and replace options.

{
    "total_count": 0,
    "+1": 0,
    "-1": 0,
    "laugh": 0,
    "hooray": 0,
    "confused": 0,
    "heart": 0,
    "rocket": 0,
    "eyes": 0
}
ignore:true/replace:true options for /db/-/create API 1473411197  
1336099533 https://github.com/simonw/datasette/issues/1927#issuecomment-1336099533 https://api.github.com/repos/simonw/datasette/issues/1927 IC_kwDOBm6k_c5Po0LN simonw 9599 2022-12-03T06:57:52Z 2022-12-03T06:57:52Z OWNER

I'm going to push what I have anyway. I'll keep this issue open while I think through the above comment.

{
    "total_count": 0,
    "+1": 0,
    "-1": 0,
    "laugh": 0,
    "hooray": 0,
    "confused": 0,
    "heart": 0,
    "rocket": 0,
    "eyes": 0
}
ignore:true/replace:true options for /db/-/create API 1473411197  
1336099368 https://github.com/simonw/datasette/issues/1927#issuecomment-1336099368 https://api.github.com/repos/simonw/datasette/issues/1927 IC_kwDOBm6k_c5Po0Io simonw 9599 2022-12-03T06:56:36Z 2022-12-03T06:56:36Z OWNER

Neither of these options make sense if you didn't pass a "pk".

My initial implementation spotted if the pk was missing and looked it up from the table, but actually I don't think that makes sense - if you know the table exists and hence don't pass the pk you should be using /-/insert or /-/upsert instead.

So maybe this work should expanded to include validation that checks if the table exists already - and if it does, confirms that the primary key (and maybe even the columns) are the same as for that existing table.

Of course if you only send row or rows then checking columns doesn't completely make sense - but we could check that the rows you have sent are equal to or a subset of the columns in the table. We could even check the column types as well, as seen in: - #1910

{
    "total_count": 0,
    "+1": 0,
    "-1": 0,
    "laugh": 0,
    "hooray": 0,
    "confused": 0,
    "heart": 0,
    "rocket": 0,
    "eyes": 0
}
ignore:true/replace:true options for /db/-/create API 1473411197  
1336094562 https://github.com/simonw/datasette/issues/1878#issuecomment-1336094562 https://api.github.com/repos/simonw/datasette/issues/1878 IC_kwDOBm6k_c5Poy9i simonw 9599 2022-12-03T06:27:50Z 2022-12-03T06:29:06Z OWNER

This adds it to the API explorer:

diff diff --git a/datasette/views/special.py b/datasette/views/special.py index 1f84b094..1b4a9d3c 100644 --- a/datasette/views/special.py +++ b/datasette/views/special.py @@ -316,21 +316,37 @@ class ApiExplorerView(BaseView): request.actor, "insert-row", (name, table) ): pks = await db.primary_keys(table) - table_links.append( - { - "path": self.ds.urls.table(name, table) + "/-/insert", - "method": "POST", - "label": "Insert rows into {}".format(table), - "json": { - "rows": [ - { - column: None - for column in await db.table_columns(table) - if column not in pks - } - ] + table_links.extend( + [ + { + "path": self.ds.urls.table(name, table) + "/-/insert", + "method": "POST", + "label": "Insert rows into {}".format(table), + "json": { + "rows": [ + { + column: None + for column in await db.table_columns(table) + if column not in pks + } + ] + }, }, - } + { + "path": self.ds.urls.table(name, table) + "/-/upsert", + "method": "POST", + "label": "Upsert rows into {}".format(table), + "json": { + "rows": [ + { + column: None + for column in await db.table_columns(table) + if column not in pks + } + ] + }, + }, + ] ) if await self.ds.permission_allowed( request.actor, "drop-table", (name, table) Except it doesn't quite, because the example JSON this generates is invalid as it does not include the primary key column.

(Made me notice that the way example columns are created for /-/insert will fail for tables that don't have an auto-incrementing primary key)

{
    "total_count": 0,
    "+1": 0,
    "-1": 0,
    "laugh": 0,
    "hooray": 0,
    "confused": 0,
    "heart": 0,
    "rocket": 0,
    "eyes": 0
}
/db/table/-/upsert API 1432013704  
1336094470 https://github.com/simonw/datasette/issues/1878#issuecomment-1336094470 https://api.github.com/repos/simonw/datasette/issues/1878 IC_kwDOBm6k_c5Poy8G simonw 9599 2022-12-03T06:27:13Z 2022-12-03T06:27:13Z OWNER

Tests are going to need to cover both rowid-only and compound primary key tables, including all of the error states.

{
    "total_count": 0,
    "+1": 0,
    "-1": 0,
    "laugh": 0,
    "hooray": 0,
    "confused": 0,
    "heart": 0,
    "rocket": 0,
    "eyes": 0
}
/db/table/-/upsert API 1432013704  
1336094381 https://github.com/simonw/datasette/issues/1878#issuecomment-1336094381 https://api.github.com/repos/simonw/datasette/issues/1878 IC_kwDOBm6k_c5Poy6t simonw 9599 2022-12-03T06:26:25Z 2022-12-03T06:26:25Z OWNER

Initial prototype: ```diff diff --git a/datasette/app.py b/datasette/app.py index 125b4969..282c0984 100644 --- a/datasette/app.py +++ b/datasette/app.py @@ -40,7 +40,7 @@ from .views.special import ( PermissionsDebugView, MessagesDebugView, ) -from .views.table import TableView, TableInsertView, TableDropView +from .views.table import TableView, TableInsertView, TableUpsertView, TableDropView from .views.row import RowView, RowDeleteView, RowUpdateView from .renderer import json_renderer from .url_builder import Urls @@ -1292,6 +1292,10 @@ class Datasette: TableInsertView.as_view(self), r"/(?P<database>[^\/.]+)/(?P<table>[^\/.]+)/-/insert$", ) + add_route( + TableUpsertView.as_view(self), + r"/(?P<database>[^\/.]+)/(?P<table>[^\/.]+)/-/upsert$", + ) add_route( TableDropView.as_view(self), r"/(?P<database>[^\/.]+)/(?P<table>[^\/.]+)/-/drop$", diff --git a/datasette/views/table.py b/datasette/views/table.py index 7ba78c11..ae0d6366 100644 --- a/datasette/views/table.py +++ b/datasette/views/table.py @@ -1074,9 +1074,15 @@ class TableInsertView(BaseView): def init(self, datasette): self.ds = datasette

  • async def _validate_data(self, request, db, table_name):
  • async def _validate_data(self, request, db, table_name, pks, upsert): errors = []

  • pks_list = []

  • if isinstance(pks, str):
  • pks_list = [pks]
  • else:
  • pks_list = list(pks) + def _errors(errors): return None, errors, {}

@@ -1135,6 +1141,15 @@ class TableInsertView(BaseView): # Validate columns of each row columns = set(await db.table_columns(table_name)) for i, row in enumerate(rows): + if upsert: + # It MUST have the primary key + missing_pks = [pk for pk in pks_list if pk not in row] + if missing_pks: + errors.append( + 'Row {} is missing primary key column(s): "{}"'.format( + i, '", "'.join(missing_pks) + ) + ) invalid_columns = set(row.keys()) - columns if invalid_columns: errors.append( @@ -1146,7 +1161,7 @@ class TableInsertView(BaseView): return _errors(errors) return rows, errors, extras

  • async def post(self, request):
  • async def post(self, request, upsert=False): try: resolved = await self.ds.resolve_table(request) except NotFound as e: @@ -1164,7 +1179,12 @@ class TableInsertView(BaseView): request.actor, "insert-row", resource=(database_name, table_name) ): return _error(["Permission denied"], 403)
  • rows, errors, extras = await self._validate_data(request, db, table_name) +
  • pks = await db.primary_keys(table_name) +
  • rows, errors, extras = await self._validate_data(
  • request, db, table_name, pks, upsert
  • ) if errors: return _error(errors, 400)

@@ -1172,15 +1192,19 @@ class TableInsertView(BaseView): replace = extras.get("replace")

     should_return = bool(extras.get("return", False))
  • Insert rows

  • def insert_rows(conn): +
  • def insert_or_upsert_rows(conn): table = sqlite_utils.Database(conn)[table_name]
  • kwargs = {}
  • if upsert:
  • kwargs["pk"] = pks[0] if len(pks) == 1 else pks
  • else:
  • kwargs = {"ignore": ignore, "replace": replace} if should_return: rowids = []
  • method = table.upsert if upsert else table.insert for row in rows:
  • rowids.append(
  • table.insert(row, ignore=ignore, replace=replace).last_rowid
  • )
  • rowids.append(method(row, **kwargs).last_rowid) return list( table.rows_where( "rowid in ({})".format(",".join("?" for _ in rowids)), @@ -1188,10 +1212,11 @@ class TableInsertView(BaseView): ) ) else:
  • table.insert_all(rows, ignore=ignore, replace=replace)
  • method_all = table.upsert_all if upsert else table.insert_all
  • method_all(rows, **kwargs)
     try:
    
    • rows = await db.execute_write_fn(insert_rows)
    • rows = await db.execute_write_fn(insert_or_upsert_rows) except Exception as e: return _error([str(e)]) result = {"ok": True} @@ -1200,6 +1225,13 @@ class TableInsertView(BaseView): return Response.json(result, status=201)

+class TableUpsertView(TableInsertView): + name = "table-upsert" + + async def post(self, request): + return await super().post(request, upsert=True) + + class TableDropView(BaseView): name = "table-drop" `` Manual testing reveals that this mostly works... but it's not doing the right thing for"return": true` - it always returns an empty list.

{
    "total_count": 0,
    "+1": 0,
    "-1": 0,
    "laugh": 0,
    "hooray": 0,
    "confused": 0,
    "heart": 0,
    "rocket": 0,
    "eyes": 0
}
/db/table/-/upsert API 1432013704  
1336073212 https://github.com/simonw/datasette/issues/1878#issuecomment-1336073212 https://api.github.com/repos/simonw/datasette/issues/1878 IC_kwDOBm6k_c5Potv8 simonw 9599 2022-12-03T05:38:49Z 2022-12-03T05:38:49Z OWNER

And on Discord today: https://discord.com/channels/823971286308356157/823971286941302908/1048426072066236536

{
    "total_count": 0,
    "+1": 0,
    "-1": 0,
    "laugh": 0,
    "hooray": 0,
    "confused": 0,
    "heart": 0,
    "rocket": 0,
    "eyes": 0
}
/db/table/-/upsert API 1432013704  
1336070843 https://github.com/simonw/datasette/issues/1878#issuecomment-1336070843 https://api.github.com/repos/simonw/datasette/issues/1878 IC_kwDOBm6k_c5PotK7 simonw 9599 2022-12-03T05:37:53Z 2022-12-03T05:37:53Z OWNER

Also requested here: https://news.ycombinator.com/item?id=33839894

{
    "total_count": 0,
    "+1": 0,
    "-1": 0,
    "laugh": 0,
    "hooray": 0,
    "confused": 0,
    "heart": 0,
    "rocket": 0,
    "eyes": 0
}
/db/table/-/upsert API 1432013704  
1336017976 https://github.com/simonw/datasette/pull/1930#issuecomment-1336017976 https://api.github.com/repos/simonw/datasette/issues/1930 IC_kwDOBm6k_c5PogQ4 codecov[bot] 22429695 2022-12-03T02:30:21Z 2022-12-03T02:30:21Z NONE

Codecov Report

Base: 90.42% // Head: 90.42% // No change to project coverage :thumbsup:

Coverage data is based on head (9928ff1) compared to base (cab5b60). Patch has no changes to coverable lines.

Additional details and impacted files ```diff @@ Coverage Diff @@ ## main #1930 +/- ## ======================================= Coverage 90.42% 90.42% ======================================= Files 36 36 Lines 5057 5057 ======================================= Hits 4573 4573 Misses 484 484 ``` Help us with your feedback. Take ten seconds to tell us [how you rate us](https://about.codecov.io/nps?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Simon+Willison). Have a feature suggestion? [Share it here.](https://app.codecov.io/gh/feedback/?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Simon+Willison)

:umbrella: View full report at Codecov.
:loudspeaker: Do you have feedback about the report comment? Let us know in this issue.

{
    "total_count": 0,
    "+1": 0,
    "-1": 0,
    "laugh": 0,
    "hooray": 0,
    "confused": 0,
    "heart": 0,
    "rocket": 0,
    "eyes": 0
}
Typo in JSON API `Updating a row` documentation 1473664029  
1335984268 https://github.com/simonw/datasette/issues/1927#issuecomment-1335984268 https://api.github.com/repos/simonw/datasette/issues/1927 IC_kwDOBm6k_c5PoYCM simonw 9599 2022-12-03T00:26:26Z 2022-12-03T00:26:26Z OWNER

Also: the documentation should clarify that you can call this API multiple times when using the rows option.

(It will probably grow "alter": true soon too).

{
    "total_count": 0,
    "+1": 0,
    "-1": 0,
    "laugh": 0,
    "hooray": 0,
    "confused": 0,
    "heart": 0,
    "rocket": 0,
    "eyes": 0
}
ignore:true/replace:true options for /db/-/create API 1473411197  

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 515.151ms · About: github-to-sqlite