issue_comments
7,931 rows where author_association = "OWNER" sorted by id descending
This data as json, CSV (advanced)
Suggested facets: reactions, created_at (date)
id ▲ | html_url | issue_url | node_id | user | created_at | updated_at | author_association | body | reactions | issue | performed_via_github_app |
---|---|---|---|---|---|---|---|---|---|---|---|
1640064620 | https://github.com/simonw/datasette/issues/2102#issuecomment-1640064620 | https://api.github.com/repos/simonw/datasette/issues/2102 | IC_kwDOBm6k_c5hwWZs | simonw 9599 | 2023-07-18T11:47:21Z | 2023-07-18T11:47:21Z | OWNER | I think I've figured out the problem here. The question being asked is "can this actor access this resource, which is within this database within this instance". The answer to this question needs to consider the full set of questions at once - yes they can access within this instance IF they have access to the specified table and that's the table being asked about. But the questions are currently being asked independently, which means the plugin hook acting on `view-instance` can't see that the answer here should be yes because it's actually about a table that the actor has explicit permission to view. So I think I may need to redesign the plugin hook to always see the full hierarchy of checks, not just a single check at a time. | {"total_count": 0, "+1": 0, "-1": 0, "laugh": 0, "hooray": 0, "confused": 0, "heart": 0, "rocket": 0, "eyes": 0} | API tokens with view-table but not view-database/view-instance cannot access the table 1805076818 | |
1638926655 | https://github.com/simonw/sqlite-utils/issues/567#issuecomment-1638926655 | https://api.github.com/repos/simonw/sqlite-utils/issues/567 | IC_kwDOCGYnMM5hsAk_ | simonw 9599 | 2023-07-17T21:42:37Z | 2023-07-17T21:42:37Z | OWNER | I really like this. I'm also interested in: - Plugins that make new custom SQL functions available - similar to this Datasette hook: https://docs.datasette.io/en/stable/plugin_hooks.html#prepare-connection-conn-database-datasette - Plugins that register functions that can be used as recipes for `sqlite-utils convert` https://sqlite-utils.datasette.io/en/stable/cli.html#sqlite-utils-convert-recipes The upload-data-to-Datasette problem is planned to be solved by a future version of https://github.com/simonw/dclient | {"total_count": 0, "+1": 0, "-1": 0, "laugh": 0, "hooray": 0, "confused": 0, "heart": 0, "rocket": 0, "eyes": 0} | Plugin system 1801394744 | |
1638567228 | https://github.com/simonw/datasette/issues/2102#issuecomment-1638567228 | https://api.github.com/repos/simonw/datasette/issues/2102 | IC_kwDOBm6k_c5hqo08 | simonw 9599 | 2023-07-17T17:24:19Z | 2023-07-17T17:25:12Z | OWNER | Confirmed that this is an issue with regular Datasette signed tokens as well. I created one on https://latest.datasette.io/-/create-token with these details: ```json { "_r": { "r": { "fixtures": { "sortable": [ "vt" ] } } }, "a": "root", "d": 3600, "t": 1689614483 } ``` Run like this: ``` curl -H 'Authorization: Bearer dstok_eyJhIjoicm9vdCIsInQiOjE2ODk2MTQ0ODMsImQiOjM2MDAsIl9yIjp7InIiOnsiZml4dHVyZXMiOnsic29ydGFibGUiOlsidnQiXX19fX0.n-VGxxawz1Q0WK7sqLfhXUgcvY0' \ https://latest.datasette.io/fixtures/sortable.json ``` Returned an HTML Forbidden page: ```html <!DOCTYPE html> <html> <head> <title>Forbidden</title> <link rel="stylesheet" href="/-/static/app.css?d59929"> ... ``` Same token againts `/-/actor.json` returns: ```json { "actor": { "id": "root", "token": "dstok", "_r": { "r": { "fixtures": { "sortable": [ "vt" ] } } }, "token_expires": 1689618083 } } ``` Reminder - `"_r"` means restrict, `"r"` means resource. | {"total_count": 0, "+1": 0, "-1": 0, "laugh": 0, "hooray": 0, "confused": 0, "heart": 0, "rocket": 0, "eyes": 0} | API tokens with view-table but not view-database/view-instance cannot access the table 1805076818 | |
1638552567 | https://github.com/simonw/datasette/issues/2104#issuecomment-1638552567 | https://api.github.com/repos/simonw/datasette/issues/2104 | IC_kwDOBm6k_c5hqlP3 | simonw 9599 | 2023-07-17T17:14:20Z | 2023-07-17T17:14:20Z | OWNER | Relevant code: https://github.com/simonw/datasette/blob/0f7192b6154edb576c41b55bd3f2a3f53e5f436a/datasette/database.py#L391-L451 | {"total_count": 0, "+1": 0, "-1": 0, "laugh": 0, "hooray": 0, "confused": 0, "heart": 0, "rocket": 0, "eyes": 0} | Tables starting with an underscore should be treated as hidden 1808215339 | |
1636093730 | https://github.com/simonw/datasette/issues/2102#issuecomment-1636093730 | https://api.github.com/repos/simonw/datasette/issues/2102 | IC_kwDOBm6k_c5hhM8i | simonw 9599 | 2023-07-14T16:26:27Z | 2023-07-14T16:32:49Z | OWNER | Here's that crucial comment: > If _r is defined then we use those to further restrict the actor. > >Crucially, we only use this to say NO (return False) - we never use it to return YES (True) because that might over-ride other restrictions placed on this actor So that's why I implemented it like this. The goal here is to be able to issue a token which can't do anything _more_ than the actor it is associated with, but CAN be configured to do less. So I think the solution here is for the `_r` checking code to perhaps implement its own view cascade logic - it notices if you have `view-table` and consequently fails to block `view-table` and `view-instance`. I'm not sure that's going to work though - would that mean that granting `view-table` grants `view-database` in a surprising and harmful way? Maybe that's OK: if you have `view-database` but permission checks fail for individual tables and queries you shouldn't be able to see a thing that you shouldn't. Need to verify that though. Also, do `Permission` instances have enough information to implement this kind of cascade without hard-coding anything? | {"total_count": 0, "+1": 0, "-1": 0, "laugh": 0, "hooray": 0, "confused": 0, "heart": 0, "rocket": 0, "eyes": 0} | API tokens with view-table but not view-database/view-instance cannot access the table 1805076818 | |
1636053060 | https://github.com/simonw/datasette/issues/2102#issuecomment-1636053060 | https://api.github.com/repos/simonw/datasette/issues/2102 | IC_kwDOBm6k_c5hhDBE | simonw 9599 | 2023-07-14T15:51:36Z | 2023-07-14T16:14:05Z | OWNER | This might only be an issue with the code that checks `_r` on actors. https://github.com/simonw/datasette/blob/0f7192b6154edb576c41b55bd3f2a3f53e5f436a/datasette/default_permissions.py#L185-L222 Added in https://github.com/simonw/datasette/commit/bcc781f4c50a8870e3389c4e60acb625c34b0317 - refs: - #1855 | {"total_count": 0, "+1": 0, "-1": 0, "laugh": 0, "hooray": 0, "confused": 0, "heart": 0, "rocket": 0, "eyes": 0} | API tokens with view-table but not view-database/view-instance cannot access the table 1805076818 | |
1636042066 | https://github.com/simonw/datasette/issues/2102#issuecomment-1636042066 | https://api.github.com/repos/simonw/datasette/issues/2102 | IC_kwDOBm6k_c5hhAVS | simonw 9599 | 2023-07-14T15:41:54Z | 2023-07-14T15:42:32Z | OWNER | I tried some code spelunking and came across https://github.com/simonw/datasette/commit/d6e03b04302a0852e7133dc030eab50177c37be7 which says: > - If you have table permission but not database permission you can now view the table page Refs: - #832 Which suggests that my initial design decision wasn't what appears to be implemented today. Needs more investigation. | {"total_count": 0, "+1": 0, "-1": 0, "laugh": 0, "hooray": 0, "confused": 0, "heart": 0, "rocket": 0, "eyes": 0} | API tokens with view-table but not view-database/view-instance cannot access the table 1805076818 | |
1636040164 | https://github.com/simonw/datasette/issues/2102#issuecomment-1636040164 | https://api.github.com/repos/simonw/datasette/issues/2102 | IC_kwDOBm6k_c5hg_3k | simonw 9599 | 2023-07-14T15:40:21Z | 2023-07-14T15:40:21Z | OWNER | Relevant code: https://github.com/simonw/datasette/blob/0f7192b6154edb576c41b55bd3f2a3f53e5f436a/datasette/app.py#L822-L855 | {"total_count": 0, "+1": 0, "-1": 0, "laugh": 0, "hooray": 0, "confused": 0, "heart": 0, "rocket": 0, "eyes": 0} | API tokens with view-table but not view-database/view-instance cannot access the table 1805076818 | |
1636036312 | https://github.com/simonw/datasette/issues/2102#issuecomment-1636036312 | https://api.github.com/repos/simonw/datasette/issues/2102 | IC_kwDOBm6k_c5hg-7Y | simonw 9599 | 2023-07-14T15:37:14Z | 2023-07-14T15:37:14Z | OWNER | I think I made this decision because I was thinking about default deny: obviously if a user has been denied access to a database. It doesn't make sense that they could access tables within it. But now that I am spending more time with authentication tokens, which default to denying everything, except for the things that you have explicitly listed, this policy, no longer makes as much sense. | {"total_count": 0, "+1": 0, "-1": 0, "laugh": 0, "hooray": 0, "confused": 0, "heart": 0, "rocket": 0, "eyes": 0} | API tokens with view-table but not view-database/view-instance cannot access the table 1805076818 | |
1634443907 | https://github.com/simonw/datasette/issues/2101#issuecomment-1634443907 | https://api.github.com/repos/simonw/datasette/issues/2101 | IC_kwDOBm6k_c5ha6KD | simonw 9599 | 2023-07-13T15:24:17Z | 2023-07-13T15:24:17Z | OWNER | https://github.com/simonw/datasette/blob/0f7192b6154edb576c41b55bd3f2a3f53e5f436a/datasette/views/table.py#L486-L506 | {"total_count": 0, "+1": 0, "-1": 0, "laugh": 0, "hooray": 0, "confused": 0, "heart": 0, "rocket": 0, "eyes": 0} | alter: true support for JSON write API 1803264272 | |
1629337927 | https://github.com/simonw/datasette/pull/2052#issuecomment-1629337927 | https://api.github.com/repos/simonw/datasette/issues/2052 | IC_kwDOBm6k_c5hHblH | simonw 9599 | 2023-07-10T16:43:38Z | 2023-07-10T16:44:23Z | OWNER | I tried running this locally just now. I made one edit: ```diff diff --git a/demos/plugins/example_js_manager_plugins.py b/demos/plugins/example_js_manager_plugins.py index 7bdb9f3f..f9dfa8e6 100644 --- a/demos/plugins/example_js_manager_plugins.py +++ b/demos/plugins/example_js_manager_plugins.py @@ -15,6 +15,6 @@ def extra_js_urls(view_name): if view_name in PERMITTED_VIEWS: return [ { - "url": f"/-/demos/plugins/static/table-example-plugins.js", + "url": f"/static/table-example-plugins.js", } ] ``` And then started it running like this: ```bash wget https://datasette.io/content.db ``` ```bash datasette content.db \ --plugins-dir demos/plugins \ --static static:datasette/demos/plugins/static ``` It didn't quite work for me - I got this error on a table page: <img width="681" alt="image" src="https://github.com/simonw/datasette/assets/9599/02f5c16d-ea43-4c36-920d-4b14406645b5"> And this error on a query page: <img width="689" alt="image" src="https://github.com/simonw/datasette/assets/9599/994db3a0-502a-4f36-8581-c47556600d63"> | {"total_count": 0, "+1": 0, "-1": 0, "laugh": 0, "hooray": 0, "confused": 0, "heart": 0, "rocket": 0, "eyes": 0} | feat: Javascript Plugin API (Custom panels, column menu items with JS actions) 1651082214 | |
1627598570 | https://github.com/simonw/sqlite-utils/issues/566#issuecomment-1627598570 | https://api.github.com/repos/simonw/sqlite-utils/issues/566 | IC_kwDOCGYnMM5hAy7q | simonw 9599 | 2023-07-09T04:13:34Z | 2023-07-09T04:13:34Z | OWNER | On consulting https://pypi.org/project/tabulate/ it looks like most of those formats don't actually makes sense without headers - so the right thing here might be to raise an error if `--fmt` and `--no-headers` are used at the same time. | {"total_count": 0, "+1": 0, "-1": 0, "laugh": 0, "hooray": 0, "confused": 0, "heart": 0, "rocket": 0, "eyes": 0} | `--no-headers` doesn't work on most formats 1795219865 | |
1627597872 | https://github.com/simonw/sqlite-utils/issues/566#issuecomment-1627597872 | https://api.github.com/repos/simonw/sqlite-utils/issues/566 | IC_kwDOCGYnMM5hAyww | simonw 9599 | 2023-07-09T04:09:56Z | 2023-07-09T04:09:56Z | OWNER | Thanks, looks like a bug. | {"total_count": 0, "+1": 0, "-1": 0, "laugh": 0, "hooray": 0, "confused": 0, "heart": 0, "rocket": 0, "eyes": 0} | `--no-headers` doesn't work on most formats 1795219865 | |
1627480353 | https://github.com/simonw/datasette/issues/1153#issuecomment-1627480353 | https://api.github.com/repos/simonw/datasette/issues/1153 | IC_kwDOBm6k_c5hAWEh | simonw 9599 | 2023-07-08T20:09:48Z | 2023-07-08T20:09:48Z | OWNER | https://docs.datasette.io/en/latest/writing_plugins.html#writing-plugins-that-accept-configuration is fixed now. | {"total_count": 0, "+1": 0, "-1": 0, "laugh": 0, "hooray": 0, "confused": 0, "heart": 0, "rocket": 0, "eyes": 0} | Use YAML examples in documentation by default, not JSON 771202454 | |
1627478910 | https://github.com/simonw/datasette/issues/1153#issuecomment-1627478910 | https://api.github.com/repos/simonw/datasette/issues/1153 | IC_kwDOBm6k_c5hAVt- | simonw 9599 | 2023-07-08T20:01:19Z | 2023-07-08T20:01:19Z | OWNER | Some examples: - https://docs.datasette.io/en/latest/sql_queries.html#canned-queries - https://docs.datasette.io/en/latest/sql_queries.html#canned-query-parameters - https://docs.datasette.io/en/latest/authentication.html#access-to-an-instance - https://docs.datasette.io/en/latest/facets.html#facets-in-metadata - https://docs.datasette.io/en/latest/full_text_search.html#configuring-full-text-search-for-a-table-or-view - https://docs.datasette.io/en/latest/metadata.html - https://docs.datasette.io/en/latest/custom_templates.html#custom-css-and-javascript - https://docs.datasette.io/en/latest/plugins.html#plugin-configuration I need to fix this section: https://docs.datasette.io/en/latest/writing_plugins.html#writing-plugins-that-accept-configuration | {"total_count": 0, "+1": 0, "-1": 0, "laugh": 0, "hooray": 0, "confused": 0, "heart": 0, "rocket": 0, "eyes": 0} | Use YAML examples in documentation by default, not JSON 771202454 | |
1627455892 | https://github.com/simonw/datasette/issues/1153#issuecomment-1627455892 | https://api.github.com/repos/simonw/datasette/issues/1153 | IC_kwDOBm6k_c5hAQGU | simonw 9599 | 2023-07-08T18:39:19Z | 2023-07-08T18:39:19Z | OWNER | ``` ERROR: Could not find a version that satisfies the requirement Sphinx==6.1.3; extra == "docs" (from datasette[docs,test]) (from versions: 0.1.61611, 0.1.61798, 0.1.61843, 0.1.61945, 0.1.61950, 0.2, 0.3, 0.4, 0.4.1, 0.4.2, 0.4.3, 0.5, 0.5.1, 0.5.2b1, 0.5.2, 0.6b1, 0.6, 0.6.1, 0.6.2, 0.6.3, 0.6.4, 0.6.5, 0.6.6, 0.6.7, 1.0b1, 1.0b2, 1.0, 1.0.1, 1.0.2, 1.0.3, 1.0.4, 1.0.5, 1.0.6, 1.0.7, 1.0.8, 1.1, 1.1.1, 1.1.2, 1.1.3, 1.2b1, 1.2b2, 1.2b3, 1.2, 1.2.1, 1.2.2, 1.2.3, 1.3b1, 1.3b2, 1.3b3, 1.3, 1.3.1, 1.3.2, 1.3.3, 1.3.4, 1.3.5, 1.3.6, 1.4a1, 1.4b1, 1.4, 1.4.1, 1.4.2, 1.4.3, 1.4.4, 1.4.5, 1.4.6, 1.4.7, 1.4.8, 1.4.9, 1.5a1, 1.5a2, 1.5b1, 1.5, 1.5.1, 1.5.2, 1.5.3, 1.5.4, 1.5.5, 1.5.6, 1.6b1, 1.6b2, 1.6b3, 1.6.1, 1.6.2, 1.6.3, 1.6.4, 1.6.5, 1.6.6, 1.6.7, 1.7.0b1, 1.7.0b2, 1.7.0, 1.7.1, 1.7.2, 1.7.3, 1.7.4, 1.7.5, 1.7.6, 1.7.7, 1.7.8, 1.7.9, 1.8.0b1, 1.8.0, 1.8.1, 1.8.2, 1.8.3, 1.8.4, 1.8.5, 1.8.6, 2.0.0b1, 2.0.0b2, 2.0.0, 2.0.1, 2.1.0, 2.1.1, 2.1.2, 2.2.0, 2.2.1, 2.2.2, 2.3.0, 2.3.1, 2.4.0, 2.4.1, 2.4.2, 2.4.3, 2.4.4, 2.4.5, 3.0.0b1, 3.0.0, 3.0.1, 3.0.2, 3.0.3, 3.0.4, 3.1.0, 3.1.1, 3.1.2, 3.2.0, 3.2.1, 3.3.0, 3.3.1, 3.4.0, 3.4.1, 3.4.2, 3.4.3, 3.5.0, 3.5.1, 3.5.2, 3.5.3, 3.5.4, 4.0.0b1, 4.0.0b2, 4.0.0, 4.0.1, 4.0.2, 4.0.3, 4.1.0, 4.1.1, 4.1.2, 4.2.0, 4.3.0, 4.3.1, 4.3.2, 4.4.0, 4.5.0, 5.0.0b1, 5.0.0, 5.0.1, 5.0.2, 5.1.0, 5.1.1, 5.2.0, 5.2.0.post0, 5.2.1, 5.2.2, 5.2.3, 5.3.0) ERROR: No matching distribution found for Sphinx==6.1.3; extra == "docs" ``` I'm going to drop Python 3.7. | {"total_count": 0, "+1": 0, "-1": 0, "laugh": 0, "hooray": 0, "confused": 0, "heart": 0, "rocket": 0, "eyes": 0} | Use YAML examples in documentation by default, not JSON 771202454 | |
1627451646 | https://github.com/simonw/datasette/issues/1153#issuecomment-1627451646 | https://api.github.com/repos/simonw/datasette/issues/1153 | IC_kwDOBm6k_c5hAPD- | simonw 9599 | 2023-07-08T18:21:24Z | 2023-07-08T18:21:24Z | OWNER | This one was tricky: <img width="764" alt="image" src="https://github.com/simonw/datasette/assets/9599/0fe2886b-4e07-4539-a254-4c47f02c3f65"> I wanted complete control over the YAML example here, so I could ensure it used multi-line strings correctly. I ended up changing my cog helper function to this: ```python import json import textwrap from yaml import safe_dump from ruamel.yaml import round_trip_load def metadata_example(cog, data=None, yaml=None): assert data or yaml, "Must provide data= or yaml=" assert not (data and yaml), "Cannot use data= and yaml=" output_yaml = None if yaml: # dedent it first yaml = textwrap.dedent(yaml).strip() # round_trip_load to preserve key order: data = round_trip_load(yaml) output_yaml = yaml else: output_yaml = safe_dump(data, sort_keys=False) cog.out("\n.. tab:: YAML\n\n") cog.out(" .. code-block:: yaml\n\n") cog.out(textwrap.indent(output_yaml, " ")) cog.out("\n\n.. tab:: JSON\n\n") cog.out(" .. code-block:: json\n\n") cog.out(textwrap.indent(json.dumps(data, indent=2), " ")) cog.out("\n") ``` This allows me to call it ith YAML in some places: ``` .. [[[cog metadata_example(cog, yaml=""" databases: fixtures: queries: neighborhood_search: fragment: fragment-goes-here hide_sql: true sql: |- select neighborhood, facet_cities.name, state from facetable join facet_cities on facetable.city_id = facet_cities.id where neighborhood like '%' || :text || '%' order by neighborhood; """) .. ]]] ``` I had to introduce https://pypi.org/project/ruamel.yaml/ as a dependency here in order to load YAML from disk while maintaining key order. I'm still using `safe_dump(data, sort_keys=False)` from PyYAML as I couldn't get the result I wanted for outputting YAML from an input of JSON using PyYAML. | {"total_count": 0, "+1": 0, "-1": 0, "laugh": 0, "hooray": 0, "confused": 0, "heart": 0, "rocket": 0, "eyes": 0} | Use YAML examples in documentation by default, not JSON 771202454 | |
1627450852 | https://github.com/simonw/datasette/issues/1153#issuecomment-1627450852 | https://api.github.com/repos/simonw/datasette/issues/1153 | IC_kwDOBm6k_c5hAO3k | simonw 9599 | 2023-07-08T18:17:35Z | 2023-07-08T18:17:35Z | OWNER | I figured out a workaround: ```python extensions = [ "sphinx.ext.extlinks", "sphinx.ext.autodoc", "sphinx_copybutton", ] if not os.environ.get("DISABLE_SPHINX_INLINE_TABS"): extensions += ["sphinx_inline_tabs"] ``` That way I can run `sphinx-build -b xml . _build` successfully if I set that environment variable. I get some noisy warnings, but it runs OK. And the resulting `docs.db` file has rows like this, which I think are fine: <img width="1533" alt="image" src="https://github.com/simonw/datasette/assets/9599/0a289f53-2c6c-43e9-bda1-d4380383a02f"> | {"total_count": 0, "+1": 0, "-1": 0, "laugh": 0, "hooray": 0, "confused": 0, "heart": 0, "rocket": 0, "eyes": 0} | Use YAML examples in documentation by default, not JSON 771202454 | |
1627448542 | https://github.com/simonw/datasette/issues/1153#issuecomment-1627448542 | https://api.github.com/repos/simonw/datasette/issues/1153 | IC_kwDOBm6k_c5hAOTe | simonw 9599 | 2023-07-08T18:05:44Z | 2023-07-08T18:05:44Z | OWNER | Running with `-P` opens a debugger when it hits the error: ```bash sphinx-build -P -b xml . _build ``` ``` (Pdb) list 2023 2024 Raise an exception unless overridden. 2025 """ 2026 if (self.document.settings.strict_visitor 2027 or node.__class__.__name__ not in self.optional): 2028 -> raise NotImplementedError( 2029 '%s visiting unknown node type: %s' 2030 % (self.__class__, node.__class__.__name__)) 2031 2032 def unknown_departure(self, node): 2033 """ (Pdb) self.optional ('meta',) (Pdb) node.__class__.__name__ 'TabContainer' (Pdb) self.document.settings.strict_visitor (Pdb) type(self.document.settings.strict_visitor) <class 'NoneType'> ``` So if I can get `TabContainer` into that `self.optional` list I'll have fixed this problem. | {"total_count": 0, "+1": 0, "-1": 0, "laugh": 0, "hooray": 0, "confused": 0, "heart": 0, "rocket": 0, "eyes": 0} | Use YAML examples in documentation by default, not JSON 771202454 | |
1627448180 | https://github.com/simonw/datasette/issues/1153#issuecomment-1627448180 | https://api.github.com/repos/simonw/datasette/issues/1153 | IC_kwDOBm6k_c5hAON0 | simonw 9599 | 2023-07-08T18:03:31Z | 2023-07-08T18:03:31Z | OWNER | Relevant code: https://github.com/docutils/docutils/blob/3b53ded52bc439d8068b6ecb20ea0a761247e479/docutils/docutils/nodes.py#L2021-L2031 ```python def unknown_visit(self, node): """ Called when entering unknown `Node` types. Raise an exception unless overridden. """ if (self.document.settings.strict_visitor or node.__class__.__name__ not in self.optional): raise NotImplementedError( '%s visiting unknown node type: %s' % (self.__class__, node.__class__.__name__)) ``` | {"total_count": 0, "+1": 0, "-1": 0, "laugh": 0, "hooray": 0, "confused": 0, "heart": 0, "rocket": 0, "eyes": 0} | Use YAML examples in documentation by default, not JSON 771202454 | |
1627447750 | https://github.com/simonw/datasette/issues/1153#issuecomment-1627447750 | https://api.github.com/repos/simonw/datasette/issues/1153 | IC_kwDOBm6k_c5hAOHG | simonw 9599 | 2023-07-08T18:00:56Z | 2023-07-08T18:00:56Z | OWNER | Actually no it's in `sphinx-build`: ``` % sphinx-build -b xml . _build Running Sphinx v6.1.3 building [mo]: targets for 0 po files that are out of date writing output... building [xml]: targets for 28 source files that are out of date updating environment: [new config] 28 added, 0 changed, 0 removed reading sources... [100%] writing_plugins looking for now-outdated files... none found pickling environment... done checking consistency... done preparing documents... done writing output... [ 3%] authentication Exception occurred: File "/Users/simon/.local/share/virtualenvs/datasette-AWNrQs95/lib/python3.10/site-packages/docutils/nodes.py", line 2028, in unknown_visit raise NotImplementedError( NotImplementedError: <class 'docutils.writers.docutils_xml.XMLTranslator'> visiting unknown node type: TabContainer The full traceback has been saved in /var/folders/x6/31xf1vxj0nn9mxqq8z0mmcfw0000gn/T/sphinx-err-1wkxmkji.log, if you want to report the issue to the developers. Please also report this if it was a user error, so that a better error message can be provided next time. A bug report can be filed in the tracker at <https://github.com/sphinx-doc/sphinx/issues>. Thanks! ``` | {"total_count": 0, "+1": 0, "-1": 0, "laugh": 0, "hooray": 0, "confused": 0, "heart": 0, "rocket": 0, "eyes": 0} | Use YAML examples in documentation by default, not JSON 771202454 | |
1627447478 | https://github.com/simonw/datasette/issues/1153#issuecomment-1627447478 | https://api.github.com/repos/simonw/datasette/issues/1153 | IC_kwDOBm6k_c5hAOC2 | simonw 9599 | 2023-07-08T17:59:25Z | 2023-07-08T17:59:25Z | OWNER | Hit a problem: ``` Exception occurred: File "/opt/hostedtoolcache/Python/3.9.17/x64/lib/python3.9/site-packages/docutils/nodes.py", line 2028, in unknown_visit raise NotImplementedError( NotImplementedError: <class 'docutils.writers.docutils_xml.XMLTranslator'> visiting unknown node type: TabContainer The full traceback has been saved in /tmp/sphinx-err-tfujyw1h.log, if you want to report the issue to the developers. Please also report this if it was a user error, so that a better error message can be provided next time. A bug report can be filed in the tracker at <https://github.com/sphinx-doc/sphinx/issues>. Thanks! ``` That's happening here: https://github.com/simonw/datasette/blob/0183e1a72d4d93b1d9a9363f4d47fcc0b5d5849c/.github/workflows/deploy-latest.yml#L42-L48 My https://github.com/simonw/sphinx-to-sqlite tool can't handle the new `TabContainer` elements introduced by `sphinx-inline-tabs`. | {"total_count": 0, "+1": 0, "-1": 0, "laugh": 0, "hooray": 0, "confused": 0, "heart": 0, "rocket": 0, "eyes": 0} | Use YAML examples in documentation by default, not JSON 771202454 | |
1627396658 | https://github.com/simonw/datasette/issues/1153#issuecomment-1627396658 | https://api.github.com/repos/simonw/datasette/issues/1153 | IC_kwDOBm6k_c5hABoy | simonw 9599 | 2023-07-08T16:40:07Z | 2023-07-08T16:40:07Z | OWNER | https://docs.datasette.io/en/latest/metadata.html ![inline-tabs](https://github.com/simonw/datasette/assets/9599/975bdff5-74ac-451e-92c3-a7dd05d4b862) | {"total_count": 0, "+1": 0, "-1": 0, "laugh": 0, "hooray": 0, "confused": 0, "heart": 0, "rocket": 0, "eyes": 0} | Use YAML examples in documentation by default, not JSON 771202454 | |
1627396450 | https://github.com/simonw/datasette/issues/1153#issuecomment-1627396450 | https://api.github.com/repos/simonw/datasette/issues/1153 | IC_kwDOBm6k_c5hABli | simonw 9599 | 2023-07-08T16:38:58Z | 2023-07-08T16:38:58Z | OWNER | I'm using `cog` and this utility function to generate the YAML/JSON tabs: https://github.com/simonw/datasette/blob/3b336d8071fb5707bd006de1d614f701d20246a3/docs/metadata_doc.py#L1-L13 Example usage: https://github.com/simonw/datasette/blob/3b336d8071fb5707bd006de1d614f701d20246a3/docs/metadata.rst?plain=1#L17-L53 | {"total_count": 0, "+1": 0, "-1": 0, "laugh": 0, "hooray": 0, "confused": 0, "heart": 0, "rocket": 0, "eyes": 0} | Use YAML examples in documentation by default, not JSON 771202454 | |
1627395947 | https://github.com/simonw/datasette/issues/1153#issuecomment-1627395947 | https://api.github.com/repos/simonw/datasette/issues/1153 | IC_kwDOBm6k_c5hABdr | simonw 9599 | 2023-07-08T16:35:45Z | 2023-07-08T16:35:45Z | OWNER | I was inspired to finally address this after seeing `sphinx-inline-tabs` at work in https://webcolors.readthedocs.io/en/latest/install.html | {"total_count": 0, "+1": 0, "-1": 0, "laugh": 0, "hooray": 0, "confused": 0, "heart": 0, "rocket": 0, "eyes": 0} | Use YAML examples in documentation by default, not JSON 771202454 | |
1618380888 | https://github.com/simonw/sqlite-utils/issues/565#issuecomment-1618380888 | https://api.github.com/repos/simonw/sqlite-utils/issues/565 | IC_kwDOCGYnMM5gdohY | simonw 9599 | 2023-07-03T14:09:11Z | 2023-07-03T14:09:31Z | OWNER | For the CLI: ```bash sqlite-utils rename-table data.db old_table_name new_table_name ``` For the Python code, should it go on Table or on Database? ```python db["foo"].rename_table("bar") db.rename_table("foo", "bar") ``` I think I like the second better, it's slightly more clear. Also need a design for an option for the `.transform()` method to indicate that the new table should be created with a new name without dropping the old one. | {"total_count": 0, "+1": 0, "-1": 0, "laugh": 0, "hooray": 0, "confused": 0, "heart": 0, "rocket": 0, "eyes": 0} | Table renaming utilities 1786258502 | |
1617395444 | https://github.com/simonw/sqlite-utils/issues/563#issuecomment-1617395444 | https://api.github.com/repos/simonw/sqlite-utils/issues/563 | IC_kwDOCGYnMM5gZ370 | simonw 9599 | 2023-07-03T05:44:43Z | 2023-07-03T05:44:43Z | OWNER | Documentation at the bottom of this section: https://sqlite-utils.datasette.io/en/latest/cli.html#inserting-csv-or-tsv-data | {"total_count": 0, "+1": 0, "-1": 0, "laugh": 0, "hooray": 0, "confused": 0, "heart": 0, "rocket": 0, "eyes": 0} | `--empty-null` option when importing CSV 1785360409 | |
1616782404 | https://github.com/simonw/sqlite-utils/issues/562#issuecomment-1616782404 | https://api.github.com/repos/simonw/sqlite-utils/issues/562 | IC_kwDOCGYnMM5gXiRE | simonw 9599 | 2023-07-02T19:24:14Z | 2023-07-02T19:26:39Z | OWNER | [Dataclasses](https://docs.python.org/3/library/dataclasses.html) were added in Python 3.7 and `sqlite-utils` was originally written for Python 3.6 - but both 3.6 and 3.7 are EOL now. The thing that makes Dataclasses particularly interesting is the potential to use type annotations with them to help specify the types of the related SQLite columns. Example for https://datasette.io/content/users ```sql CREATE TABLE [users] ( [login] TEXT, [id] INTEGER PRIMARY KEY, [node_id] TEXT, [avatar_url] TEXT, [gravatar_id] TEXT, [html_url] TEXT, [type] TEXT, [site_admin] INTEGER, [name] TEXT ); ``` And the dataclass: ```python from dataclasses import dataclass @dataclass class User: id: int login: str node_id: str avatar_url: str gravatar_id: str html_url: str type: str site_admin: int name: str ``` | {"total_count": 0, "+1": 0, "-1": 0, "laugh": 0, "hooray": 0, "confused": 0, "heart": 0, "rocket": 0, "eyes": 0} | Explore the intersection between sqlite-utils and dataclasses 1784794489 | |
1614652001 | https://github.com/simonw/datasette/issues/2093#issuecomment-1614652001 | https://api.github.com/repos/simonw/datasette/issues/2093 | IC_kwDOBm6k_c5gPaJh | simonw 9599 | 2023-06-30T13:27:13Z | 2023-06-30T13:27:13Z | OWNER | I agree, settings in the DB doesn't make sense but metadata does. On the JSON v YAML v TOML issue I just spotted Caddy has a concept of config adapters which they use to resolve exactly that problem: https://caddyserver.com/docs/config-adapters | {"total_count": 0, "+1": 0, "-1": 0, "laugh": 0, "hooray": 0, "confused": 0, "heart": 0, "rocket": 0, "eyes": 0} | Proposal: Combine settings, metadata, static, etc. into a single `datasette.toml` File 1781530343 | |
1613889979 | https://github.com/simonw/datasette/issues/2093#issuecomment-1613889979 | https://api.github.com/repos/simonw/datasette/issues/2093 | IC_kwDOBm6k_c5gMgG7 | simonw 9599 | 2023-06-29T22:44:08Z | 2023-06-30T13:25:39Z | OWNER | I do like also being able to set options using command line options though - for things like SQL time limits I'd much rather be able to throw on `--setting sql_time_limit_ms 10000` than have to save a config file to disk. So I'd want to support both. Which maybe means also having a way to set plugin options with CLI options. `datasette publish` kind of has that ability already: ``` datasette publish heroku my_database.db \ --name my-heroku-app-demo \ --install=datasette-auth-github \ --plugin-secret datasette-auth-github client_id your_client_id \ --plugin-secret datasette-auth-github client_secret your_client_secret ``` | {"total_count": 0, "+1": 0, "-1": 0, "laugh": 0, "hooray": 0, "confused": 0, "heart": 0, "rocket": 0, "eyes": 0} | Proposal: Combine settings, metadata, static, etc. into a single `datasette.toml` File 1781530343 | |
1613887492 | https://github.com/simonw/datasette/issues/2093#issuecomment-1613887492 | https://api.github.com/repos/simonw/datasette/issues/2093 | IC_kwDOBm6k_c5gMfgE | simonw 9599 | 2023-06-29T22:40:25Z | 2023-06-29T22:40:25Z | OWNER | I'm strongly in favour of combining settings, configuration and plugin configuration. I'm not keen on mixing in metadata as well - that feels like a different concept to me, and I'm unhappy with how that's already had things like plugin settings leak into it. I'm not yet sold on TOML - I actually find it less intuitive than YAML, surprisingly. They all have their warts I guess. Datasette already has the ability to consume JSON or YAML for metadata - maybe it could grow TOML support too? That way users could have a `datasette.json` or `datasette.yaml` or `datasette.toml` file depending on their preference. In terms of metadata: since that's means to be driven by a plugin hook anyway, maybe one of the potential sources of metadata is a `metadata` nested object in that `datasette.*` configuration file. Or you can have it in a separate `metadata.json` or bundled into the SQLite database or some other plugin-driven mechanism. | {"total_count": 0, "+1": 0, "-1": 0, "laugh": 0, "hooray": 0, "confused": 0, "heart": 0, "rocket": 0, "eyes": 0} | Proposal: Combine settings, metadata, static, etc. into a single `datasette.toml` File 1781530343 | |
1613381990 | https://github.com/simonw/datasette/issues/2092#issuecomment-1613381990 | https://api.github.com/repos/simonw/datasette/issues/2092 | IC_kwDOBm6k_c5gKkFm | simonw 9599 | 2023-06-29T15:23:04Z | 2023-06-29T15:26:28Z | OWNER | Felt lazy: ```bash symbex test_homepage -f tests/test_api.py | \ llm -m 4 --system 'Change all of the == some integer tests in this code to isinstance(thing, int) instead' ``` Output: ```python # File: tests/test_api.py Line: 26 @pytest.mark.asyncio async def test_homepage(ds_client): response = await ds_client.get("/.json") assert response.status_code == 200 assert "application/json; charset=utf-8" == response.headers["content-type"] data = response.json() assert isinstance(data.keys(), int) d = data["fixtures"] assert d["name"] == "fixtures" assert isinstance(d["tables_count"], int) assert isinstance(len(d["tables_and_views_truncated"]), int) assert d["tables_and_views_more"] is True # 4 hidden FTS tables + no_primary_key (hidden in metadata) assert isinstance(d["hidden_tables_count"], int) # 201 in no_primary_key, plus 6 in other hidden tables: assert isinstance(d["hidden_table_rows_sum"], int), data assert isinstance(d["views_count"], int) ``` I'll use most of that and delete the obsoleted comments. | {"total_count": 0, "+1": 0, "-1": 0, "laugh": 0, "hooray": 0, "confused": 0, "heart": 0, "rocket": 0, "eyes": 0} | test_homepage intermittent failure 1781047747 | |
1613375407 | https://github.com/simonw/datasette/issues/2092#issuecomment-1613375407 | https://api.github.com/repos/simonw/datasette/issues/2092 | IC_kwDOBm6k_c5gKiev | simonw 9599 | 2023-06-29T15:20:52Z | 2023-06-29T15:21:05Z | OWNER | I'm going to remove this assertion entirely. The homepage JSON needs a refactor anyway. | {"total_count": 0, "+1": 0, "-1": 0, "laugh": 0, "hooray": 0, "confused": 0, "heart": 0, "rocket": 0, "eyes": 0} | test_homepage intermittent failure 1781047747 | |
1613369355 | https://github.com/simonw/datasette/issues/2091#issuecomment-1613369355 | https://api.github.com/repos/simonw/datasette/issues/2091 | IC_kwDOBm6k_c5gKhAL | simonw 9599 | 2023-06-29T15:18:34Z | 2023-06-29T15:18:34Z | OWNER | Posted on the Glitch feedback forum about this here: https://support.glitch.com/t/upgrade-python-version-from-3-7-which-is-now-eol-to-something-more-recent/63011 | {"total_count": 0, "+1": 0, "-1": 0, "laugh": 0, "hooray": 0, "confused": 0, "heart": 0, "rocket": 0, "eyes": 0} | Drop support for Python 3.7 1781022369 | |
1613360413 | https://github.com/simonw/datasette/issues/2091#issuecomment-1613360413 | https://api.github.com/repos/simonw/datasette/issues/2091 | IC_kwDOBm6k_c5gKe0d | simonw 9599 | 2023-06-29T15:13:04Z | 2023-06-29T15:13:04Z | OWNER | One problem: https://glitch.com/ still provides 3.7: ``` $ python3 --version Python 3.7.10 ``` <img width="956" alt="image" src="https://github.com/simonw/datasette/assets/9599/e1fa44c0-4362-40e6-a2c7-d5b09b784c66"> | {"total_count": 0, "+1": 0, "-1": 0, "laugh": 0, "hooray": 0, "confused": 0, "heart": 0, "rocket": 0, "eyes": 0} | Drop support for Python 3.7 1781022369 | |
1613346412 | https://github.com/simonw/datasette/issues/2090#issuecomment-1613346412 | https://api.github.com/repos/simonw/datasette/issues/2090 | IC_kwDOBm6k_c5gKbZs | simonw 9599 | 2023-06-29T15:05:04Z | 2023-06-29T15:05:04Z | OWNER | Decided to fix just those "Ambiguous variable name" ones: ```bash ruff check . | grep E741 ``` Then iterated through and fixed them all. | {"total_count": 0, "+1": 0, "-1": 0, "laugh": 0, "hooray": 0, "confused": 0, "heart": 0, "rocket": 0, "eyes": 0} | Adopt ruff for linting 1781005740 | |
1613339404 | https://github.com/simonw/datasette/issues/2090#issuecomment-1613339404 | https://api.github.com/repos/simonw/datasette/issues/2090 | IC_kwDOBm6k_c5gKZsM | simonw 9599 | 2023-06-29T15:01:01Z | 2023-06-29T15:01:20Z | OWNER | I tried it just now and got some interesting results. I dropped in a `ruff.toml` file: ```toml line-length = 160 ``` Because the default line length limit of 88 was causing a lot of noisy errors. Then run: ```bash pip install ruff ruff check . ``` Plenty of warnings about unused imports - running `ruff check . --fix` fixed those automatically, but I think I still need to manually review them as some might be imports which are deliberate and should be in `__all__` to ensure they are visible from that module as well. Some lines in tests are longer than even 160 chars, e.g.: https://github.com/simonw/datasette/blob/99ba05118891db9dc30f1dca22ad6709775560de/tests/test_html.py#L673-L681 These can have ` # noqa: E501` added to the end of those lines to skip the check for them. That got it down to: ``` % ruff check . datasette/views/table.py:23:5: F811 Redefinition of unused `format_bytes` from line 19 run_tests.py:2:5: E401 Multiple imports on one line tests/test_api.py:591:40: F811 Redefinition of unused `app_client_no_files` from line 7 tests/test_api.py:629:35: F811 Redefinition of unused `app_client_no_files` from line 7 tests/test_api.py:635:54: F811 Redefinition of unused `app_client_with_dot` from line 8 tests/test_api.py:661:25: F811 Redefinition of unused `app_client_shorter_time_limit` from line 9 tests/test_api.py:759:25: F811 Redefinition of unused `app_client_two_attached_databases_one_immutable` from line 10 tests/test_api.py:892:28: F811 Redefinition of unused `app_client_larger_cache_size` from line 11 tests/test_api.py:928:5: F811 Redefinition of unused `app_client_with_cors` from line 12 tests/test_api.py:929:5: F811 Redefinition of unused `app_client_two_attached_databases_one_immutable` from line 10 tests/test_api.py:969:38: F811 Redefinition of unused `app_client_two_attached_databases` from line 13 tests/test_api.py:976:39: F811 Redefinition of unused `app_client_conflicting_database_names` from line 14 tests/test_api.py:987:38: F811 Redefinition of u… | {"total_count": 0, "+1": 0, "-1": 0, "laugh": 0, "hooray": 0, "confused": 0, "heart": 0, "rocket": 0, "eyes": 0} | Adopt ruff for linting 1781005740 | |
1613316722 | https://github.com/simonw/datasette/issues/2089#issuecomment-1613316722 | https://api.github.com/repos/simonw/datasette/issues/2089 | IC_kwDOBm6k_c5gKUJy | simonw 9599 | 2023-06-29T14:48:10Z | 2023-06-29T14:48:10Z | OWNER | Spell check is passing now. | {"total_count": 0, "+1": 0, "-1": 0, "laugh": 0, "hooray": 0, "confused": 0, "heart": 0, "rocket": 0, "eyes": 0} | codespell test failure 1780973290 | |
1613315851 | https://github.com/simonw/datasette/issues/2089#issuecomment-1613315851 | https://api.github.com/repos/simonw/datasette/issues/2089 | IC_kwDOBm6k_c5gKT8L | simonw 9599 | 2023-06-29T14:47:38Z | 2023-06-29T14:47:38Z | OWNER | Confirmed, this was a 2.2.5 change: https://github.com/codespell-project/codespell/releases/tag/v2.2.5 > - Add displaing->displaying by [@peternewman](https://github.com/peternewman) in [#2808](https://github.com/codespell-project/codespell/pull/2808) | {"total_count": 0, "+1": 0, "-1": 0, "laugh": 0, "hooray": 0, "confused": 0, "heart": 0, "rocket": 0, "eyes": 0} | codespell test failure 1780973290 | |
1613307716 | https://github.com/simonw/datasette/issues/2089#issuecomment-1613307716 | https://api.github.com/repos/simonw/datasette/issues/2089 | IC_kwDOBm6k_c5gKR9E | simonw 9599 | 2023-06-29T14:42:23Z | 2023-06-29T14:42:23Z | OWNER | Yes, upgrading locally got me the correct version and the test failure: ``` % pip install -U codespell Requirement already satisfied: codespell in /Users/simon/.local/share/virtualenvs/datasette-AWNrQs95/lib/python3.10/site-packages (2.2.2) Collecting codespell Downloading codespell-2.2.5-py3-none-any.whl (242 kB) ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 242.7/242.7 kB 4.9 MB/s eta 0:00:00 Installing collected packages: codespell Attempting uninstall: codespell Found existing installation: codespell 2.2.2 Uninstalling codespell-2.2.2: Successfully uninstalled codespell-2.2.2 Successfully installed codespell-2.2.5 % codespell docs/metadata.rst docs/metadata.rst:192: displaing ==> displaying ``` | {"total_count": 0, "+1": 0, "-1": 0, "laugh": 0, "hooray": 0, "confused": 0, "heart": 0, "rocket": 0, "eyes": 0} | codespell test failure 1780973290 | |
1613306787 | https://github.com/simonw/datasette/issues/2089#issuecomment-1613306787 | https://api.github.com/repos/simonw/datasette/issues/2089 | IC_kwDOBm6k_c5gKRuj | simonw 9599 | 2023-06-29T14:41:47Z | 2023-06-29T14:41:47Z | OWNER | Looks like in CI it's running 2.2.5: ``` Collecting codespell (from datasette==1.0a2) Downloading codespell-2.2.5-py3-none-any.whl (242 kB) ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 242.7/242.7 kB 31.1 MB/s eta 0:00:00 ``` But on my laptop it's 2.2.2: ``` % codespell --version 2.2.2 ``` | {"total_count": 0, "+1": 0, "-1": 0, "laugh": 0, "hooray": 0, "confused": 0, "heart": 0, "rocket": 0, "eyes": 0} | codespell test failure 1780973290 | |
1613305070 | https://github.com/simonw/datasette/issues/2089#issuecomment-1613305070 | https://api.github.com/repos/simonw/datasette/issues/2089 | IC_kwDOBm6k_c5gKRTu | simonw 9599 | 2023-06-29T14:40:44Z | 2023-06-29T14:40:44Z | OWNER | I'm not sure why I can't duplicate this failure in my local development environment: ``` % codespell docs/metadata.rst ``` It finds no errors. | {"total_count": 0, "+1": 0, "-1": 0, "laugh": 0, "hooray": 0, "confused": 0, "heart": 0, "rocket": 0, "eyes": 0} | codespell test failure 1780973290 | |
1613290899 | https://github.com/simonw/datasette/pull/2077#issuecomment-1613290899 | https://api.github.com/repos/simonw/datasette/issues/2077 | IC_kwDOBm6k_c5gKN2T | simonw 9599 | 2023-06-29T14:32:16Z | 2023-06-29T14:32:16Z | OWNER | @dependabot recreate | {"total_count": 1, "+1": 1, "-1": 0, "laugh": 0, "hooray": 0, "confused": 0, "heart": 0, "rocket": 0, "eyes": 0} | Bump furo from 2023.3.27 to 2023.5.20 1719759468 | |
1610512875 | https://github.com/simonw/datasette/issues/1510#issuecomment-1610512875 | https://api.github.com/repos/simonw/datasette/issues/1510 | IC_kwDOBm6k_c5f_nnr | simonw 9599 | 2023-06-28T02:02:10Z | 2023-06-28T02:05:21Z | OWNER | I prototyped an approach to this using dataclasses and a `cog` mechanism for turning those into rendered tables in Sphinx. Here's what that prototype looks like: <img width="1069" alt="image" src="https://github.com/simonw/datasette/assets/9599/79210a43-716c-4470-a62d-01aa871973ec"> See https://github.com/simonw/datasette/commit/68223784167fdec4e7ebfca56002a6548ba7b423 for how it works. Here's the class that documented: https://github.com/simonw/datasette/blob/68223784167fdec4e7ebfca56002a6548ba7b423/datasette/context.py#L54-L68 And the code that generates the rST: https://github.com/simonw/datasette/blob/68223784167fdec4e7ebfca56002a6548ba7b423/datasette/context.py#L19-L45 And the bit that cog executes: https://github.com/simonw/datasette/blob/68223784167fdec4e7ebfca56002a6548ba7b423/docs/template_context.rst?plain=1#L9-L12 | {"total_count": 0, "+1": 0, "-1": 0, "laugh": 0, "hooray": 0, "confused": 0, "heart": 0, "rocket": 0, "eyes": 0} | Datasette 1.0 documented template context (maybe via API docs) 1054244712 | |
1610040517 | https://github.com/simonw/sqlite-utils/issues/561#issuecomment-1610040517 | https://api.github.com/repos/simonw/sqlite-utils/issues/561 | IC_kwDOCGYnMM5f90TF | simonw 9599 | 2023-06-27T18:44:31Z | 2023-06-27T18:44:38Z | OWNER | Got this working: ```bash sqlite-utils insert /tmp/playground.db Playground_Submission_Data \ ~/Downloads/Playground_Submission_Data.csv --csv --stop-after 2000 ``` | {"total_count": 0, "+1": 0, "-1": 0, "laugh": 0, "hooray": 0, "confused": 0, "heart": 0, "rocket": 0, "eyes": 0} | `--stop-after` option for `insert` and `upsert` commands 1777548699 | |
1606415188 | https://github.com/simonw/sqlite-utils/issues/235#issuecomment-1606415188 | https://api.github.com/repos/simonw/sqlite-utils/issues/235 | IC_kwDOCGYnMM5fv_NU | simonw 9599 | 2023-06-26T01:46:47Z | 2023-06-26T01:47:01Z | OWNER | I just tested this in a brand new virtual environment using the macOS Python 3: ```bash pipenv shell --python /Applications/Xcode.app/Contents/Developer/usr/bin/python3 ``` Then in that virtual environment I ran: ```bash pip install sqlite-utils # Confirm the right one is on the path: which sqlite-utils curl "https://data.nasa.gov/resource/y77d-th95.json" | \ sqlite-utils insert meteorites.db meteorites - --pk=id sqlite-utils extract meteorites.db meteorites recclass ``` This threw the same error reported above. Then I did this: ```bash rm meteorites.db pip install sqlean.py curl "https://data.nasa.gov/resource/y77d-th95.json" | \ sqlite-utils insert meteorites.db meteorites - --pk=id sqlite-utils extract meteorites.db meteorites recclass ``` And that second time it worked correctly. | {"total_count": 1, "+1": 0, "-1": 0, "laugh": 0, "hooray": 0, "confused": 0, "heart": 1, "rocket": 0, "eyes": 0} | Extract columns cannot create foreign key relation: sqlite3.OperationalError: table sqlite_master may not be modified 810618495 | |
1606411508 | https://github.com/simonw/sqlite-utils/issues/235#issuecomment-1606411508 | https://api.github.com/repos/simonw/sqlite-utils/issues/235 | IC_kwDOCGYnMM5fv-T0 | simonw 9599 | 2023-06-26T01:42:10Z | 2023-06-26T01:42:22Z | OWNER | https://sqlite-utils.datasette.io/en/stable/changelog.html#v3-33 - upgrading to `sqlite-utils>=3.33` and then installing both `sqlean.py` and `sqlite-dump` in the same virtual environment as `sqlite-utils` should fix this issue. | {"total_count": 1, "+1": 0, "-1": 0, "laugh": 0, "hooray": 1, "confused": 0, "heart": 0, "rocket": 0, "eyes": 0} | Extract columns cannot create foreign key relation: sqlite3.OperationalError: table sqlite_master may not be modified 810618495 | |
1606315321 | https://github.com/simonw/sqlite-utils/pull/560#issuecomment-1606315321 | https://api.github.com/repos/simonw/sqlite-utils/issues/560 | IC_kwDOCGYnMM5fvm05 | simonw 9599 | 2023-06-25T23:18:33Z | 2023-06-25T23:18:33Z | OWNER | Documentation preview: https://sqlite-utils--560.org.readthedocs.build/en/560/installation.html#alternatives-to-sqlite3 | {"total_count": 0, "+1": 0, "-1": 0, "laugh": 0, "hooray": 0, "confused": 0, "heart": 0, "rocket": 0, "eyes": 0} | Use sqlean if available in environment 1773458985 | |
1606310630 | https://github.com/simonw/sqlite-utils/pull/560#issuecomment-1606310630 | https://api.github.com/repos/simonw/sqlite-utils/issues/560 | IC_kwDOCGYnMM5fvlrm | simonw 9599 | 2023-06-25T23:06:07Z | 2023-06-25T23:06:07Z | OWNER | Filed an issue about the above with `pysqlite3` (which `sqlean.py` is based on) here: - https://github.com/coleifer/pysqlite3/issues/58 | {"total_count": 0, "+1": 0, "-1": 0, "laugh": 0, "hooray": 0, "confused": 0, "heart": 0, "rocket": 0, "eyes": 0} | Use sqlean if available in environment 1773458985 | |
1606297356 | https://github.com/simonw/sqlite-utils/pull/560#issuecomment-1606297356 | https://api.github.com/repos/simonw/sqlite-utils/issues/560 | IC_kwDOCGYnMM5fvicM | simonw 9599 | 2023-06-25T22:42:41Z | 2023-06-25T22:42:41Z | OWNER | Yes that does seem to do the trick: ```pycon >>> import sqlean >>> db = sqlean.connect("/tmp/4.db") >>> db.execute('PRAGMA journal_mode;').fetchall() [('delete',)] >>> db.isolation_level '' >>> db.execute('PRAGMA journal_mode=wal;') Traceback (most recent call last): File "<stdin>", line 1, in <module> sqlean.dbapi2.OperationalError: cannot change into wal mode from within a transaction >>> db.isolation_level = None >>> db.isolation_level >>> db.execute('PRAGMA journal_mode=wal;') <sqlean.dbapi2.Cursor object at 0x104437c20> ``` Weird how `isolation_level` of empty string causes the error, but setting that to `None` fixes the error. | {"total_count": 0, "+1": 0, "-1": 0, "laugh": 0, "hooray": 0, "confused": 0, "heart": 0, "rocket": 0, "eyes": 0} | Use sqlean if available in environment 1773458985 | |
1606294627 | https://github.com/simonw/sqlite-utils/pull/560#issuecomment-1606294627 | https://api.github.com/repos/simonw/sqlite-utils/issues/560 | IC_kwDOCGYnMM5fvhxj | simonw 9599 | 2023-06-25T22:40:10Z | 2023-06-25T22:40:10Z | OWNER | I suspect this has something to do with `autocommit` mode in `sqlite3` - which I may be able to turn off by setting `con.isolation_level = None`. | {"total_count": 0, "+1": 0, "-1": 0, "laugh": 0, "hooray": 0, "confused": 0, "heart": 0, "rocket": 0, "eyes": 0} | Use sqlean if available in environment 1773458985 | |
1606293382 | https://github.com/simonw/sqlite-utils/pull/560#issuecomment-1606293382 | https://api.github.com/repos/simonw/sqlite-utils/issues/560 | IC_kwDOCGYnMM5fvheG | simonw 9599 | 2023-06-25T22:34:47Z | 2023-06-25T22:34:47Z | OWNER | ```pycon >>> import sqlite3 >>> db = sqlite3.connect("/tmp/1.db") >>> db.execute('PRAGMA journal_mode=wal;') <sqlite3.Cursor object at 0x102475ec0> >>> import sqlean >>> db2 = sqlean.connect("/tmp/2.db") >>> db2.execute('PRAGMA journal_mode=wal;') Traceback (most recent call last): File "<stdin>", line 1, in <module> sqlean.dbapi2.OperationalError: cannot change into wal mode from within a transaction ``` | {"total_count": 0, "+1": 0, "-1": 0, "laugh": 0, "hooray": 0, "confused": 0, "heart": 0, "rocket": 0, "eyes": 0} | Use sqlean if available in environment 1773458985 | |
1606290917 | https://github.com/simonw/sqlite-utils/pull/560#issuecomment-1606290917 | https://api.github.com/repos/simonw/sqlite-utils/issues/560 | IC_kwDOCGYnMM5fvg3l | simonw 9599 | 2023-06-25T22:32:28Z | 2023-06-25T22:32:28Z | OWNER | I've fixed most of the test failures, but I still need to fix this one: > cannot change into wal mode from within a transaction | {"total_count": 0, "+1": 0, "-1": 0, "laugh": 0, "hooray": 0, "confused": 0, "heart": 0, "rocket": 0, "eyes": 0} | Use sqlean if available in environment 1773458985 | |
1606273005 | https://github.com/simonw/sqlite-utils/pull/560#issuecomment-1606273005 | https://api.github.com/repos/simonw/sqlite-utils/issues/560 | IC_kwDOCGYnMM5fvcft | simonw 9599 | 2023-06-25T21:47:47Z | 2023-06-25T21:47:47Z | OWNER | I can use https://github.com/simonw/sqlite-dump as an optional dependency to handle the missing `.iterdump()` method. | {"total_count": 0, "+1": 0, "-1": 0, "laugh": 0, "hooray": 0, "confused": 0, "heart": 0, "rocket": 0, "eyes": 0} | Use sqlean if available in environment 1773458985 | |
1606270887 | https://github.com/simonw/sqlite-utils/pull/560#issuecomment-1606270887 | https://api.github.com/repos/simonw/sqlite-utils/issues/560 | IC_kwDOCGYnMM5fvb-n | simonw 9599 | 2023-06-25T21:37:12Z | 2023-06-26T08:21:00Z | OWNER | On my own laptop I got a crash running the tests - details here: - https://github.com/nalgeon/sqlean.py/issues/3 | {"total_count": 0, "+1": 0, "-1": 0, "laugh": 0, "hooray": 0, "confused": 0, "heart": 0, "rocket": 0, "eyes": 0} | Use sqlean if available in environment 1773458985 | |
1606270055 | https://github.com/simonw/sqlite-utils/pull/560#issuecomment-1606270055 | https://api.github.com/repos/simonw/sqlite-utils/issues/560 | IC_kwDOCGYnMM5fvbxn | simonw 9599 | 2023-06-25T21:31:56Z | 2023-06-25T21:31:56Z | OWNER | Lots of failures now that I'm trying to run the tests against `sqlean.py` on macOS and Python 3.10: https://github.com/simonw/sqlite-utils/actions/runs/5371800108/jobs/9744802953 A bunch of these, because `pysqlite3` chooses not to implement `.iterdump()`: ``` @pytest.fixture def db_to_analyze_path(db_to_analyze, tmpdir): path = str(tmpdir / "test.db") db = sqlite3.connect(path) > db.executescript("\n".join(db_to_analyze.conn.iterdump())) E AttributeError: 'sqlean.dbapi2.Connection' object has no attribute 'iterdump' ``` Also some of these: ``` def test_analyze_whole_database(db): assert set(db.table_names()) == {"one_index", "two_indexes"} db.analyze() > assert set(db.table_names()) == {"one_index", "two_indexes", "sqlite_stat1"} E AssertionError: assert {'one_index',...'two_indexes'} == {'one_index',...'two_indexes'} E Extra items in the left set: E 'sqlite_stat4' E Full diff: E - {'two_indexes', 'sqlite_stat1', 'one_index'} E + {'two_indexes', 'sqlite_stat1', 'sqlite_stat4', 'one_index'} E ? ++++++++++++++++ ``` Apparently `sqlean.py` adds a `sqlite_stat4` table that the tests are not expecting. Plus some errors that look like this: ``` def test_enable_wal(): runner = CliRunner() dbs = ["test.db", "test2.db"] with runner.isolated_filesystem(): for dbname in dbs: db = Database(dbname) db["t"].create({"pk": int}, pk="pk") assert db.journal_mode == "delete" result = runner.invoke(cli.cli, ["enable-wal"] + dbs) > assert 0 == result.exit_code E AssertionError: assert 0 == 1 E + where 1 = <Result OperationalError('cannot change into wal mode from within a transaction')>.exit_code ``` Test summary: ``` ============ 13 failed, 909 passed, 16 skipped, 2 errors in 19.29s ============= ``` | {"total_count": 0, "+1": 0, "-1": 0, "laugh": 0, "hooray": 0, "confused": 0, "heart": 0, "rocket": 0, "eyes": 0} | Use sqlean if available in environment 1773458985 | |
1604379952 | https://github.com/simonw/sqlite-utils/issues/235#issuecomment-1604379952 | https://api.github.com/repos/simonw/sqlite-utils/issues/235 | IC_kwDOCGYnMM5foOUw | simonw 9599 | 2023-06-23T14:39:55Z | 2023-06-23T15:39:32Z | OWNER | Ideally a workaround for this right now would be to install `pysqlite3` in the same virtual environment: sqlite-utils install pysqlite3-binary But `pysqlite3-binary` doesn't yet ship a wheel for macOS so this probably won't work for most people. The "easiest" fix at the moment is to use Python from Homebrew - so `brew install sqlite-utils` for example won't suffer from this problem. Not a great solution for people who aren't using Homebrew though! | {"total_count": 0, "+1": 0, "-1": 0, "laugh": 0, "hooray": 0, "confused": 0, "heart": 0, "rocket": 0, "eyes": 0} | Extract columns cannot create foreign key relation: sqlite3.OperationalError: table sqlite_master may not be modified 810618495 | |
1600778057 | https://github.com/simonw/datasette/issues/260#issuecomment-1600778057 | https://api.github.com/repos/simonw/datasette/issues/260 | IC_kwDOBm6k_c5fae9J | simonw 9599 | 2023-06-21T12:51:22Z | 2023-06-21T12:51:22Z | OWNER | Another example of confusion from this today: https://discord.com/channels/823971286308356157/823971286941302908/1121042411238457374 See also https://gist.github.com/BinomeDeNewton/651ac8b50dd5420f8e54d1682eee5fed?permalink_comment_id=4605982#gistcomment-4605982 | {"total_count": 0, "+1": 0, "-1": 0, "laugh": 0, "hooray": 0, "confused": 0, "heart": 0, "rocket": 0, "eyes": 0} | Validate metadata.json on startup 323223872 | |
1565058994 | https://github.com/simonw/datasette/pull/2053#issuecomment-1565058994 | https://api.github.com/repos/simonw/datasette/issues/2053 | IC_kwDOBm6k_c5dSOey | simonw 9599 | 2023-05-26T23:13:02Z | 2023-05-26T23:13:02Z | OWNER | I should have an extra called `extra_html_context` which bundles together all of the weird extra stuff needed by the HTML template, and is then passed as the root context when the template is rendered (with the other stuff from extras patched into it). | {"total_count": 0, "+1": 0, "-1": 0, "laugh": 0, "hooray": 0, "confused": 0, "heart": 0, "rocket": 0, "eyes": 0} | WIP new JSON for queries 1656432059 | |
1563793781 | https://github.com/simonw/datasette/pull/2053#issuecomment-1563793781 | https://api.github.com/repos/simonw/datasette/issues/2053 | IC_kwDOBm6k_c5dNZl1 | simonw 9599 | 2023-05-26T04:27:55Z | 2023-05-26T04:27:55Z | OWNER | I should split out a `canned_query.html` template too, as something that extends the `query.html` template. | {"total_count": 0, "+1": 0, "-1": 0, "laugh": 0, "hooray": 0, "confused": 0, "heart": 0, "rocket": 0, "eyes": 0} | WIP new JSON for queries 1656432059 | |
1563667574 | https://github.com/simonw/datasette/pull/2053#issuecomment-1563667574 | https://api.github.com/repos/simonw/datasette/issues/2053 | IC_kwDOBm6k_c5dM6x2 | simonw 9599 | 2023-05-26T00:40:22Z | 2023-05-26T00:40:22Z | OWNER | Or maybe... - `BaseQueryView(View)` - knows how to render the results of a SQL query - `QueryView(BaseQueryView)` - renders from `?sql=` - `CannedQueryView(BaseQueryView)` - renders for a named canned query And then later perhaps: - `RowQueryView(BaseQueryView)` - renders the `select * from t where pk = ?` - `TableQueryView(BaseQueryView)` - replaces the super complex existing `TableView` | {"total_count": 0, "+1": 0, "-1": 0, "laugh": 0, "hooray": 0, "confused": 0, "heart": 0, "rocket": 0, "eyes": 0} | WIP new JSON for queries 1656432059 | |
1563663925 | https://github.com/simonw/datasette/pull/2053#issuecomment-1563663925 | https://api.github.com/repos/simonw/datasette/issues/2053 | IC_kwDOBm6k_c5dM541 | simonw 9599 | 2023-05-26T00:32:47Z | 2023-05-26T00:35:47Z | OWNER | I'm going to entirely split canned queries off from `?sql=` queries - they share a bunch of code right now which is just making everything much harder to follow. I'll refactor their shared bits into functions that they both call. Or _maybe_ I'll try having `CannedQueryView` as a subclass of `QueryView`. | {"total_count": 0, "+1": 0, "-1": 0, "laugh": 0, "hooray": 0, "confused": 0, "heart": 0, "rocket": 0, "eyes": 0} | WIP new JSON for queries 1656432059 | |
1563663616 | https://github.com/simonw/datasette/pull/2053#issuecomment-1563663616 | https://api.github.com/repos/simonw/datasette/issues/2053 | IC_kwDOBm6k_c5dM50A | simonw 9599 | 2023-05-26T00:32:08Z | 2023-05-26T00:32:08Z | OWNER | Now that I have the new `View` subclass from #2078 I want to use it to simplify this code. Challenge: there are several things to consider here: - The `/db` page without `?sql=` displays a list of tables in that database - With `?sql=` it shows the query results for that query (or an error) - If it's a `/db/name-of-canned-query` it works a bit like the query page, but executes a canned query instead of the `?sql=` query - POST `/db/name-of-canned-query` is support for writable canned queries | {"total_count": 0, "+1": 0, "-1": 0, "laugh": 0, "hooray": 0, "confused": 0, "heart": 0, "rocket": 0, "eyes": 0} | WIP new JSON for queries 1656432059 | |
1563650990 | https://github.com/simonw/datasette/pull/2080#issuecomment-1563650990 | https://api.github.com/repos/simonw/datasette/issues/2080 | IC_kwDOBm6k_c5dM2uu | simonw 9599 | 2023-05-26T00:08:59Z | 2023-05-26T00:08:59Z | OWNER | I'm not going to document this yet, I want to let it bake for a bit longer first. | {"total_count": 0, "+1": 0, "-1": 0, "laugh": 0, "hooray": 0, "confused": 0, "heart": 0, "rocket": 0, "eyes": 0} | New View base class 1726603778 | |
1563626231 | https://github.com/simonw/datasette/pull/2080#issuecomment-1563626231 | https://api.github.com/repos/simonw/datasette/issues/2080 | IC_kwDOBm6k_c5dMwr3 | simonw 9599 | 2023-05-25T23:25:17Z | 2023-05-25T23:25:17Z | OWNER | I'm going to try using this for the `/-/patterns` page. | {"total_count": 0, "+1": 0, "-1": 0, "laugh": 0, "hooray": 0, "confused": 0, "heart": 0, "rocket": 0, "eyes": 0} | New View base class 1726603778 | |
1563625093 | https://github.com/simonw/datasette/issues/2078#issuecomment-1563625093 | https://api.github.com/repos/simonw/datasette/issues/2078 | IC_kwDOBm6k_c5dMwaF | simonw 9599 | 2023-05-25T23:23:15Z | 2023-05-25T23:23:15Z | OWNER | Rest of the work on this will happen in the PR: - #2080 | {"total_count": 0, "+1": 0, "-1": 0, "laugh": 0, "hooray": 0, "confused": 0, "heart": 0, "rocket": 0, "eyes": 0} | Resolve the difference between `wrap_view()` and `BaseView` 1726236847 | |
1563607291 | https://github.com/simonw/datasette/issues/2079#issuecomment-1563607291 | https://api.github.com/repos/simonw/datasette/issues/2079 | IC_kwDOBm6k_c5dMsD7 | simonw 9599 | 2023-05-25T22:56:28Z | 2023-05-25T22:56:28Z | OWNER | Wrote this up as a TIL: https://til.simonwillison.net/http/testing-cors-max-age | {"total_count": 0, "+1": 0, "-1": 0, "laugh": 0, "hooray": 0, "confused": 0, "heart": 0, "rocket": 0, "eyes": 0} | Datasette should serve Access-Control-Max-Age 1726531350 | |
1563597589 | https://github.com/simonw/datasette/issues/2079#issuecomment-1563597589 | https://api.github.com/repos/simonw/datasette/issues/2079 | IC_kwDOBm6k_c5dMpsV | simonw 9599 | 2023-05-25T22:42:07Z | 2023-05-25T22:42:07Z | OWNER | Mystery solved as to why I wasn't seeing this work: <img width="951" alt="CleanShot 2023-05-25 at 15 38 32@2x" src="https://github.com/simonw/datasette/assets/9599/140ee046-69c2-4a68-b7cb-a4603552477c"> I had "Disable Cache" checked! I ran this experiment after un-checking that box: ```javascript fetch('https://latest.datasette.io/ephemeral/foo/1/-/update', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ test: 'test' }) }); // And run it again fetch('https://latest.datasette.io/ephemeral/foo/1/-/update', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ test: 'test' }) }); // Now try a thing that doesn't serve that max-age header yet: fetch('https://latest-with-plugins.datasette.io/ephemeral/foo/1/-/update', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ test: 'test' }) }); // And a second time but within 5s fetch('https://latest-with-plugins.datasette.io/ephemeral/foo/1/-/update', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ test: 'test' }) }); // Third time after waiting longer than 5s fetch('https://latest-with-plugins.datasette.io/ephemeral/foo/1/-/update', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ test: 'test' }) }); // Try that original one again - still within the 1hr cache time fetch('https://latest.datasette.io/ephemeral/foo/1/-/update', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ test: 'test' }) }); ``` The results show that the cache of 1hr was being obeyed for `latest.datasette.io` while the `latest-with-plugins.datasette.io` default cache of 5s was being obeyed too. <img width="904" alt="image" src="https://github.com/simonw/datasette/as… | {"total_count": 0, "+1": 0, "-1": 0, "laugh": 0, "hooray": 0, "confused": 0, "heart": 0, "rocket": 0, "eyes": 0} | Datasette should serve Access-Control-Max-Age 1726531350 | |
1563588199 | https://github.com/simonw/datasette/issues/2079#issuecomment-1563588199 | https://api.github.com/repos/simonw/datasette/issues/2079 | IC_kwDOBm6k_c5dMnZn | simonw 9599 | 2023-05-25T22:29:47Z | 2023-05-25T22:30:12Z | OWNER | https://fetch.spec.whatwg.org/#http-access-control-max-age says: > Indicates the number of seconds (5 by default) the information provided by the [Access-Control-Allow-Methods](https://fetch.spec.whatwg.org/#http-access-control-allow-methods) and [Access-Control-Allow-Headers](https://fetch.spec.whatwg.org/#http-access-control-allow-headers) [headers](https://fetch.spec.whatwg.org/#concept-header) can be cached. So there was already a 5s cache anyway. | {"total_count": 0, "+1": 0, "-1": 0, "laugh": 0, "hooray": 0, "confused": 0, "heart": 0, "rocket": 0, "eyes": 0} | Datasette should serve Access-Control-Max-Age 1726531350 | |
1563587230 | https://github.com/simonw/datasette/issues/2079#issuecomment-1563587230 | https://api.github.com/repos/simonw/datasette/issues/2079 | IC_kwDOBm6k_c5dMnKe | simonw 9599 | 2023-05-25T22:28:20Z | 2023-05-25T22:28:20Z | OWNER | Weird... after the deploy went out: <img width="945" alt="image" src="https://github.com/simonw/datasette/assets/9599/a8abb12a-446b-4970-8e76-3b218b6d02c7"> But the request did indeed get the new header: <img width="951" alt="image" src="https://github.com/simonw/datasette/assets/9599/7c72578c-9250-47d3-a4d2-1ece8c07ef84"> So I'm not sure why it's making multiple `POST` requests like that. Maybe it's because the attempted `POST` failed with a 404? | {"total_count": 0, "+1": 0, "-1": 0, "laugh": 0, "hooray": 0, "confused": 0, "heart": 0, "rocket": 0, "eyes": 0} | Datasette should serve Access-Control-Max-Age 1726531350 | |
1563565407 | https://github.com/simonw/datasette/issues/2079#issuecomment-1563565407 | https://api.github.com/repos/simonw/datasette/issues/2079 | IC_kwDOBm6k_c5dMh1f | simonw 9599 | 2023-05-25T22:09:53Z | 2023-05-25T22:09:53Z | OWNER | Updated docs: https://docs.datasette.io/en/latest/json_api.html#enabling-cors | {"total_count": 0, "+1": 0, "-1": 0, "laugh": 0, "hooray": 0, "confused": 0, "heart": 0, "rocket": 0, "eyes": 0} | Datasette should serve Access-Control-Max-Age 1726531350 | |
1563563438 | https://github.com/simonw/datasette/issues/2079#issuecomment-1563563438 | https://api.github.com/repos/simonw/datasette/issues/2079 | IC_kwDOBm6k_c5dMhWu | simonw 9599 | 2023-05-25T22:08:28Z | 2023-05-25T22:08:28Z | OWNER | I ran this on https://www.example.com/ twice using the console: ```javascript fetch( `https://latest.datasette.io/ephemeral/foo/1/-/update`, { method: "POST", mode: "cors", headers: { Authorization: `Bearer tok`, "Content-Type": "application/json", }, body: JSON.stringify({update: {blah: 1}}), } ) .then((r) => r.json()) .then((data) => { console.log(data); }); ``` And got this in the network pane: <img width="945" alt="image" src="https://github.com/simonw/datasette/assets/9599/cc767917-388b-4616-b35d-efaa1591948a"> | {"total_count": 0, "+1": 0, "-1": 0, "laugh": 0, "hooray": 0, "confused": 0, "heart": 0, "rocket": 0, "eyes": 0} | Datasette should serve Access-Control-Max-Age 1726531350 | |
1563558915 | https://github.com/simonw/datasette/issues/2079#issuecomment-1563558915 | https://api.github.com/repos/simonw/datasette/issues/2079 | IC_kwDOBm6k_c5dMgQD | simonw 9599 | 2023-05-25T22:04:41Z | 2023-05-25T22:04:41Z | OWNER | I'm going with 3600 for 1 hour instead of 600 for 10 minutes. | {"total_count": 0, "+1": 0, "-1": 0, "laugh": 0, "hooray": 0, "confused": 0, "heart": 0, "rocket": 0, "eyes": 0} | Datasette should serve Access-Control-Max-Age 1726531350 | |
1563547097 | https://github.com/simonw/datasette/issues/2079#issuecomment-1563547097 | https://api.github.com/repos/simonw/datasette/issues/2079 | IC_kwDOBm6k_c5dMdXZ | simonw 9599 | 2023-05-25T21:51:38Z | 2023-05-25T21:51:38Z | OWNER | Also need to update this documentation: https://github.com/simonw/datasette/blob/9584879534ff0556e04e4c420262972884cac87b/docs/json_api.rst?plain=1#L453-L465 Or maybe make that automated via `cog`. | {"total_count": 0, "+1": 0, "-1": 0, "laugh": 0, "hooray": 0, "confused": 0, "heart": 0, "rocket": 0, "eyes": 0} | Datasette should serve Access-Control-Max-Age 1726531350 | |
1563522011 | https://github.com/simonw/datasette/issues/2078#issuecomment-1563522011 | https://api.github.com/repos/simonw/datasette/issues/2078 | IC_kwDOBm6k_c5dMXPb | simonw 9599 | 2023-05-25T21:22:30Z | 2023-05-25T21:22:30Z | OWNER | This is bad: ```python async def __call__(self, request, datasette): try: handler = getattr(self, request.method.lower()) return await handler(request, datasette) except AttributeError: return await self.method_not_allowed(request) ``` Because it hides any `AttributeError` exceptions that might occur in the view code. | {"total_count": 0, "+1": 0, "-1": 0, "laugh": 0, "hooray": 0, "confused": 0, "heart": 0, "rocket": 0, "eyes": 0} | Resolve the difference between `wrap_view()` and `BaseView` 1726236847 | |
1563511171 | https://github.com/simonw/datasette/issues/2078#issuecomment-1563511171 | https://api.github.com/repos/simonw/datasette/issues/2078 | IC_kwDOBm6k_c5dMUmD | simonw 9599 | 2023-05-25T21:11:20Z | 2023-05-25T21:13:05Z | OWNER | I'm going to call this `VerbView` for the moment. Might even rename it to `View` later. | {"total_count": 0, "+1": 0, "-1": 0, "laugh": 0, "hooray": 0, "confused": 0, "heart": 0, "rocket": 0, "eyes": 0} | Resolve the difference between `wrap_view()` and `BaseView` 1726236847 | |
1563498048 | https://github.com/simonw/datasette/issues/2078#issuecomment-1563498048 | https://api.github.com/repos/simonw/datasette/issues/2078 | IC_kwDOBm6k_c5dMRZA | simonw 9599 | 2023-05-25T20:57:52Z | 2023-05-25T20:58:13Z | OWNER | Here's a new `BaseView` class that automatically populates `OPTIONS` based on available methods: ```python class BaseView: async def head(self, *args, **kwargs): try: response = await self.get(*args, **kwargs) response.body = b"" return response except AttributeError: raise async def method_not_allowed(self, request): if ( request.path.endswith(".json") or request.headers.get("content-type") == "application/json" ): response = Response.json( {"ok": False, "error": "Method not allowed"}, status=405 ) else: response = Response.text("Method not allowed", status=405) return response async def options(self, request, *args, **kwargs): response = Response.text("ok") response.headers["allow"] = ", ".join( method.upper() for method in ("head", "get", "post", "put", "patch", "delete") if hasattr(self, method) ) return response async def __call__(self, request, datasette): try: handler = getattr(self, request.method.lower()) return await handler(request, datasette) except AttributeError: return await self.method_not_allowed(request) class DemoView(BaseView): async def get(self, datasette, request): return Response.text("Hello there! {} - {}".format(datasette, request)) post = get ``` | {"total_count": 0, "+1": 0, "-1": 0, "laugh": 0, "hooray": 0, "confused": 0, "heart": 0, "rocket": 0, "eyes": 0} | Resolve the difference between `wrap_view()` and `BaseView` 1726236847 | |
1563488929 | https://github.com/simonw/datasette/issues/2078#issuecomment-1563488929 | https://api.github.com/repos/simonw/datasette/issues/2078 | IC_kwDOBm6k_c5dMPKh | simonw 9599 | 2023-05-25T20:48:12Z | 2023-05-25T20:48:39Z | OWNER | Actually no need for that extra level of parameter detection: `BaseView.__call__` should _always_ take `datasette, request` - `scope` and `receive` are both available on `request`, and `send` is only needed if you're not planning on returning a `Response` object. So the `get` and `post` and suchlike methods should take `datasette` and `request` too. | {"total_count": 0, "+1": 0, "-1": 0, "laugh": 0, "hooray": 0, "confused": 0, "heart": 0, "rocket": 0, "eyes": 0} | Resolve the difference between `wrap_view()` and `BaseView` 1726236847 | |
1563444296 | https://github.com/simonw/datasette/issues/2078#issuecomment-1563444296 | https://api.github.com/repos/simonw/datasette/issues/2078 | IC_kwDOBm6k_c5dMERI | simonw 9599 | 2023-05-25T20:06:08Z | 2023-05-25T20:06:08Z | OWNER | This prototype seems to work well: ```diff diff --git a/datasette/app.py b/datasette/app.py index d7dace67..ed0edf28 100644 --- a/datasette/app.py +++ b/datasette/app.py @@ -17,6 +17,7 @@ import secrets import sys import threading import time +import types import urllib.parse from concurrent import futures from pathlib import Path @@ -1266,6 +1267,8 @@ class Datasette: # TODO: /favicon.ico and /-/static/ deserve far-future cache expires add_route(favicon, "/favicon.ico") + add_route(wrap_view(DemoView, self), '/demo') + add_route( asgi_static(app_root / "datasette" / "static"), r"/-/static/(?P<path>.*)$" ) @@ -1673,8 +1676,46 @@ def _cleaner_task_str(task): return _cleaner_task_str_re.sub("", s) -def wrap_view(view_fn, datasette): - @functools.wraps(view_fn) +class DemoView: + async def __call__(self, datasette, request): + return Response.text("Hello there! {} - {}".format(datasette, request)) + +def wrap_view(view_fn_or_class, datasette): + is_function = isinstance(view_fn_or_class, types.FunctionType) + if is_function: + return wrap_view_function(view_fn_or_class, datasette) + else: + if not isinstance(view_fn_or_class, type): + raise ValueError("view_fn_or_class must be a function or a class") + return wrap_view_class(view_fn_or_class, datasette) + + +def wrap_view_class(view_class, datasette): + async def async_view_for_class(request, send): + instance = view_class() + if inspect.iscoroutinefunction(instance.__call__): + return await async_call_with_supported_arguments( + instance.__call__, + scope=request.scope, + receive=request.receive, + send=send, + request=request, + datasette=datasette, + ) + else: + return call_with_supported_arguments( + instance.__call__, … | {"total_count": 0, "+1": 0, "-1": 0, "laugh": 0, "hooray": 0, "confused": 0, "heart": 0, "rocket": 0, "eyes": 0} | Resolve the difference between `wrap_view()` and `BaseView` 1726236847 | |
1563419066 | https://github.com/simonw/datasette/issues/2078#issuecomment-1563419066 | https://api.github.com/repos/simonw/datasette/issues/2078 | IC_kwDOBm6k_c5dL-G6 | simonw 9599 | 2023-05-25T19:42:16Z | 2023-05-25T19:43:08Z | OWNER | Maybe what I want here is the ability to register classes with the router - and have the router know that if it's a class it should instantiate it via its constructor and then await `__call__` it. The neat thing about it is that it can reduce the risk of having a class instance that accidentally shares state between requests. It also encourages that each class only responds based on the `datasette, request, ...` objects that are passed to its methods. | {"total_count": 0, "+1": 0, "-1": 0, "laugh": 0, "hooray": 0, "confused": 0, "heart": 0, "rocket": 0, "eyes": 0} | Resolve the difference between `wrap_view()` and `BaseView` 1726236847 | |
1563359114 | https://github.com/simonw/datasette/issues/2078#issuecomment-1563359114 | https://api.github.com/repos/simonw/datasette/issues/2078 | IC_kwDOBm6k_c5dLveK | simonw 9599 | 2023-05-25T18:47:57Z | 2023-05-25T18:47:57Z | OWNER | Oops, that broke everything: ``` @documented async def await_me_maybe(value: typing.Any) -> typing.Any: "If value is callable, call it. If awaitable, await it. Otherwise return it." > if callable(value): E TypeError: 'module' object is not callable ``` | {"total_count": 0, "+1": 0, "-1": 0, "laugh": 0, "hooray": 0, "confused": 0, "heart": 0, "rocket": 0, "eyes": 0} | Resolve the difference between `wrap_view()` and `BaseView` 1726236847 | |
1563329245 | https://github.com/simonw/datasette/issues/2078#issuecomment-1563329245 | https://api.github.com/repos/simonw/datasette/issues/2078 | IC_kwDOBm6k_c5dLoLd | simonw 9599 | 2023-05-25T18:26:47Z | 2023-05-25T18:28:08Z | OWNER | With type hints and a namedtuple: ```python import asyncio import types from typing import NamedTuple, Any class CallableStatus(NamedTuple): is_callable: bool is_async_callable: bool def check_callable(obj: Any) -> CallableStatus: if not callable(obj): return CallableStatus(False, False) if isinstance(obj, type): # It's a class return CallableStatus(True, False) if isinstance(obj, types.FunctionType): return CallableStatus(True, asyncio.iscoroutinefunction(obj)) if hasattr(obj, "__call__"): return CallableStatus(True, asyncio.iscoroutinefunction(obj.__call__)) assert False, "obj {} is somehow callable with no __call__ method".format(repr(obj)) ``` ```python for thing in ( async_func, non_async_func, AsyncClass(), NotAsyncClass(), ClassNoCall(), AsyncClass, NotAsyncClass, ClassNoCall, ): print(thing, check_callable(thing)) ``` ``` <function async_func at 0x1073d5120> CallableStatus(is_callable=True, is_async_callable=True) <function non_async_func at 0x1073d5080> CallableStatus(is_callable=True, is_async_callable=False) <__main__.AsyncClass object at 0x106ba7490> CallableStatus(is_callable=True, is_async_callable=True) <__main__.NotAsyncClass object at 0x106740150> CallableStatus(is_callable=True, is_async_callable=False) <__main__.ClassNoCall object at 0x10676d910> CallableStatus(is_callable=False, is_async_callable=False) <class '__main__.AsyncClass'> CallableStatus(is_callable=True, is_async_callable=False) <class '__main__.NotAsyncClass'> CallableStatus(is_callable=True, is_async_callable=False) <class '__main__.ClassNoCall'> CallableStatus(is_callable=True, is_async_callable=False) ``` | {"total_count": 0, "+1": 0, "-1": 0, "laugh": 0, "hooray": 0, "confused": 0, "heart": 0, "rocket": 0, "eyes": 0} | Resolve the difference between `wrap_view()` and `BaseView` 1726236847 | |
1563326000 | https://github.com/simonw/datasette/issues/2078#issuecomment-1563326000 | https://api.github.com/repos/simonw/datasette/issues/2078 | IC_kwDOBm6k_c5dLnYw | simonw 9599 | 2023-05-25T18:23:38Z | 2023-05-25T18:23:38Z | OWNER | I don't like that `is_callable()` implies a single boolean result but actually returns a pair. I'll call it `check_callable(obj)` instead. | {"total_count": 0, "+1": 0, "-1": 0, "laugh": 0, "hooray": 0, "confused": 0, "heart": 0, "rocket": 0, "eyes": 0} | Resolve the difference between `wrap_view()` and `BaseView` 1726236847 | |
1563318598 | https://github.com/simonw/datasette/issues/2078#issuecomment-1563318598 | https://api.github.com/repos/simonw/datasette/issues/2078 | IC_kwDOBm6k_c5dLllG | simonw 9599 | 2023-05-25T18:17:03Z | 2023-05-25T18:21:25Z | OWNER | I think I want that to return `(is_callable, is_async)` - so I can both test if the thing can be called AND if it should be awaited in the same operation (without any exceptions). I tried this: ```python def is_callable(obj): "Returns (is_callable, is_async_callable)" if not callable(obj): return False, False if isinstance(obj, types.FunctionType): return True, asyncio.iscoroutinefunction(obj) if hasattr(obj, '__call__'): return True, asyncio.iscoroutinefunction(obj.__call__) return False, False ``` ```python for thing in ( async_func, non_async_func, AsyncClass(), NotAsyncClass(), ClassNoCall(), AsyncClass, NotAsyncClass, ClassNoCall ): print(thing, is_callable(thing)) ``` And got: ``` <function async_func at 0x1073d5120> (True, True) <function non_async_func at 0x1073d5080> (True, False) <__main__.AsyncClass object at 0x106cce490> (True, True) <__main__.NotAsyncClass object at 0x106ccf710> (True, False) <__main__.ClassNoCall object at 0x106ccc810> (False, False) <class '__main__.AsyncClass'> (True, True) <class '__main__.NotAsyncClass'> (True, False) <class '__main__.ClassNoCall'> (True, False) ``` Which is almost right, but I don't like that `AsyncClass` is shown as callable (which it is, since it's a class) and awaitable (which it is not - the `__call__` method may be async but calling the class constructor is not). So I'm going to detect classes using `isinstance(obj, type)`. ```python def is_callable(obj): "Returns (is_callable, is_async_callable)" if not callable(obj): return False, False if isinstance(obj, type): # It's a class return True, False if isinstance(obj, types.FunctionType): return True, asyncio.iscoroutinefunction(obj) if hasattr(obj, '__call__'): return True, asyncio.iscoroutinefunction(obj.__call__) assert False, "obj {} somehow is callable with no __call__ method".format(obj) ``` I am re… | {"total_count": 0, "+1": 0, "-1": 0, "laugh": 0, "hooray": 0, "confused": 0, "heart": 0, "rocket": 0, "eyes": 0} | Resolve the difference between `wrap_view()` and `BaseView` 1726236847 | |
1563308919 | https://github.com/simonw/datasette/issues/2078#issuecomment-1563308919 | https://api.github.com/repos/simonw/datasette/issues/2078 | IC_kwDOBm6k_c5dLjN3 | simonw 9599 | 2023-05-25T18:08:34Z | 2023-05-25T18:08:34Z | OWNER | After much fiddling this seems to work: ```python import asyncio, types def is_async_callable(obj): if not callable(obj): raise ValueError("Object is not callable") if isinstance(obj, types.FunctionType): return asyncio.iscoroutinefunction(obj) if hasattr(obj, '__call__'): return asyncio.iscoroutinefunction(obj.__call__) raise ValueError("Not a function and has no __call__ attribute") ``` Tested like so: ```python class AsyncClass: async def __call__(self): pass class NotAsyncClass: def __call__(self): pass class ClassNoCall: pass async def async_func(): pass def non_async_func(): pass for thing in (AsyncClass(), NotAsyncClass(), ClassNoCall(), async_func, non_async_func): try: print(thing, is_async_callable(thing)) except Exception as ex: print(thing, ex) ``` ``` <__main__.AsyncClass object at 0x106c32150> True <__main__.NotAsyncClass object at 0x106c32390> False <__main__.ClassNoCall object at 0x106c32750> Object is not callable <function async_func at 0x1073d5120> True <function non_async_func at 0x1073d5080> False ``` | {"total_count": 0, "+1": 0, "-1": 0, "laugh": 0, "hooray": 0, "confused": 0, "heart": 0, "rocket": 0, "eyes": 0} | Resolve the difference between `wrap_view()` and `BaseView` 1726236847 | |
1563294669 | https://github.com/simonw/datasette/issues/2078#issuecomment-1563294669 | https://api.github.com/repos/simonw/datasette/issues/2078 | IC_kwDOBm6k_c5dLfvN | simonw 9599 | 2023-05-25T17:57:06Z | 2023-05-25T17:57:06Z | OWNER | I may need to be able to detect if a class instance has an `async def __call__` method - I think I can do that like so: ```python def iscoroutinefunction(obj): if inspect.iscoroutinefunction(obj): return True if hasattr(obj, '__call__') and inspect.iscoroutinefunction(obj.__call__): return True return False ``` From https://github.com/encode/starlette/issues/886#issuecomment-606585152 | {"total_count": 0, "+1": 0, "-1": 0, "laugh": 0, "hooray": 0, "confused": 0, "heart": 0, "rocket": 0, "eyes": 0} | Resolve the difference between `wrap_view()` and `BaseView` 1726236847 | |
1563292373 | https://github.com/simonw/datasette/issues/2078#issuecomment-1563292373 | https://api.github.com/repos/simonw/datasette/issues/2078 | IC_kwDOBm6k_c5dLfLV | simonw 9599 | 2023-05-25T17:55:12Z | 2023-05-25T17:55:30Z | OWNER | So I think subclasses of `BaseView` need to offer a callable which accepts all five of the DI arguments - `datasette`, `request`, `scope`, `send`, `receive` - and then makes a decision based on the HTTP verb as to which method of the class to call. Those methods themselves can accept a subset of those parameters and will only be sent on to them. Having two layers of parameter detection feels a little bit untidy, but I think it will work. | {"total_count": 0, "+1": 0, "-1": 0, "laugh": 0, "hooray": 0, "confused": 0, "heart": 0, "rocket": 0, "eyes": 0} | Resolve the difference between `wrap_view()` and `BaseView` 1726236847 | |
1563285150 | https://github.com/simonw/datasette/pull/2053#issuecomment-1563285150 | https://api.github.com/repos/simonw/datasette/issues/2053 | IC_kwDOBm6k_c5dLdae | simonw 9599 | 2023-05-25T17:48:50Z | 2023-05-25T17:49:52Z | OWNER | Uncommitted experimental code: ```diff diff --git a/datasette/views/database.py b/datasette/views/database.py index 455ebd1f..85775433 100644 --- a/datasette/views/database.py +++ b/datasette/views/database.py @@ -909,12 +909,13 @@ async def query_view( elif format_ in datasette.renderers.keys(): # Dispatch request to the correct output format renderer # (CSV is not handled here due to streaming) + print(data) result = call_with_supported_arguments( datasette.renderers[format_][0], datasette=datasette, - columns=columns, - rows=rows, - sql=sql, + columns=data["rows"][0].keys(), + rows=data["rows"], + sql='', query_name=None, database=db.name, table=None, @@ -923,7 +924,7 @@ async def query_view( # These will be deprecated in Datasette 1.0: args=request.args, data={ - "rows": rows, + "rows": data["rows"], }, # TODO what should this be? ) result = await await_me_maybe(result) diff --git a/docs/index.rst b/docs/index.rst index 5a9cc7ed..254ed3da 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -57,6 +57,7 @@ Contents settings introspection custom_templates + template_context plugins writing_plugins plugin_hooks ``` Where `docs/template_context.rst` looked like this: ```rst .. _template_context: Template context ================ .. currentmodule:: datasette.context This page describes the variables made available to templates used by Datasette to render different pages of the application. .. autoclass:: QueryContext :members: ``` And `datasette/context.py` had this: ```python from dataclasses import dataclass @dataclass class QueryContext: """ Used by the ``/database`` page when showing the results of a SQL query """ id: int "… | {"total_count": 0, "+1": 0, "-1": 0, "laugh": 0, "hooray": 0, "confused": 0, "heart": 0, "rocket": 0, "eyes": 0} | WIP new JSON for queries 1656432059 | |
1563283939 | https://github.com/simonw/datasette/issues/2078#issuecomment-1563283939 | https://api.github.com/repos/simonw/datasette/issues/2078 | IC_kwDOBm6k_c5dLdHj | simonw 9599 | 2023-05-25T17:47:38Z | 2023-05-25T17:47:38Z | OWNER | The idea behind `wrap_view()` is dependency injection - it's mainly used by plugins: https://docs.datasette.io/en/0.64.3/plugin_hooks.html#register-routes-datasette But I like the pattern so I started using it for some of Datasette's own features. I should use it for _all_ of Datasette's own features. But I still like the way `BaseView` helps with running different code for GET/POST/etc verbs. | {"total_count": 0, "+1": 0, "-1": 0, "laugh": 0, "hooray": 0, "confused": 0, "heart": 0, "rocket": 0, "eyes": 0} | Resolve the difference between `wrap_view()` and `BaseView` 1726236847 | |
1563282327 | https://github.com/simonw/datasette/issues/2078#issuecomment-1563282327 | https://api.github.com/repos/simonw/datasette/issues/2078 | IC_kwDOBm6k_c5dLcuX | simonw 9599 | 2023-05-25T17:46:05Z | 2023-05-25T17:46:05Z | OWNER | Here's what `wrap_view()` does: https://github.com/simonw/datasette/blob/49184c569cd70efbda4f3f062afef3a34401d8d5/datasette/app.py#L1676-L1700 It's used e.g. here: https://github.com/simonw/datasette/blob/49184c569cd70efbda4f3f062afef3a34401d8d5/datasette/app.py#L1371-L1375 The `BaseView` thing meanwhile works like this: https://github.com/simonw/datasette/blob/d97e82df3c8a3f2e97038d7080167be9bb74a68d/datasette/views/base.py#L56-L157 | {"total_count": 0, "+1": 0, "-1": 0, "laugh": 0, "hooray": 0, "confused": 0, "heart": 0, "rocket": 0, "eyes": 0} | Resolve the difference between `wrap_view()` and `BaseView` 1726236847 | |
1556292204 | https://github.com/simonw/sqlite-utils/issues/552#issuecomment-1556292204 | https://api.github.com/repos/simonw/sqlite-utils/issues/552 | IC_kwDOCGYnMM5cwyJs | simonw 9599 | 2023-05-21T21:05:15Z | 2023-05-21T21:05:15Z | OWNER | Now live at https://sqlite-utils.datasette.io/en/latest/installation.html#setting-up-shell-completion | {"total_count": 0, "+1": 0, "-1": 0, "laugh": 0, "hooray": 0, "confused": 0, "heart": 0, "rocket": 0, "eyes": 0} | Document how to setup shell auto-completion 1718612569 | |
1556291915 | https://github.com/simonw/sqlite-utils/issues/551#issuecomment-1556291915 | https://api.github.com/repos/simonw/sqlite-utils/issues/551 | IC_kwDOCGYnMM5cwyFL | simonw 9599 | 2023-05-21T21:04:03Z | 2023-05-21T21:04:03Z | OWNER | Now live at https://sqlite-utils.datasette.io/en/latest/cli.html | {"total_count": 0, "+1": 0, "-1": 0, "laugh": 0, "hooray": 0, "confused": 0, "heart": 0, "rocket": 0, "eyes": 0} | Make as many examples in the CLI docs as possible copy-and-pastable 1718607907 | |
1556288300 | https://github.com/simonw/sqlite-utils/pull/553#issuecomment-1556288300 | https://api.github.com/repos/simonw/sqlite-utils/issues/553 | IC_kwDOCGYnMM5cwxMs | simonw 9599 | 2023-05-21T20:48:01Z | 2023-05-21T20:48:01Z | OWNER | If https://sqlite-utils--553.org.readthedocs.build/en/553/cli.html#running-sql-queries looks good I can merge this. | {"total_count": 0, "+1": 0, "-1": 0, "laugh": 0, "hooray": 0, "confused": 0, "heart": 0, "rocket": 0, "eyes": 0} | Reformatted CLI examples in docs 1718635018 | |
1556288270 | https://github.com/simonw/sqlite-utils/issues/551#issuecomment-1556288270 | https://api.github.com/repos/simonw/sqlite-utils/issues/551 | IC_kwDOCGYnMM5cwxMO | simonw 9599 | 2023-05-21T20:47:51Z | 2023-05-21T20:47:51Z | OWNER | This page has all of the changes: https://sqlite-utils--553.org.readthedocs.build/en/553/cli.html#running-sql-queries | {"total_count": 0, "+1": 0, "-1": 0, "laugh": 0, "hooray": 0, "confused": 0, "heart": 0, "rocket": 0, "eyes": 0} | Make as many examples in the CLI docs as possible copy-and-pastable 1718607907 | |
1556287599 | https://github.com/simonw/sqlite-utils/issues/551#issuecomment-1556287599 | https://api.github.com/repos/simonw/sqlite-utils/issues/551 | IC_kwDOCGYnMM5cwxBv | simonw 9599 | 2023-05-21T20:44:55Z | 2023-05-21T20:44:55Z | OWNER | Put this in a PR so I can preview it: - #553 | {"total_count": 0, "+1": 0, "-1": 0, "laugh": 0, "hooray": 0, "confused": 0, "heart": 0, "rocket": 0, "eyes": 0} | Make as many examples in the CLI docs as possible copy-and-pastable 1718607907 | |
1556269616 | https://github.com/simonw/sqlite-utils/issues/545#issuecomment-1556269616 | https://api.github.com/repos/simonw/sqlite-utils/issues/545 | IC_kwDOCGYnMM5cwsow | simonw 9599 | 2023-05-21T19:33:13Z | 2023-05-21T19:33:13Z | OWNER | Now released: https://sqlite-utils.datasette.io/en/stable/changelog.html#v3-32 | {"total_count": 0, "+1": 0, "-1": 0, "laugh": 0, "hooray": 0, "confused": 0, "heart": 0, "rocket": 0, "eyes": 0} | Try out Trogon for a tui interface 1718517882 | |
1556265772 | https://github.com/simonw/sqlite-utils/issues/551#issuecomment-1556265772 | https://api.github.com/repos/simonw/sqlite-utils/issues/551 | IC_kwDOCGYnMM5cwrss | simonw 9599 | 2023-05-21T19:16:15Z | 2023-05-21T19:16:15Z | OWNER | Another option: <img width="593" alt="image" src="https://github.com/simonw/sqlite-utils/assets/9599/a9d819a0-89e3-4b67-b2f0-1052b0a7a7c0"> That's using this markup: ``` Newline-delimited JSON ~~~~~~~~~~~~~~~~~~~~~~ Use ``--nl`` to get back newline-delimited JSON objects: .. code-block:: bash sqlite-utils dogs.db "select * from dogs" --nl .. code-block:: output {"id": 1, "age": 4, "name": "Cleo"} {"id": 2, "age": 2, "name": "Pancakes"} ``` And this extra CSS: ```css .highlight-output .highlight { border-left: 9px solid #30c94f; } ``` | {"total_count": 0, "+1": 0, "-1": 0, "laugh": 0, "hooray": 0, "confused": 0, "heart": 0, "rocket": 0, "eyes": 0} | Make as many examples in the CLI docs as possible copy-and-pastable 1718607907 | |
1556263182 | https://github.com/simonw/sqlite-utils/issues/551#issuecomment-1556263182 | https://api.github.com/repos/simonw/sqlite-utils/issues/551 | IC_kwDOCGYnMM5cwrEO | simonw 9599 | 2023-05-21T19:06:48Z | 2023-05-21T19:06:48Z | OWNER | I could split them up into two blocks like this: <img width="771" alt="image" src="https://github.com/simonw/sqlite-utils/assets/9599/9b042ac8-db30-49d5-8882-1ea7be272a49"> I do miss the visual indication that one of these is the command and one is the output though. | {"total_count": 0, "+1": 0, "-1": 0, "laugh": 0, "hooray": 0, "confused": 0, "heart": 0, "rocket": 0, "eyes": 0} | Make as many examples in the CLI docs as possible copy-and-pastable 1718607907 | |
1556262574 | https://github.com/simonw/sqlite-utils/issues/551#issuecomment-1556262574 | https://api.github.com/repos/simonw/sqlite-utils/issues/551 | IC_kwDOCGYnMM5cwq6u | simonw 9599 | 2023-05-21T19:04:59Z | 2023-05-21T19:04:59Z | OWNER | I wrote the docs like this because early examples include both the command and its output: https://sqlite-utils.datasette.io/en/stable/cli.html#returning-json <img width="766" alt="image" src="https://github.com/simonw/sqlite-utils/assets/9599/a5d05f24-ca0f-4718-a0a8-d85adc736141"> | {"total_count": 0, "+1": 0, "-1": 0, "laugh": 0, "hooray": 0, "confused": 0, "heart": 0, "rocket": 0, "eyes": 0} | Make as many examples in the CLI docs as possible copy-and-pastable 1718607907 | |
1556255309 | https://github.com/simonw/sqlite-utils/issues/550#issuecomment-1556255309 | https://api.github.com/repos/simonw/sqlite-utils/issues/550 | IC_kwDOCGYnMM5cwpJN | simonw 9599 | 2023-05-21T18:42:25Z | 2023-05-21T18:42:25Z | OWNER | Tests passed here: https://github.com/simonw/sqlite-utils/actions/runs/5039119716 | {"total_count": 0, "+1": 0, "-1": 0, "laugh": 0, "hooray": 0, "confused": 0, "heart": 0, "rocket": 0, "eyes": 0} | AttributeError: 'EntryPoints' object has no attribute 'get' for flake8 on Python 3.7 1718595700 |
Advanced export
JSON shape: default, array, newline-delimited, object
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]);
updated_at (date) 992 ✖