{"id": 2029908157, "node_id": "I_kwDOBm6k_c54_fC9", "number": 2214, "title": "CSV export fails for some `text` foreign key references", "user": {"value": 2874, "label": "precipice"}, "state": "open", "locked": 0, "assignee": null, "milestone": null, "comments": 1, "created_at": "2023-12-07T05:04:34Z", "updated_at": "2023-12-07T07:36:34Z", "closed_at": null, "author_association": "NONE", "pull_request": null, "body": "I'm starting this issue without a clear reproduction in case someone else has seen this behavior, and to use the issue as a notebook for research. \r\n\r\nI'm using Datasette with the [SWITRS](https://iswitrs.chp.ca.gov/) data set, which is a California Highway Patrol collection of traffic incident data from the past decade or so. I receive data from them in CSV and want to work with it in Datasette, then export it to CSV for mapping in Felt.com.\r\n\r\nTheir data makes extensive use of codes for incident column data (`1` for `Monday` and so on), some of it integer codes and some of it letter/text codes. The text codes are sometimes blank or `-`. During import, I'm creating lookup tables for foreign key references to make the Datasette UI presentation of the data easier to read.\r\n\r\nIf I import the data and set up the integer foreign keys, everything works fine, but if I set up the text foreign keys, CSV export starts to fail. \r\n\r\nThe foreign key configuration is as follows:\r\n\r\n```\r\n# Some tables use integer ids, like sensible tables do. Let's import them first\r\n# since we favor them.\r\n\r\nfor TABLE in DAY_OF_WEEK CHP_SHIFT POPULATION SPECIAL_COND BEAT_TYPE COLLISION_SEVERITY\r\ndo\r\n\tsqlite-utils create-table records.db $TABLE id integer name text --pk=id\r\n\tsqlite-utils insert records.db $TABLE lookup-tables/$TABLE.csv --csv\r\n\tsqlite-utils add-foreign-key records.db collisions $TABLE $TABLE id\r\n\tsqlite-utils create-index records.db collisions $TABLE\r\ndone\r\n\r\n# *Other* tables use letter keys, like they were raised by WOLVES. Let's put them\r\n# at the end of the import queue.\r\n\r\nfor TABLE in WEATHER_1 WEATHER_2 LOCATION_TYPE RAMP_INTERSECTION SIDE_OF_HWY \\\r\nPRIMARY_COLL_FACTOR PCF_CODE_OF_VIOL PCF_VIOL_CATEGORY TYPE_OF_COLLISION MVIW \\\r\nPED_ACTION ROAD_SURFACE ROAD_COND_1 ROAD_COND_2 LIGHTING CONTROL_DEVICE \\\r\nSTWD_VEHTYPE_AT_FAULT CHP_VEHTYPE_AT_FAULT PRIMARY_RAMP SECONDARY_RAMP\r\ndo\r\n\tsqlite-utils create-table records.db $TABLE key text name text --pk=key\r\n\tsqlite-utils insert records.db $TABLE lookup-tables/$TABLE.csv --csv\r\n\tsqlite-utils add-foreign-key records.db collisions $TABLE $TABLE key\r\n\tsqlite-utils create-index records.db collisions $TABLE\r\ndone\r\n```\r\n\r\nYou can see the full code and import script here: https://github.com/radical-bike-lobby/switrs-db\r\n\r\nIf I run this code and then hit the CSV export link in the Datasette interface (the simple link or the \"advanced\" dialog), export fails after a small number of CSV rows are written. I am not seeing any detailed error messages but this appears in the logging output:\r\n\r\n```\r\nINFO: 127.0.0.1:57885 - \"GET /records/collisions.csv?_facet=PRIMARY_RD&PRIMARY_RD=ASHBY+AV&_labels=on&_size=max HTTP/1.1\" 200 OK\r\nCaught this error: \r\n\r\n```\r\n\r\n(No other output follows `error:` other than a blank line.)\r\n\r\nI've stared at the rows directly after the error occurs and can't yet see what is causing the problem. I'm going to set up a development environment and see if I get any more detailed error output, and then stare more at some problematic lines to see if I can get a simple reproduction.", "repo": {"value": 107914493, "label": "datasette"}, "type": "issue", "active_lock_reason": null, "performed_via_github_app": null, "reactions": "{\"url\": \"https://api.github.com/repos/simonw/datasette/issues/2214/reactions\", \"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "draft": null, "state_reason": null} {"id": 2023057255, "node_id": "I_kwDOBm6k_c54lWdn", "number": 2212, "title": "Can't filter with numbers", "user": {"value": 605070, "label": "fzakaria"}, "state": "open", "locked": 0, "assignee": null, "milestone": null, "comments": 0, "created_at": "2023-12-04T05:26:29Z", "updated_at": "2023-12-04T05:26:29Z", "closed_at": null, "author_association": "NONE", "pull_request": null, "body": "I have a schema that uses numbers for a column (actually it's a boolean 1 or 0 but SQLite doesn't have Boolean).\r\nI can't seem to get the facet to work or even filtering on this column.\r\n\r\nMy guess is that Datasette is \"stringifying\" the number and it's not matching?\r\nExample: https://debian-sqlelf.fly.dev/debian/elf_symbols?_sort_desc=name&_facet=exported&exported=0", "repo": {"value": 107914493, "label": "datasette"}, "type": "issue", "active_lock_reason": null, "performed_via_github_app": null, "reactions": "{\"url\": \"https://api.github.com/repos/simonw/datasette/issues/2212/reactions\", \"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "draft": null, "state_reason": null} {"id": 2019811176, "node_id": "I_kwDOBm6k_c54Y99o", "number": 2211, "title": "Unreachable exception handlers for `sqlite3.OperationalError`", "user": {"value": 1214074, "label": "mattparmett"}, "state": "open", "locked": 0, "assignee": null, "milestone": null, "comments": 0, "created_at": "2023-12-01T00:50:22Z", "updated_at": "2023-12-01T00:50:22Z", "closed_at": null, "author_association": "NONE", "pull_request": null, "body": "There are several places where `sqlite3.OperationalError` is caught as part of an exception handler which catches multiple exceptions, but is then caught again immediately afterwards by a dedicated exception handler.\r\n\r\nBecause the exception will be caught by the first handler, the logic in the second handler is unreachable and will never be executed. If this is intended behavior, the second handler can be removed. If this is not intended, and the second handler should be the one that catches this exception, then `sqlite3.OperationalError` should be removed from the tuple of exceptions in the first handler.\r\n\r\nThis issue was found via a CodeQL query on the repository, and I've listed the occurrences found by the query below. There may be other instances of this issue in the code that were not surfaced by the query. I'd be happy to share the query if others would like to view or run it.\r\n\r\nOne example:\r\n\r\nhttps://github.com/simonw/datasette/blob/452a587e236ef642cbc6ae345b58767ea8420cb5/datasette/views/database.py#L534-L537\r\n\r\nOther instances:\r\n\r\nhttps://github.com/simonw/datasette/blob/main/datasette/views/base.py#L266-L270\r\nhttps://github.com/simonw/datasette/blob/main/datasette/views/base.py#L452-L456", "repo": {"value": 107914493, "label": "datasette"}, "type": "issue", "active_lock_reason": null, "performed_via_github_app": null, "reactions": "{\"url\": \"https://api.github.com/repos/simonw/datasette/issues/2211/reactions\", \"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "draft": null, "state_reason": null} {"id": 1994845152, "node_id": "I_kwDOBm6k_c525uvg", "number": 2207, "title": "ModuleNotFoundError: No module named 'click_default_group", "user": {"value": 283441, "label": "honzajavorek"}, "state": "open", "locked": 0, "assignee": null, "milestone": null, "comments": 0, "created_at": "2023-11-15T14:04:32Z", "updated_at": "2023-11-15T14:04:32Z", "closed_at": null, "author_association": "NONE", "pull_request": null, "body": "No matter what I do, I'm getting this error:\r\n\r\n```\r\n$ datasette\r\nTraceback (most recent call last):\r\n File \"/Users/honza/Library/Caches/pypoetry/virtualenvs/juniorguru-Lgaxwd2n-py3.11/bin/datasette\", line 5, in \r\n from datasette.cli import cli\r\n File \"/Users/honza/Library/Caches/pypoetry/virtualenvs/juniorguru-Lgaxwd2n-py3.11/lib/python3.11/site-packages/datasette/cli.py\", line 6, in \r\n from click_default_group import DefaultGroup\r\nModuleNotFoundError: No module named 'click_default_group'\r\n```\r\n\r\nI have datasette in my dependencies like this:\r\n\r\n```toml\r\n[tool.poetry.group.dev.dependencies]\r\ndatasette = {version = \"1.0a7\", allow-prereleases = true}\r\n```\r\n\r\nI had the latest regular version (not pre-release) there originally, but the result was the same:\r\n\r\n```toml\r\n[tool.poetry.group.dev.dependencies]\r\ndatasette = \"0.64.5\"\r\n```\r\n\r\nFull pyproject.toml is at https://github.com/honzajavorek/junior.guru/ Previously datasette worked for me, but I guess something had to upgrade and now I can't even launch it.", "repo": {"value": 107914493, "label": "datasette"}, "type": "issue", "active_lock_reason": null, "performed_via_github_app": null, "reactions": "{\"url\": \"https://api.github.com/repos/simonw/datasette/issues/2207/reactions\", \"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "draft": null, "state_reason": null} {"id": 1977726056, "node_id": "I_kwDOBm6k_c514bRo", "number": 2203, "title": "custom plugin not seen as sql function", "user": {"value": 7113541, "label": "LyzardKing"}, "state": "open", "locked": 0, "assignee": null, "milestone": null, "comments": 0, "created_at": "2023-11-05T10:30:19Z", "updated_at": "2023-11-05T10:30:19Z", "closed_at": null, "author_association": "NONE", "pull_request": null, "body": "Hi, I'm not sure if this is the right repo for this issue.\r\n\r\nI'm using datasette with the parquet (to read a duckdb), and jellyfish plugins. Both work perfectly.\r\n\r\nNow I need to create a simple plugin that uses the python rouge package and returns a similarity score (similarly to how the jellyfish plugin works).\r\nIf I create a custom plugin, even the example hello_world one, copied directly from the tutorial, I get the following error:\r\n```duckdb.duckdb.CatalogException: Catalog Error: Scalar Function with name hello_world does not exist!```\r\n\r\nSince the jellyfish plugin doesn't do anything more complex, I'm wondering if there is some other kind of issue with my setup.", "repo": {"value": 107914493, "label": "datasette"}, "type": "issue", "active_lock_reason": null, "performed_via_github_app": null, "reactions": "{\"url\": \"https://api.github.com/repos/simonw/datasette/issues/2203/reactions\", \"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "draft": null, "state_reason": null} {"id": 410384988, "node_id": "MDU6SXNzdWU0MTAzODQ5ODg=", "number": 411, "title": "How to pass named parameter into spatialite MakePoint() function", "user": {"value": 1055831, "label": "dazzag24"}, "state": "closed", "locked": 0, "assignee": null, "milestone": null, "comments": 3, "created_at": "2019-02-14T16:30:22Z", "updated_at": "2023-10-25T13:23:04Z", "closed_at": "2019-05-05T12:25:04Z", "author_association": "NONE", "pull_request": null, "body": "Hi,\r\ndatasette version: \"0.26.2\"\r\nextensions: \r\n spatialite: \"4.4.0-RC0\"\r\nsqlite version: \"3.22.0\"\r\n\r\nI have a table of airports with latitude and longitude columns. I've added spatialite (with KNN support). After creating the db using csvs-to-sqlit, I run these commands to setup the spatialite tables:\r\n\r\n```\r\nconn.execute('SELECT InitSpatialMetadata(1)')\r\n\r\nconn.execute(\"SELECT AddGeometryColumn('airports', 'point_geom', 4326, 'POINT', 2);\")\r\n\r\nconn.execute('''UPDATE airports SET point_geom = GeomFromText('POINT('||\"longitude\"||' '||\"latitude\"||')',4326);''')\r\n\r\nconn.execute(\"SELECT CreateSpatialIndex('airports', 'point_geom');\")\r\n```\r\n\r\nI'm attempting to create a canned query and have this in my metadata.json file:\r\n```\r\n\"find_airports_nearest_to_point\":{\r\n \"sql\":\"SELECT a.pos AS rank, b.id, b.name, b.country, b.latitude AS latitude, b.longitude AS longitude, a.distance / 1000.0 AS dist_km FROM KNN AS a JOIN airports AS b ON (b.rowid = a.fid) WHERE f_table_name = \\\"airports\\\" AND ref_geometry = MakePoint( :Long , :Lat ) AND max_items = 10;\"}\r\n```\r\nwhich doesn't seem to perform the templating of the name parameters correctly and I get no results. \r\n\r\nHave also tired:\r\n```\r\nMakePoint( || :Long || , || :Lat || )\r\n```\r\nwhich returns this error:\r\n```\r\nnear \"||\": syntax error\r\n```\r\n\r\nHowever I cannot seem to find the correct combination of named parameter syntax (:Lat) or sqlite concatenation operator to make it work. Any ideas if using named parameters inside functions is supported?\r\n\r\nThanks\r\nDarren", "repo": {"value": 107914493, "label": "datasette"}, "type": "issue", "active_lock_reason": null, "performed_via_github_app": null, "reactions": "{\"url\": \"https://api.github.com/repos/simonw/datasette/issues/411/reactions\", \"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "draft": null, "state_reason": "completed"} {"id": 1955676270, "node_id": "I_kwDOBm6k_c50kUBu", "number": 2201, "title": "Discord invite link is invalid", "user": {"value": 11708906, "label": "andrewsanchez"}, "state": "open", "locked": 0, "assignee": null, "milestone": null, "comments": 0, "created_at": "2023-10-21T21:50:05Z", "updated_at": "2023-10-21T21:50:05Z", "closed_at": null, "author_association": "NONE", "pull_request": null, "body": "https://datasette.io/discord leads to https://discord.com/invite/ktd74dm5mw and returns the following:\r\n\r\n\"CleanShot\r\n", "repo": {"value": 107914493, "label": "datasette"}, "type": "issue", "active_lock_reason": null, "performed_via_github_app": null, "reactions": "{\"url\": \"https://api.github.com/repos/simonw/datasette/issues/2201/reactions\", \"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "draft": null, "state_reason": null} {"id": 1910269679, "node_id": "I_kwDOBm6k_c5x3Gbv", "number": 2196, "title": "Discord invite link returns 401", "user": {"value": 1892194, "label": "Olshansk"}, "state": "closed", "locked": 0, "assignee": null, "milestone": null, "comments": 2, "created_at": "2023-09-24T15:16:54Z", "updated_at": "2023-10-13T00:07:08Z", "closed_at": "2023-10-12T21:54:54Z", "author_association": "NONE", "pull_request": null, "body": "I found the link to the datasette discord channel via [this query](https://github.com/search?q=repo%3Asimonw%2Fdatasette%20discord&type=code).\r\n\r\nThe following video should be self explanatory:\r\n\r\nhttps://github.com/simonw/datasette/assets/1892194/8cd33e88-bcaa-41f3-9818-ab4d589c3f02\r\n\r\n\r\nLink for reference: https://discord.com/invite/ktd74dm5mw", "repo": {"value": 107914493, "label": "datasette"}, "type": "issue", "active_lock_reason": null, "performed_via_github_app": null, "reactions": "{\"url\": \"https://api.github.com/repos/simonw/datasette/issues/2196/reactions\", \"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "draft": null, "state_reason": "completed"} {"id": 1930008379, "node_id": "I_kwDOBm6k_c5zCZc7", "number": 2197, "title": "click-default-group-wheel dependency conflict", "user": {"value": 1176293, "label": "ar-jan"}, "state": "closed", "locked": 0, "assignee": null, "milestone": null, "comments": 3, "created_at": "2023-10-06T11:49:20Z", "updated_at": "2023-10-12T21:53:17Z", "closed_at": "2023-10-12T21:53:17Z", "author_association": "NONE", "pull_request": null, "body": "I upgraded my dependencies, then ran into this problem running `datasette inspect`:\r\n\r\n> env/lib/python3.9/site-packages/datasette/cli.py\", line 6, in \r\n> from click_default_group import DefaultGroup\r\n> ModuleNotFoundError: No module named 'click_default_group'\r\n\r\nTurns out the released version of datasette still depends on `click-default-group-wheel`, so `click-default-group` doesn't get installed/recognized:\r\n\r\n```\r\n$ virtualenv venv\r\n$ source venv/bin/activate\r\n$ pip install datasette\r\n$ pip list | grep click-default-group\r\nclick-default-group 1.2.4\r\nclick-default-group-wheel 1.2.3\r\n$ python -c \"from click_default_group import DefaultGroup\"\r\nTraceback (most recent call last):\r\n File \"\", line 1, in \r\nModuleNotFoundError: No module named 'click_default_group'\r\n$ pip install --force-reinstall click-default-group\r\n...\r\nERROR: pip's dependency resolver does not currently take into account all the packages that are installed.\r\nThis behaviour is the source of the following dependency conflicts.\r\ndatasette 0.64.4 requires click-default-group-wheel>=1.2.2, which is not installed.\r\nSuccessfully installed click-8.1.7 click-default-group-1.2.4\r\n```", "repo": {"value": 107914493, "label": "datasette"}, "type": "issue", "active_lock_reason": null, "performed_via_github_app": null, "reactions": "{\"url\": \"https://api.github.com/repos/simonw/datasette/issues/2197/reactions\", \"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "draft": null, "state_reason": "completed"} {"id": 1931794126, "node_id": "I_kwDOBm6k_c5zJNbO", "number": 2198, "title": "--load-extension=spatialite not working with Windows", "user": {"value": 363004, "label": "hcarter333"}, "state": "open", "locked": 0, "assignee": null, "milestone": null, "comments": 0, "created_at": "2023-10-08T12:50:22Z", "updated_at": "2023-10-08T12:50:22Z", "closed_at": null, "author_association": "NONE", "pull_request": null, "body": "Using each of\r\n`python -m datasette counties.db -m metadata.yml --load-extension=SpatiaLite`\r\n\r\nand \r\n\r\n`python -m datasette counties.db --load-extension=\"C:\\Windows\\System32\\mod_spatialite.dll\"`\r\n\r\nand\r\n\r\n`python -m datasette counties.db --load-extension=C:\\Windows\\System32\\mod_spatialite.dll`\r\n\r\nI got the error:\r\n\r\n```\r\n File \"C:\\Users\\m3n7es\\AppData\\Local\\Packages\\PythonSoftwareFoundation.Python.3.11_qbz5n2kfra8p0\\LocalCache\\local-packages\\Python311\\site-packages\\datasette\\database.py\", line 209, in in_thread\r\n self.ds._prepare_connection(conn, self.name)\r\n File \"C:\\Users\\m3n7es\\AppData\\Local\\Packages\\PythonSoftwareFoundation.Python.3.11_qbz5n2kfra8p0\\LocalCache\\local-packages\\Python311\\site-packages\\datasette\\app.py\", line 596, in _prepare_connection\r\n conn.execute(\"SELECT load_extension(?, ?)\", [path, entrypoint])\r\nsqlite3.OperationalError: The specified module could not be found.\r\n\r\n```\r\n\r\nI finally tried modifying the code in app.py to read:\r\n\r\n```\r\n def _prepare_connection(self, conn, database):\r\n conn.row_factory = sqlite3.Row\r\n conn.text_factory = lambda x: str(x, \"utf-8\", \"replace\")\r\n if self.sqlite_extensions:\r\n conn.enable_load_extension(True)\r\n for extension in self.sqlite_extensions:\r\n # \"extension\" is either a string path to the extension\r\n # or a 2-item tuple that specifies which entrypoint to load.\r\n #if isinstance(extension, tuple):\r\n # path, entrypoint = extension\r\n # conn.execute(\"SELECT load_extension(?, ?)\", [path, entrypoint])\r\n #else:\r\n conn.execute(\"SELECT load_extension('C:\\Windows\\System32\\mod_spatialite.dll')\")\r\n\r\n```\r\nAt which point the counties example worked. \r\n\r\nIs there a correct way to install/use the extension on Windows? My method will cause issues if there's a second extension to be used.\r\n\r\nOn an unrelated note, my next step is to figure out how to write a query across the two loaded databases supplied from the command line:\r\n`python -m datasette rm_toucans_23_10_07.db counties.db -m metadata.yml --load-extension=SpatiaLite`\r\n\r\n\r\n\r\n", "repo": {"value": 107914493, "label": "datasette"}, "type": "issue", "active_lock_reason": null, "performed_via_github_app": null, "reactions": "{\"url\": \"https://api.github.com/repos/simonw/datasette/issues/2198/reactions\", \"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "draft": null, "state_reason": null} {"id": 1825007061, "node_id": "I_kwDOBm6k_c5sx2XV", "number": 2123, "title": "datasette serve when invoked with --reload interprets the serve command as a file", "user": {"value": 79087, "label": "cadeef"}, "state": "open", "locked": 0, "assignee": null, "milestone": null, "comments": 2, "created_at": "2023-07-27T19:07:22Z", "updated_at": "2023-09-18T13:02:46Z", "closed_at": null, "author_association": "NONE", "pull_request": null, "body": "When running `datasette serve` with the `--reload` flag, the serve command is picked up as a file argument:\r\n\r\n```\r\n$ datasette serve --reload test_db\r\nStarting monitor for PID 13574.\r\nError: Invalid value for '[FILES]...': Path 'serve' does not exist.\r\nPress ENTER or change a file to reload.\r\n```\r\n\r\nIf a 'serve' file is created it launches properly (albeit with an empty database called serve):\r\n\r\n```\r\n$ touch serve; datasette serve --reload test_db\r\nStarting monitor for PID 13628.\r\nINFO: Started server process [13628]\r\nINFO: Waiting for application startup.\r\nINFO: Application startup complete.\r\nINFO: Uvicorn running on http://127.0.0.1:8001 (Press CTRL+C to quit)\r\n```\r\n\r\nVersion (running from HEAD on main):\r\n\r\n```\r\n$ datasette --version\r\ndatasette, version 1.0a2\r\n```\r\n\r\nThis issue appears to have existed for awhile as https://github.com/simonw/datasette/issues/1380#issuecomment-953366110 mentions the error in a different context.\r\n\r\nI'm happy to debug and land a patch if it's welcome.", "repo": {"value": 107914493, "label": "datasette"}, "type": "issue", "active_lock_reason": null, "performed_via_github_app": null, "reactions": "{\"url\": \"https://api.github.com/repos/simonw/datasette/issues/2123/reactions\", \"total_count\": 2, \"+1\": 2, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "draft": null, "state_reason": null} {"id": 1899310542, "node_id": "I_kwDOBm6k_c5xNS3O", "number": 2187, "title": "Datasette for serving JSON only", "user": {"value": 19705106, "label": "geofinder"}, "state": "open", "locked": 0, "assignee": null, "milestone": null, "comments": 0, "created_at": "2023-09-16T05:48:29Z", "updated_at": "2023-09-16T05:48:29Z", "closed_at": null, "author_association": "NONE", "pull_request": null, "body": "Hi, is there any way to use datasette for serving json only without displaying webpage? I've tried to search about this in documentation but didn't get any information", "repo": {"value": 107914493, "label": "datasette"}, "type": "issue", "active_lock_reason": null, "performed_via_github_app": null, "reactions": "{\"url\": \"https://api.github.com/repos/simonw/datasette/issues/2187/reactions\", \"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "draft": null, "state_reason": null} {"id": 1010112818, "node_id": "I_kwDOBm6k_c48NRky", "number": 1479, "title": "Win32 \"used by another process\" error with datasette publish", "user": {"value": 76450761, "label": "kirajano"}, "state": "open", "locked": 0, "assignee": null, "milestone": null, "comments": 7, "created_at": "2021-09-28T19:12:00Z", "updated_at": "2023-09-07T02:14:16Z", "closed_at": null, "author_association": "NONE", "pull_request": null, "body": "I unfortunately was not successful to deploy to fly.io. Please see the details above of the three scenarios that I took. I am also new to datasette.\r\n\r\nFailed to deploy. Attaching logs:\r\n1. Tried with an app created via `flyctl apps create frosty-fog-8565` and the ran `datasette publish fly covid.db --app frosty-fog-8565` \r\n``` \r\nDeploying frosty-fog-8565\r\n==> Validating app configuration\r\n--> Validating app configuration done\r\nServices\r\nTCP 80/443 \u21e2 8080\r\n\r\nError error connecting to docker: An unknown error occured.\r\n\r\nTraceback (most recent call last):\r\n File \"c:\\users\\grott\\anaconda3\\lib\\runpy.py\", line 193, in _run_module_as_main\r\n \"__main__\", mod_spec)\r\n File \"c:\\users\\grott\\anaconda3\\lib\\runpy.py\", line 85, in _run_code\r\n exec(code, run_globals)\r\n File \"C:\\Users\\grott\\Anaconda3\\Scripts\\datasette.exe\\__main__.py\", line 7, in \r\n File \"c:\\users\\grott\\anaconda3\\lib\\site-packages\\click\\core.py\", line 829, in __call__\r\n return self.main(*args, **kwargs)\r\n File \"c:\\users\\grott\\anaconda3\\lib\\site-packages\\click\\core.py\", line 782, in main\r\n rv = self.invoke(ctx)\r\n File \"c:\\users\\grott\\anaconda3\\lib\\site-packages\\click\\core.py\", line 1259, in invoke\r\n return _process_result(sub_ctx.command.invoke(sub_ctx))\r\n File \"c:\\users\\grott\\anaconda3\\lib\\site-packages\\click\\core.py\", line 1259, in invoke\r\n return _process_result(sub_ctx.command.invoke(sub_ctx))\r\n File \"c:\\users\\grott\\anaconda3\\lib\\site-packages\\click\\core.py\", line 1066, in invoke\r\n return ctx.invoke(self.callback, **ctx.params)\r\n File \"c:\\users\\grott\\anaconda3\\lib\\site-packages\\click\\core.py\", line 610, in invoke\r\n return callback(*args, **kwargs)\r\n File \"c:\\users\\grott\\anaconda3\\lib\\site-packages\\datasette_publish_fly\\__init__.py\", line 156, in fly\r\n \"--remote-only\",\r\n File \"c:\\users\\grott\\anaconda3\\lib\\contextlib.py\", line 119, in __exit__\r\n next(self.gen)\r\n File \"c:\\users\\grott\\anaconda3\\lib\\site-packages\\datasette\\utils\\__init__.py\", line 451, in temporary_docker_directory\r\n tmp.cleanup()\r\n File \"c:\\users\\grott\\anaconda3\\lib\\tempfile.py\", line 811, in cleanup\r\n _shutil.rmtree(self.name)\r\n File \"c:\\users\\grott\\anaconda3\\lib\\shutil.py\", line 516, in rmtree\r\n return _rmtree_unsafe(path, onerror)\r\n File \"c:\\users\\grott\\anaconda3\\lib\\shutil.py\", line 395, in _rmtree_unsafe\r\n _rmtree_unsafe(fullname, onerror)\r\n File \"c:\\users\\grott\\anaconda3\\lib\\shutil.py\", line 404, in _rmtree_unsafe\r\n onerror(os.rmdir, path, sys.exc_info())\r\n File \"c:\\users\\grott\\anaconda3\\lib\\shutil.py\", line 402, in _rmtree_unsafe\r\n os.rmdir(path)\r\nPermissionError: [WinError 32] The process cannot access the file because it is being used by another process: 'C:\\\\Users\\\\grott\\\\AppData\\\\Local\\\\Temp\\\\tmpgcm8cz66\\\\frosty-fog-8565'\r\n```\r\n\r\n2. Tried also with an app that gets autogenerate when running `flyctl launch`. This also generates the .toml file. Ran then `datasette publish fly covid.db --app dark-feather-168` **but different error now**\r\n```Deploying dark-feather-168\r\n==> Validating app configuration\r\n\r\nError not possible to validate configuration: server returned Post \"https://api.fly.io/graphql\": unexpected EOF\r\n\r\nTraceback (most recent call last):\r\n File \"c:\\users\\grott\\anaconda3\\lib\\runpy.py\", line 193, in _run_module_as_main \r\n \"__main__\", mod_spec)\r\n exec(code, run_globals)\r\n File \"C:\\Users\\grott\\Anaconda3\\Scripts\\datasette.exe\\__main__.py\", line 7, in \r\n File \"c:\\users\\grott\\anaconda3\\lib\\site-packages\\click\\core.py\", line 829, in __call__\r\n return self.main(*args, **kwargs)\r\n File \"c:\\users\\grott\\anaconda3\\lib\\site-packages\\click\\core.py\", line 782, in main\r\n rv = self.invoke(ctx)\r\n File \"c:\\users\\grott\\anaconda3\\lib\\site-packages\\click\\core.py\", line 1259, in invoke\r\n return _process_result(sub_ctx.command.invoke(sub_ctx))\r\n File \"c:\\users\\grott\\anaconda3\\lib\\site-packages\\click\\core.py\", line 1259, in invoke\r\n return _process_result(sub_ctx.command.invoke(sub_ctx))\r\n File \"c:\\users\\grott\\anaconda3\\lib\\site-packages\\click\\core.py\", line 1066, in invoke\r\n return ctx.invoke(self.callback, **ctx.params)\r\n File \"c:\\users\\grott\\anaconda3\\lib\\site-packages\\click\\core.py\", line 610, in invoke\r\n return callback(*args, **kwargs)\r\n File \"c:\\users\\grott\\anaconda3\\lib\\site-packages\\datasette_publish_fly\\__init__.py\", line 156, in fly\r\n \"--remote-only\",\r\n File \"c:\\users\\grott\\anaconda3\\lib\\contextlib.py\", line 119, in __exit__\r\n next(self.gen)\r\n File \"c:\\users\\grott\\anaconda3\\lib\\site-packages\\datasette\\utils\\__init__.py\", line 451, in temporary_docker_directory\r\n tmp.cleanup()\r\n File \"c:\\users\\grott\\anaconda3\\lib\\tempfile.py\", line 811, in cleanup\r\n _shutil.rmtree(self.name)\r\n File \"c:\\users\\grott\\anaconda3\\lib\\shutil.py\", line 516, in rmtree\r\n return _rmtree_unsafe(path, onerror)\r\n File \"c:\\users\\grott\\anaconda3\\lib\\shutil.py\", line 395, in _rmtree_unsafe\r\n _rmtree_unsafe(fullname, onerror)\r\n File \"c:\\users\\grott\\anaconda3\\lib\\shutil.py\", line 404, in _rmtree_unsafe\r\n onerror(os.rmdir, path, sys.exc_info())\r\n File \"c:\\users\\grott\\anaconda3\\lib\\shutil.py\", line 402, in _rmtree_unsafe\r\n os.rmdir(path)\r\nPermissionError: [WinError 32] The process cannot access the file because it is being used by another process: 'C:\\\\Users\\\\grott\\\\AppData\\\\Local\\\\Temp\\\\tmpnoyewcre\\\\dark-feather-168'\r\n```\r\n\r\nThese are also the contents of the generated **.toml file** in 2 scenario:\r\n\r\n```\r\n# fly.toml file generated for dark-feather-168 on 2021-09-28T20:35:44+02:00\r\n\r\napp = \"dark-feather-168\"\r\n\r\nkill_signal = \"SIGINT\"\r\nkill_timeout = 5\r\nprocesses = []\r\n\r\n[env]\r\n\r\n[experimental]\r\n allowed_public_ports = []\r\n auto_rollback = true\r\n\r\n[[services]]\r\n http_checks = []\r\n internal_port = 8080\r\n processes = [\"app\"]\r\n protocol = \"tcp\"\r\n script_checks = []\r\n\r\n [services.concurrency]\r\n hard_limit = 25\r\n soft_limit = 20\r\n type = \"connections\"\r\n\r\n [[services.ports]]\r\n handlers = [\"http\"]\r\n port = 80\r\n\r\n [[services.ports]]\r\n handlers = [\"tls\", \"http\"]\r\n port = 443\r\n\r\n [[services.tcp_checks]]\r\n grace_period = \"1s\"\r\n interval = \"15s\"\r\n restart_limit = 6\r\n timeout = \"2s\"\r\n```\r\n\r\n3. But also trying `datasette package covid.db` to create a local DOCKERFILE to later try to push it via `flyctl deploy` fails as well.\r\n\r\n```[+] Building 147.3s (11/11) FINISHED\r\n => [internal] load build definition from Dockerfile 0.2s \r\n => => transferring dockerfile: 396B 0.0s \r\n => [internal] load .dockerignore 0.1s \r\n => => transferring context: 2B 0.0s \r\n => [internal] load metadata for docker.io/library/python:3.8 4.7s \r\n => [auth] library/python:pull token for registry-1.docker.io 0.0s \r\n => [internal] load build context 0.1s \r\n => => transferring context: 82.37kB 0.0s \r\n => [1/5] FROM docker.io/library/python:3.8@sha256:530de807b46a11734e2587a784573c12c5034f2f14025f838589e6c0e3 108.3s \r\n => => resolve docker.io/library/python:3.8@sha256:530de807b46a11734e2587a784573c12c5034f2f14025f838589e6c0e3b5 0.0s \r\n => => sha256:56182bcdf4d4283aa1f46944b4ef7ac881e28b4d5526720a4e9ba03a4730846a 2.22kB / 2.22kB 0.0s \r\n => => sha256:955615a668ce169f8a1443fc6b6e6215f43fe0babfb4790712a2d3171f34d366 54.93MB / 54.93MB 21.6s \r\n => => sha256:911ea9f2bd51e53a455297e0631e18a72a86d7e2c8e1807176e80f991bde5d64 10.87MB / 10.87MB 15.5s \r\n => => sha256:530de807b46a11734e2587a784573c12c5034f2f14025f838589e6c0e3b5c5b6 1.86kB / 1.86kB 0.0s \r\n => => sha256:ff08f08727e50193dcf499afc30594c47e70cc96f6fcfd1a01240524624264d0 8.65kB / 8.65kB 0.0s \r\n => => sha256:2756ef5f69a5190f4308619e0f446d95f5515eef4a814dbad0bcebbbbc7b25a8 5.15MB / 5.15MB 6.4s \r\n => => sha256:27b0a22ee906271a6ce9ddd1754fdd7d3b59078e0b57b6cc054c7ed7ac301587 54.57MB / 54.57MB 37.7s \r\n => => sha256:8584d51a9262f9a3a436dea09ba40fa50f85802018f9bd299eee1bf538481077 196.45MB / 196.45MB 82.3s \r\n => => sha256:524774b7d3638702fe9ae0ea3fcfb81b027dfd75cc2fc14f0119e764b9543d58 6.29MB / 6.29MB 26.6s \r\n => => extracting sha256:955615a668ce169f8a1443fc6b6e6215f43fe0babfb4790712a2d3171f34d366 5.4s \r\n => => sha256:9460f6b75036e38367e2f27bb15e85777c5d6cd52ad168741c9566186415aa26 16.81MB / 16.81MB 40.5s \r\n => => extracting sha256:2756ef5f69a5190f4308619e0f446d95f5515eef4a814dbad0bcebbbbc7b25a8 0.6s \r\n => => extracting sha256:911ea9f2bd51e53a455297e0631e18a72a86d7e2c8e1807176e80f991bde5d64 0.6s \r\n => => sha256:9bc548096c181514aa1253966a330134d939496027f92f57ab376cd236eb280b 232B / 232B 40.1s \r\n => => extracting sha256:27b0a22ee906271a6ce9ddd1754fdd7d3b59078e0b57b6cc054c7ed7ac301587 5.8s \r\n => => sha256:1d87379b86b89fd3b8bb1621128f00c8f962756e6aaaed264ec38db733273543 2.35MB / 2.35MB 41.8s \r\n => => extracting sha256:8584d51a9262f9a3a436dea09ba40fa50f85802018f9bd299eee1bf538481077 18.8s \r\n => => extracting sha256:524774b7d3638702fe9ae0ea3fcfb81b027dfd75cc2fc14f0119e764b9543d58 1.2s \r\n => => extracting sha256:9460f6b75036e38367e2f27bb15e85777c5d6cd52ad168741c9566186415aa26 2.9s \r\n => => extracting sha256:9bc548096c181514aa1253966a330134d939496027f92f57ab376cd236eb280b 0.0s \r\n => => extracting sha256:1d87379b86b89fd3b8bb1621128f00c8f962756e6aaaed264ec38db733273543 0.8s \r\n => [2/5] COPY . /app 2.3s \r\n => [3/5] WORKDIR /app 0.2s \r\n => [4/5] RUN pip install -U datasette 26.9s \r\n => [5/5] RUN datasette inspect covid.db --inspect-file inspect-data.json 3.1s\r\n => exporting to image 1.2s \r\n => => exporting layers 1.2s \r\n => => writing image sha256:b5db0c205cd3454c21fbb00ecf6043f261540bcf91c2dfc36d418f1a23a75d7a 0.0s\r\n\r\nUse 'docker scan' to run Snyk tests against images to find vulnerabilities and learn how to fix them\r\nTraceback (most recent call last):\r\n \"__main__\", mod_spec)\r\n File \"c:\\users\\grott\\anaconda3\\lib\\runpy.py\", line 85, in _run_code\r\n exec(code, run_globals)\r\n File \"C:\\Users\\grott\\Anaconda3\\Scripts\\datasette.exe\\__main__.py\", line 7, in \r\n File \"c:\\users\\grott\\anaconda3\\lib\\site-packages\\click\\core.py\", line 829, in __call__\r\n return self.main(*args, **kwargs)\r\n File \"c:\\users\\grott\\anaconda3\\lib\\site-packages\\click\\core.py\", line 782, in main\r\n rv = self.invoke(ctx)\r\n File \"c:\\users\\grott\\anaconda3\\lib\\site-packages\\click\\core.py\", line 1259, in invoke\r\n return _process_result(sub_ctx.command.invoke(sub_ctx))\r\n File \"c:\\users\\grott\\anaconda3\\lib\\site-packages\\click\\core.py\", line 1066, in invoke\r\n return ctx.invoke(self.callback, **ctx.params)\r\n File \"c:\\users\\grott\\anaconda3\\lib\\site-packages\\click\\core.py\", line 610, in invoke\r\n return callback(*args, **kwargs)\r\n File \"c:\\users\\grott\\anaconda3\\lib\\site-packages\\datasette\\cli.py\", line 283, in package\r\n call(args)\r\n File \"c:\\users\\grott\\anaconda3\\lib\\contextlib.py\", line 119, in __exit__\r\n next(self.gen)\r\n File \"c:\\users\\grott\\anaconda3\\lib\\site-packages\\datasette\\utils\\__init__.py\", line 451, in temporary_docker_directory\r\n tmp.cleanup()\r\n File \"c:\\users\\grott\\anaconda3\\lib\\tempfile.py\", line 811, in cleanup\r\n _shutil.rmtree(self.name)\r\n File \"c:\\users\\grott\\anaconda3\\lib\\shutil.py\", line 516, in rmtree\r\n return _rmtree_unsafe(path, onerror)\r\n File \"c:\\users\\grott\\anaconda3\\lib\\shutil.py\", line 395, in _rmtree_unsafe\r\n _rmtree_unsafe(fullname, onerror)\r\n File \"c:\\users\\grott\\anaconda3\\lib\\shutil.py\", line 404, in _rmtree_unsafe\r\n onerror(os.rmdir, path, sys.exc_info())\r\n File \"c:\\users\\grott\\anaconda3\\lib\\shutil.py\", line 402, in _rmtree_unsafe\r\n os.rmdir(path)\r\nPermissionError: [WinError 32] The process cannot access the file because it is being used by another process: 'C:\\\\Users\\\\grott\\\\AppData\\\\Local\\\\Temp\\\\tmpkb27qid3\\\\datasette'```", "repo": {"value": 107914493, "label": "datasette"}, "type": "issue", "active_lock_reason": null, "performed_via_github_app": null, "reactions": "{\"url\": \"https://api.github.com/repos/simonw/datasette/issues/1479/reactions\", \"total_count\": 1, \"+1\": 1, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "draft": null, "state_reason": null} {"id": 787098146, "node_id": "MDU6SXNzdWU3ODcwOTgxNDY=", "number": 1190, "title": "`datasette publish upload` mechanism for uploading databases to an existing Datasette instance", "user": {"value": 1024355, "label": "tomershvueli"}, "state": "closed", "locked": 0, "assignee": null, "milestone": null, "comments": 3, "created_at": "2021-01-15T18:18:42Z", "updated_at": "2023-08-30T22:16:39Z", "closed_at": "2023-08-30T22:16:38Z", "author_association": "NONE", "pull_request": null, "body": "If I have a self-hosted instance of Datasette up and running, I'd like to be able to the use the CLI to publish databases to that instance, not only Google or Heroku. Ideally there'd be a `url` parameter or something similar to which one could point the publish command to their instance. ", "repo": {"value": 107914493, "label": "datasette"}, "type": "issue", "active_lock_reason": null, "performed_via_github_app": null, "reactions": "{\"url\": \"https://api.github.com/repos/simonw/datasette/issues/1190/reactions\", \"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "draft": null, "state_reason": "completed"} {"id": 814595021, "node_id": "MDU6SXNzdWU4MTQ1OTUwMjE=", "number": 1241, "title": "Share button for copying current URL", "user": {"value": 7107523, "label": "Kabouik"}, "state": "open", "locked": 0, "assignee": null, "milestone": null, "comments": 6, "created_at": "2021-02-23T15:55:40Z", "updated_at": "2023-08-24T20:09:52Z", "closed_at": null, "author_association": "NONE", "pull_request": null, "body": "I use datasette in an `iframe` inside another HTML file that contains other ways to represent my data (mostly leaflets maps built with R on summarized data), and the datasette `iframe` is a tab in that page. \r\n\r\nThis particular use prevents users to access the full URLs of their datasette views and queries, which is a shame because the way datasette handles URLs to make every view or query easy to share is awesome. I know how to get the URL from the context menu of my browser, but I don't think many visitors would do it or even notice that datasette uses permalinks for pretty much every action they do. Would it be possible to add a \"Share link\" button to the interface, either in datasette itself or in a plugin?", "repo": {"value": 107914493, "label": "datasette"}, "type": "issue", "active_lock_reason": null, "performed_via_github_app": null, "reactions": "{\"url\": \"https://api.github.com/repos/simonw/datasette/issues/1241/reactions\", \"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "draft": null, "state_reason": null} {"id": 1858228057, "node_id": "I_kwDOBm6k_c5uwk9Z", "number": 2147, "title": "Plugin hook for database queries that are run", "user": {"value": 18899, "label": "jackowayed"}, "state": "open", "locked": 0, "assignee": null, "milestone": null, "comments": 6, "created_at": "2023-08-20T18:43:50Z", "updated_at": "2023-08-24T03:54:35Z", "closed_at": null, "author_association": "NONE", "pull_request": null, "body": "I'm interested in making a plugin that saves every query that gets run to a table in the database. (I know about datasette-query-history but thought it would be good to have a server-side option.)\r\n\r\nAs far as I can tell reading the docs, there isn't really a hook setup to allow this.\r\n\r\nMaybe I could hack it with some of the hooks that are passed requests, but that doesn't seem good.\r\n\r\nI'm a little surprised this isn't possible, so I thought I would open an issue and see if that's a deeply considered decision or just \"haven't needed it yet.\" I'm potentially interested in implementing the hook if the latter.", "repo": {"value": 107914493, "label": "datasette"}, "type": "issue", "active_lock_reason": null, "performed_via_github_app": null, "reactions": "{\"url\": \"https://api.github.com/repos/simonw/datasette/issues/2147/reactions\", \"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "draft": null, "state_reason": null} {"id": 1838266862, "node_id": "I_kwDOBm6k_c5tkbnu", "number": 2126, "title": "Permissions in metadata.yml / metadata.json", "user": {"value": 36199671, "label": "ctsrc"}, "state": "closed", "locked": 0, "assignee": null, "milestone": null, "comments": 3, "created_at": "2023-08-06T16:24:10Z", "updated_at": "2023-08-11T05:52:30Z", "closed_at": "2023-08-11T05:52:29Z", "author_association": "NONE", "pull_request": null, "body": "https://docs.datasette.io/en/latest/authentication.html#other-permissions-in-metadata says the following:\r\n\r\n> For all other permissions, you can use one or more \"permissions\" blocks in your metadata.\r\n\r\n> To grant access to the permissions debug tool to all signed in users you can grant permissions-debug to any actor with an id matching the wildcard * by adding this a the root of your metadata:\r\n\r\n```yaml\r\npermissions:\r\n debug-menu:\r\n id: '*'\r\n```\r\n\r\nI tried this.\r\n\r\nMy `metadata.yml` file looks like:\r\n\r\n```yaml\r\npermissions:\r\n debug-menu:\r\n id: '*'\r\n permissions-debug:\r\n id: '*'\r\nplugins:\r\n datasette-auth-passwords:\r\n myuser_password_hash:\r\n $env: \"PASSWORD_HASH_MYUSER\"\r\n```\r\n\r\nAnd then I run\r\n\r\n```zsh\r\ndatasette -m metadata.yml tiddlywiki.db --root\r\n```\r\n\r\nAnd I open a session for the \"root\" user of datasette with the link given.\r\n\r\nI open a private browser session and log in as \"myuser\" from http://127.0.0.1:8001/-/login\r\n\r\nThen I check http://127.0.0.1:8001/-/actor which confirms that I am logged in as the \"myuser\" actor\r\n\r\n```json\r\n{\r\n \"actor\": {\r\n \"id\": \"myuser\"\r\n }\r\n}\r\n```\r\n\r\nIn the session where I am logged in as \"myuser\" I then try to go to http://127.0.0.1:8001/-/permissions\r\n\r\nBut all I get there as the logged in user \"myuser\" is\r\n\r\n> Forbidden\r\n>\r\n> Permission denied\r\n\r\nAnd then if I check the http://127.0.0.1:8001/-/permissions as the datasette \"root\" user from another browser session, I see:\r\n\r\n> permissions-debug checked at 2023-08-06T16:22:58.997841 \u2717 (used default)\r\n>\r\n> Actor: {\"id\": \"myuser\"}\r\n\r\nIt seems that in spite of having tried to give the `permissions-debug` permission to the \"myuser\" user in my `metadata.yml` file, datasette does not agree that \"myuser\" has permission `permissions-debug`..\r\n\r\nWhat do I need to do differently so that my \"myuser\" user is able to access http://127.0.0.1:8001/-/permissions ?", "repo": {"value": 107914493, "label": "datasette"}, "type": "issue", "active_lock_reason": null, "performed_via_github_app": null, "reactions": "{\"url\": \"https://api.github.com/repos/simonw/datasette/issues/2126/reactions\", \"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "draft": null, "state_reason": "completed"} {"id": 476852861, "node_id": "MDU6SXNzdWU0NzY4NTI4NjE=", "number": 568, "title": "Add database_color as a configurable option", "user": {"value": 50906992, "label": "LBHELewis"}, "state": "open", "locked": 0, "assignee": null, "milestone": null, "comments": 1, "created_at": "2019-08-05T13:14:45Z", "updated_at": "2023-08-11T05:19:42Z", "closed_at": null, "author_association": "NONE", "pull_request": null, "body": "This would be really useful as it would allow us to tie in with colour schemes.", "repo": {"value": 107914493, "label": "datasette"}, "type": "issue", "active_lock_reason": null, "performed_via_github_app": null, "reactions": "{\"url\": \"https://api.github.com/repos/simonw/datasette/issues/568/reactions\", \"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "draft": null, "state_reason": null} {"id": 1841501975, "node_id": "I_kwDOBm6k_c5twxcX", "number": 2133, "title": "[feature request]`datasette install plugins.json` options", "user": {"value": 54462, "label": "HaveF"}, "state": "closed", "locked": 0, "assignee": null, "milestone": null, "comments": 9, "created_at": "2023-08-08T15:06:50Z", "updated_at": "2023-08-10T00:31:24Z", "closed_at": "2023-08-09T22:04:46Z", "author_association": "NONE", "pull_request": null, "body": "Hi, simon \u2764\ufe0f \r\n\r\n`datasette plugins --all > plugins.json` could generate all plugins info. On another machine, it would be great to install all plugins just by `datasette install plugins.json`", "repo": {"value": 107914493, "label": "datasette"}, "type": "issue", "active_lock_reason": null, "performed_via_github_app": null, "reactions": "{\"url\": \"https://api.github.com/repos/simonw/datasette/issues/2133/reactions\", \"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "draft": null, "state_reason": "completed"} {"id": 1824457306, "node_id": "I_kwDOBm6k_c5svwJa", "number": 2122, "title": "Parameters on canned queries: fixed or query-generated list?", "user": {"value": 1563881, "label": "meowcat"}, "state": "open", "locked": 0, "assignee": null, "milestone": null, "comments": 0, "created_at": "2023-07-27T14:07:07Z", "updated_at": "2023-07-27T14:07:07Z", "closed_at": null, "author_association": "NONE", "pull_request": null, "body": "Hi,\r\n\r\ncurrently parameters in canned queries are just text fields. It would be cool to have one of the options below. Would you accept a PR doing something in this direction? (Possibly this could even work as a plugin.)\r\n\r\n* adding facets, which would work like facets on tables or views, giving a list of selectable options (and leaving parameters as is)\r\n* making it possible to provide a query which returns selectable values for a parameter, e.g.\r\n``` \r\ncalendar_entries_current_instrument:\r\n sql: | \r\n select * from calendar_entries \r\n where \r\n DTEND_UNIX > UNIXEPOCH() and\r\n DTSTART_UNIX < UNIXEPOCH() + :days *24*60*60 and\r\n current = 1 and\r\n MACHINE = :instrument\r\n order by\r\n DTSTART_UNIX\r\n params:\r\n days: \r\n sql: \"SELECT VALUE FROM generate_series(1, 30, 1)\"\r\n # this obviously requires the corresponding sqlite extension\r\n instrument:\r\n sql: \"SELECT DISTINCT MACHINE FROM calendar_entries\"\r\n```\r\n* making it possible to provide a fixed list of parameters\r\n``` \r\ncalendar_entries_current_instrument:\r\n sql: | \r\n select * from calendar_entries \r\n where \r\n DTEND_UNIX > UNIXEPOCH() and\r\n DTSTART_UNIX < UNIXEPOCH() + :days *24*60*60 and\r\n current = 1 and\r\n MACHINE = :instrument\r\n order by\r\n DTSTART_UNIX\r\n params:\r\n days: \r\n values: [1, 2, 3, 5, 10, 20, 30]\r\n instrument:\r\n values: [supermachine, crappymachine, boringmachine]\r\n```", "repo": {"value": 107914493, "label": "datasette"}, "type": "issue", "active_lock_reason": null, "performed_via_github_app": null, "reactions": "{\"url\": \"https://api.github.com/repos/simonw/datasette/issues/2122/reactions\", \"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "draft": null, "state_reason": null} {"id": 1811824307, "node_id": "I_kwDOBm6k_c5r_j6z", "number": 2105, "title": "When reverse proxying datasette with nginx an URL element gets erronously added", "user": {"value": 2235371, "label": "aki-k"}, "state": "open", "locked": 0, "assignee": null, "milestone": null, "comments": 3, "created_at": "2023-07-19T12:16:53Z", "updated_at": "2023-07-21T21:17:09Z", "closed_at": null, "author_association": "NONE", "pull_request": null, "body": "I use this nginx config:\r\n```\r\n location /datasette-llm {\r\n return 302 /datasette-llm/;\r\n }\r\n\r\n location /datasette-llm/ {\r\n proxy_set_header Upgrade $http_upgrade;\r\n proxy_set_header Connection \"Upgrade\";\r\n proxy_http_version 1.1;\r\n proxy_set_header X-Real-IP $remote_addr;\r\n proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;\r\n proxy_set_header X-Forwarded-Proto https;\r\n proxy_set_header X-Forwarded-Host $http_host;\r\n proxy_set_header Host $host;\r\n proxy_max_temp_file_size 0;\r\n proxy_pass http://127.0.0.1:8001/datasette-llm/;\r\n proxy_redirect http:// https://;\r\n proxy_buffering off;\r\n proxy_request_buffering off;\r\n proxy_set_header Origin '';\r\n client_max_body_size 0;\r\n auth_basic \"datasette-llm\";\r\n auth_basic_user_file /etc/nginx/custom-userdb;\r\n }\r\n```\r\nThen I start datasette with this command:\r\n```\r\ndatasette serve --setting base_url /datasette-llm/ $(llm logs path)\r\n```\r\nEverything else works right, except the links in \"This data as json, CSV\".\r\nThey get an extra URL element \"datasette-llm\" like this:\r\n\r\nhttps://192.168.1.3:5432/datasette-llm/datasette-llm/logs.json?sql=select+*+from+_llm_migrations\r\n\r\nhttps://192.168.1.3:5432/datasette-llm/datasette-llm/logs.csv?sql=select+*+from+_llm_migrations&_size=max\r\n\r\nWhen I remove that extra \"datasette-llm\" from the URL, those links work too.", "repo": {"value": 107914493, "label": "datasette"}, "type": "issue", "active_lock_reason": null, "performed_via_github_app": null, "reactions": "{\"url\": \"https://api.github.com/repos/simonw/datasette/issues/2105/reactions\", \"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "draft": null, "state_reason": null} {"id": 1794097871, "node_id": "I_kwDOBm6k_c5q78LP", "number": 2095, "title": "Introduce \"dark mode\" CSS", "user": {"value": 3315059, "label": "jamietanna"}, "state": "open", "locked": 0, "assignee": null, "milestone": null, "comments": 0, "created_at": "2023-07-07T19:15:58Z", "updated_at": "2023-07-07T19:15:58Z", "closed_at": null, "author_association": "NONE", "pull_request": null, "body": "Using [the CSS media query `prefers-color-scheme`](https://developer.mozilla.org/en-US/docs/Web/CSS/@media/prefers-color-scheme) we can provide a dark-mode version of Datasette", "repo": {"value": 107914493, "label": "datasette"}, "type": "issue", "active_lock_reason": null, "performed_via_github_app": null, "reactions": "{\"url\": \"https://api.github.com/repos/simonw/datasette/issues/2095/reactions\", \"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "draft": null, "state_reason": null} {"id": 1762180409, "node_id": "I_kwDOBm6k_c5pCL05", "number": 2085, "title": "Interactive row selection in Datasette ", "user": {"value": 24938923, "label": "learning4life"}, "state": "open", "locked": 0, "assignee": null, "milestone": null, "comments": 0, "created_at": "2023-06-18T08:29:45Z", "updated_at": "2023-06-18T08:31:23Z", "closed_at": null, "author_association": "NONE", "pull_request": null, "body": "Simon did a excellent [prototype](https://til.simonwillison.net/datasette/row-selection-prototype) of an interactive row selection in Datasette.\r\n\r\nI hope this [functionality](https://camo.githubusercontent.com/3d4a0f31fb6a27fd279f809af5b53dc3b76faa63c7721e228951c5252b645a77/68747470733a2f2f7374617469632e73696d6f6e77696c6c69736f6e2e6e65742f7374617469632f323032332f6461746173657474652d7069636b65722e676966) can be turned into a Datasette plugin.\r\n", "repo": {"value": 107914493, "label": "datasette"}, "type": "issue", "active_lock_reason": null, "performed_via_github_app": null, "reactions": "{\"url\": \"https://api.github.com/repos/simonw/datasette/issues/2085/reactions\", \"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "draft": null, "state_reason": null} {"id": 1761613778, "node_id": "I_kwDOBm6k_c5pABfS", "number": 2084, "title": "Support facets for columns that contain timestamps", "user": {"value": 19492893, "label": "devxpy"}, "state": "open", "locked": 0, "assignee": null, "milestone": null, "comments": 0, "created_at": "2023-06-17T03:33:54Z", "updated_at": "2023-06-17T03:33:54Z", "closed_at": null, "author_association": "NONE", "pull_request": null, "body": "\r\nDjango has this very nice filter for datetime fields -\r\n\r\n\"image\"\r\n\r\nIt would be nice to have something similar to facet by a field that contains a timestamp in datasette too - Which doesn't seem to do anything with timestamps right now...\r\n\r\n\"image\"\r\n", "repo": {"value": 107914493, "label": "datasette"}, "type": "issue", "active_lock_reason": null, "performed_via_github_app": null, "reactions": "{\"url\": \"https://api.github.com/repos/simonw/datasette/issues/2084/reactions\", \"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "draft": null, "state_reason": null} {"id": 1727478903, "node_id": "I_kwDOBm6k_c5m9zx3", "number": 2081, "title": "Update Endpoints defined in metadata throws 403 Forbidden after a while", "user": {"value": 15085007, "label": "cutmasta-kun"}, "state": "open", "locked": 0, "assignee": null, "milestone": null, "comments": 0, "created_at": "2023-05-26T11:52:30Z", "updated_at": "2023-05-26T11:52:30Z", "closed_at": null, "author_association": "NONE", "pull_request": null, "body": "Hello. I expose an endpoint to update `tasks`:\r\n```\r\n{\r\n \"title\": \"My Datasette Instance\",\r\n \"databases\": {\r\n \"tasks\": {\r\n \"queries\": {\r\n \"update_task\": {\r\n \"sql\": \"UPDATE tasks SET status = :status, result = :result, systemMessage = :systemMessage WHERE queueID = :queueID\",\r\n \"write\": true,\r\n \"on_success_message\": \"Task updated\",\r\n \"on_success_redirect\": \"/tasks/tasks.json\",\r\n \"on_error_message\": \"Task update failed\",\r\n \"on_error_redirect\": \"/tasks.json\",\r\n \"params\": [\"queueID\", \"taskData\", \"status\", \"result\", \"systemMessage\"]\r\n }\r\n }\r\n }\r\n }\r\n}\r\n```\r\n\r\nThis works really well! But after a while, the Datasette Instanz answers with **403 Forbidden**.\r\nI have to delete the database and recreate it in order to work again.\r\n\r\nAny help here? (\u00b4\u3002\uff3f\u3002\uff40)", "repo": {"value": 107914493, "label": "datasette"}, "type": "issue", "active_lock_reason": null, "performed_via_github_app": null, "reactions": "{\"url\": \"https://api.github.com/repos/simonw/datasette/issues/2081/reactions\", \"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "draft": null, "state_reason": null} {"id": 1698865182, "node_id": "I_kwDOBm6k_c5lQqAe", "number": 2069, "title": "[BUG] Cannot insert new data to deployed instance", "user": {"value": 31861128, "label": "yqlbu"}, "state": "open", "locked": 0, "assignee": null, "milestone": null, "comments": 1, "created_at": "2023-05-07T02:59:42Z", "updated_at": "2023-05-07T03:17:35Z", "closed_at": null, "author_association": "NONE", "pull_request": null, "body": "## Summary\r\n\r\nRecently, I deployed an instance of datasette to Vercel with the following plugins:\r\n\r\n- datasette-auth-tokens\r\n- datasette-insert\r\n\r\nWith the above plugins, I was able to insert new data to local sqlite db. However, when it comes to the deployment on Vercel, things behave differently. I observed some errors from the logs console on Vercel:\r\n\r\n```console\r\nFile \"/var/task/datasette/database.py\", line 179, in _execute_writes\r\nconn = self.connect(write=True)\r\nFile \"/var/task/datasette/database.py\", line 93, in connect\r\nassert not (write and not self.is_mutable)\r\nAssertionError\r\n``` \r\n\r\n\"image\"\r\n\r\nI think it is a potential bug.\r\n\r\n## Reproduce\r\n\r\n
metadata.json\r\n
\r\n\r\n```json\r\n{\r\n \"plugins\": {\r\n \"datasette-insert\": {\r\n \"allow\": {\r\n \"id\": \"*\"\r\n }\r\n },\r\n \"datasette-auth-tokens\": {\r\n \"tokens\": [\r\n {\r\n \"token\": {\r\n \"$env\": \"INSERT_TOKEN\"\r\n },\r\n \"actor\": {\r\n \"id\": \"repeater\"\r\n }\r\n }\r\n ],\r\n \"param\": \"_auth_token\"\r\n }\r\n }\r\n}\r\n```\r\n\r\n
\r\n\r\n
commands\r\n
\r\n\r\n```bash\r\n# deploy\r\ndatasette publish vercel remote.db \\\r\n --project=repeater-bot-sqlite \\\r\n --metadata metadata.json \\\r\n --install datasette-auth-tokens \\\r\n --install datasette-insert \\\r\n --vercel-json=vercel.json\r\n\r\n# test insert\r\ncat fixtures/dogs.json | curl --request POST -d @- -H \"Authorization: Bearer \" \\\r\n 'https://repeater-bot-sqlite.vercel.app/-/insert/remote/dogs?pk=id'\r\n```\r\n\r\n
\r\n\r\n
logs\r\n
\r\n\r\n```console\r\nTraceback (most recent call last):\r\nFile \"/var/task/datasette/app.py\", line 1354, in route_path\r\nresponse = await view(request, send)\r\nFile \"/var/task/datasette/app.py\", line 1500, in async_view_fn\r\nresponse = await async_call_with_supported_arguments(\r\nFile \"/var/task/datasette/utils/__init__.py\", line 1005, in async_call_with_supported_arguments\r\nreturn await fn(*call_with)\r\nFile \"/var/task/datasette_insert/__init__.py\", line 14, in insert_or_upsert\r\nresponse = await insert_or_upsert_implementation(request, datasette)\r\nFile \"/var/task/datasette_insert/__init__.py\", line 91, in insert_or_upsert_implementation\r\ntable_count = await db.execute_write_fn(write_in_thread, block=True)\r\nFile \"/var/task/datasette/database.py\", line 167, in execute_write_fn\r\nraise result\r\nFile \"/var/task/datasette/database.py\", line 179, in _execute_writes\r\nconn = self.connect(write=True)\r\nFile \"/var/task/datasette/database.py\", line 93, in connect\r\nassert not (write and not self.is_mutable)\r\nAssertionError\r\n```\r\n\r\n
", "repo": {"value": 107914493, "label": "datasette"}, "type": "issue", "active_lock_reason": null, "performed_via_github_app": null, "reactions": "{\"url\": \"https://api.github.com/repos/simonw/datasette/issues/2069/reactions\", \"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "draft": null, "state_reason": null} {"id": 1690765434, "node_id": "I_kwDOBm6k_c5kxwh6", "number": 2067, "title": "Litestream-restored db: errors on 3.11 and 3.10.8; but works on py3.10.7 and 3.10.6", "user": {"value": 39538958, "label": "justmars"}, "state": "open", "locked": 0, "assignee": null, "milestone": null, "comments": 1, "created_at": "2023-05-01T12:42:28Z", "updated_at": "2023-05-03T00:16:03Z", "closed_at": null, "author_association": "NONE", "pull_request": null, "body": "Hi! Wondering if this issue is limited to my local system or if it affects others as well. \r\n\r\nIt seems like 3.11 errors out on a \"litestream-restored\" database. On further investigation, it also appears to conk out on 3.10.8 but works on 3.10.7 and 3.10.6.\r\n\r\nTo demo issue I created a test database, replicated it to an aws s3 bucket, then restored the same under various .pyenv-versioned shells where I test whether I can read the database via the sqlite3 cli.\r\n\r\n```sh\r\n# create new shell with 3.11.3\r\nlitestream restore -o data/db.sqlite s3://mytestbucketxx/db\r\nsqlite3 data/db.sqlite \r\n# SQLite version 3.41.2 2023-03-22 11:56:21\r\n# Enter \".help\" for usage hints.\r\n# sqlite> .tables\r\n# _litestream_lock _litestream_seq movie \r\n# sqlite> \r\n```\r\n\r\nHowever this get me an `OperationalError` when reading via datasette:\r\n\r\n
\r\nError on 3.11.3 and 3.10.8\r\n\r\n```sh\r\ndatasette data/db.sqlite\r\n```\r\n\r\n```console\r\n/tester/.venv/lib/python3.11/site-packages/pkg_resources/__init__.py:121: DeprecationWarning: pkg_resources is deprecated as an API\r\n warnings.warn(\"pkg_resources is deprecated as an API\", DeprecationWarning)\r\nTraceback (most recent call last):\r\n File \"/tester/.venv/bin/datasette\", line 8, in \r\n sys.exit(cli())\r\n ^^^^^\r\n File \"/tester/.venv/lib/python3.11/site-packages/click/core.py\", line 1130, in __call__\r\n return self.main(*args, **kwargs)\r\n ^^^^^^^^^^^^^^^^^^^^^^^^^^\r\n File \"/tester/.venv/lib/python3.11/site-packages/click/core.py\", line 1055, in main\r\n rv = self.invoke(ctx)\r\n ^^^^^^^^^^^^^^^^\r\n File \"/tester/.venv/lib/python3.11/site-packages/click/core.py\", line 1657, in invoke\r\n return _process_result(sub_ctx.command.invoke(sub_ctx))\r\n ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\r\n File \"/tester/.venv/lib/python3.11/site-packages/click/core.py\", line 1404, in invoke\r\n return ctx.invoke(self.callback, **ctx.params)\r\n ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\r\n File \"/tester/.venv/lib/python3.11/site-packages/click/core.py\", line 760, in invoke\r\n return __callback(*args, **kwargs)\r\n ^^^^^^^^^^^^^^^^^^^^^^^^^^^\r\n File \"/tester/.venv/lib/python3.11/site-packages/datasette/cli.py\", line 143, in wrapped\r\n return fn(*args, **kwargs)\r\n ^^^^^^^^^^^^^^^^^^^\r\n File \"/tester/.venv/lib/python3.11/site-packages/datasette/cli.py\", line 615, in serve\r\n asyncio.get_event_loop().run_until_complete(check_databases(ds))\r\n File \"/Users/mv/.pyenv/versions/3.11.3/lib/python3.11/asyncio/base_events.py\", line 653, in run_until_complete\r\n return future.result()\r\n ^^^^^^^^^^^^^^^\r\n File \"/tester/.venv/lib/python3.11/site-packages/datasette/cli.py\", line 660, in check_databases\r\n await database.execute_fn(check_connection)\r\n File \"/tester/.venv/lib/python3.11/site-packages/datasette/database.py\", line 213, in execute_fn\r\n return await asyncio.get_event_loop().run_in_executor(\r\n ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\r\n File \"/Users/mv/.pyenv/versions/3.11.3/lib/python3.11/concurrent/futures/thread.py\", line 58, in run\r\n result = self.fn(*self.args, **self.kwargs)\r\n ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\r\n File \"/tester/.venv/lib/python3.11/site-packages/datasette/database.py\", line 211, in in_thread\r\n return fn(conn)\r\n ^^^^^^^^\r\n File \"/tester/.venv/lib/python3.11/site-packages/datasette/utils/__init__.py\", line 951, in check_connection\r\n for r in conn.execute(\r\n ^^^^^^^^^^^^^\r\nsqlite3.OperationalError: unable to open database file\r\n```\r\n\r\n
\r\n\r\n\r\n
\r\nWorks on 3.10.7, 3.10.6\r\n\r\n```sh\r\n# create new shell with 3.10.7 / 3.10.6\r\nlitestream restore -o data/db.sqlite s3://mytestbucketxx/db\r\ndatasette data/db.sqlite\r\n# ...\r\n# INFO: Uvicorn running on http://127.0.0.1:8001 (Press CTRL+C to quit)\r\n```\r\n\r\n
\r\n\r\nIn both scenarios, the only dependencies were the pinned python version and the latest Datasette version 0.64.", "repo": {"value": 107914493, "label": "datasette"}, "type": "issue", "active_lock_reason": null, "performed_via_github_app": null, "reactions": "{\"url\": \"https://api.github.com/repos/simonw/datasette/issues/2067/reactions\", \"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "draft": null, "state_reason": null} {"id": 1665053646, "node_id": "I_kwDOBm6k_c5jPrPO", "number": 2059, "title": "\"Deceptive site ahead\" alert on Heroku deployment", "user": {"value": 1186275, "label": "mtdukes"}, "state": "open", "locked": 0, "assignee": null, "milestone": null, "comments": 1, "created_at": "2023-04-12T18:34:51Z", "updated_at": "2023-04-13T01:13:01Z", "closed_at": null, "author_association": "NONE", "pull_request": null, "body": "I deployed a fairly basic instance of Datasette (`datasette-auth-passwords` is the only plugin) using Heroku. The deployed URL now gives a \"Deceptive site ahead\" warning to users.\r\n\r\nIs there way around this? Maybe a way to add ownership verification [through Google's search console](https://search.google.com/search-console/welcome)? ", "repo": {"value": 107914493, "label": "datasette"}, "type": "issue", "active_lock_reason": null, "performed_via_github_app": null, "reactions": "{\"url\": \"https://api.github.com/repos/simonw/datasette/issues/2059/reactions\", \"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "draft": null, "state_reason": null} {"id": 1531991339, "node_id": "I_kwDOBm6k_c5bUFUr", "number": 1989, "title": "Suggestion: Hiding columns", "user": {"value": 116795, "label": "pax"}, "state": "open", "locked": 0, "assignee": null, "milestone": null, "comments": 3, "created_at": "2023-01-13T09:33:32Z", "updated_at": "2023-03-31T06:18:05Z", "closed_at": null, "author_association": "NONE", "pull_request": null, "body": "As there's the possibility of [hiding tables](https://docs.datasette.io/en/stable/metadata.html#hiding-tables) - I've run into the **need of hiding specific columns** - data that's either not relevant for public or can't be shown due to privacy reasons. ", "repo": {"value": 107914493, "label": "datasette"}, "type": "issue", "active_lock_reason": null, "performed_via_github_app": null, "reactions": "{\"url\": \"https://api.github.com/repos/simonw/datasette/issues/1989/reactions\", \"total_count\": 1, \"+1\": 1, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "draft": null, "state_reason": null} {"id": 1646068413, "node_id": "I_kwDOBm6k_c5iHQK9", "number": 2048, "title": "Test failures encountered while packaging for GNU Guix", "user": {"value": 8332263, "label": "Apteryks"}, "state": "open", "locked": 0, "assignee": null, "milestone": null, "comments": 0, "created_at": "2023-03-29T15:36:54Z", "updated_at": "2023-03-29T15:36:54Z", "closed_at": null, "author_association": "NONE", "pull_request": null, "body": "Hello,\r\n\r\nWhile reviewing a packaged submitted to Guix to add `datasette`, the test suite produces the following errors:\r\n```\r\n=================================== FAILURES ===================================\r\n_________________________ test_row_strange_table_name __________________________\r\n[gw21] linux -- Python 3.9.9 /gnu/store/slsh0qjv5j68xda2bb6h8gsxwyi1j25a-python-wrapper-3.9.9/bin/python\r\n\r\napp_client = \r\n\r\n def test_row_strange_table_name(app_client):\r\n response = app_client.get(\r\n \"/fixtures/table~2Fwith~2Fslashes~2Ecsv/3.json?_shape=objects\"\r\n )\r\n> assert response.status == 200\r\nE assert 400 == 200\r\nE + where 400 = .status\r\n\r\n/tmp/guix-build-datasette-0.64.2.drv-0/source/tests/test_api.py:701: AssertionError\r\n----------------------------- Captured stderr call -----------------------------\r\nERROR: conn=, sql = 'select rowid, * from [table%7E2Fwith%7E2Fslashes%7E2Ecsv] where \"rowid\"=:p0', params = {'p0': '3'}: no such table: table%7E2Fwith%7E2Fslashes%7E2Ecsv\r\n_______________ test_database_page_for_database_with_dot_in_name _______________\r\n[gw15] linux -- Python 3.9.9 /gnu/store/slsh0qjv5j68xda2bb6h8gsxwyi1j25a-python-wrapper-3.9.9/bin/python\r\n\r\napp_client_with_dot = \r\n\r\n def test_database_page_for_database_with_dot_in_name(app_client_with_dot):\r\n response = app_client_with_dot.get(\"/fixtures~2Edot.json\")\r\n> assert response.status == 200\r\nE assert 302 == 200\r\nE + where 302 = .status\r\n\r\n/tmp/guix-build-datasette-0.64.2.drv-0/source/tests/test_api.py:633: AssertionError\r\n___________________ test_tilde_encoded_database_names[fo%o] ____________________\r\n[gw6] linux -- Python 3.9.9 /gnu/store/slsh0qjv5j68xda2bb6h8gsxwyi1j25a-python-wrapper-3.9.9/bin/python\r\n\r\ndb_name = 'fo%o'\r\n\r\n @pytest.mark.asyncio\r\n @pytest.mark.parametrize(\"db_name\", (\"foo\", r\"fo%o\", \"f~/c.d\"))\r\n async def test_tilde_encoded_database_names(db_name):\r\n ds = Datasette()\r\n ds.add_memory_database(db_name)\r\n response = await ds.client.get(\"/.json\")\r\n assert db_name in response.json().keys()\r\n path = response.json()[db_name][\"path\"]\r\n # And the JSON for that database\r\n response2 = await ds.client.get(path + \".json\")\r\n> assert response2.status_code == 200\r\nE assert 302 == 200\r\nE + where 302 = .status_code\r\n\r\n/tmp/guix-build-datasette-0.64.2.drv-0/source/tests/test_api.py:983: AssertionError\r\n__________________ test_tilde_encoded_database_names[f~/c.d] ___________________\r\n[gw7] linux -- Python 3.9.9 /gnu/store/slsh0qjv5j68xda2bb6h8gsxwyi1j25a-python-wrapper-3.9.9/bin/python\r\n\r\ndb_name = 'f~/c.d'\r\n\r\n @pytest.mark.asyncio\r\n @pytest.mark.parametrize(\"db_name\", (\"foo\", r\"fo%o\", \"f~/c.d\"))\r\n async def test_tilde_encoded_database_names(db_name):\r\n ds = Datasette()\r\n ds.add_memory_database(db_name)\r\n response = await ds.client.get(\"/.json\")\r\n assert db_name in response.json().keys()\r\n path = response.json()[db_name][\"path\"]\r\n # And the JSON for that database\r\n response2 = await ds.client.get(path + \".json\")\r\n> assert response2.status_code == 200\r\nE assert 302 == 200\r\nE + where 302 = .status_code\r\n\r\n/tmp/guix-build-datasette-0.64.2.drv-0/source/tests/test_api.py:983: AssertionError\r\n______________ test_database_with_space_in_name[/searchable.json] ______________\r\n[gw21] linux -- Python 3.9.9 /gnu/store/slsh0qjv5j68xda2bb6h8gsxwyi1j25a-python-wrapper-3.9.9/bin/python\r\n\r\napp_client_two_attached_databases = \r\npath = '/searchable.json'\r\n\r\n @pytest.mark.parametrize(\r\n \"path\",\r\n (\r\n \"/\",\r\n \".json\",\r\n \"/searchable\",\r\n \"/searchable.json\",\r\n \"/searchable_view\",\r\n \"/searchable_view.json\",\r\n ),\r\n )\r\n def test_database_with_space_in_name(app_client_two_attached_databases, path):\r\n> response = app_client_two_attached_databases.get(\r\n \"/extra~20database\" + path, follow_redirects=True\r\n )\r\n\r\n/tmp/guix-build-datasette-0.64.2.drv-0/source/tests/test_api.py:920: \r\n_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ \r\n/gnu/store/mcclmphjgbrgpa0v037a4nlq336482g8-python-asgiref-3.4.1/lib/python3.9/site-packages/asgiref/sync.py:223: in __call__\r\n return call_result.result()\r\n/gnu/store/65i3nhcwmz0p8rqbg48gaavyky4g4hwk-python-3.9.9/lib/python3.9/concurrent/futures/_base.py:438: in result\r\n return self.__get_result()\r\n/gnu/store/65i3nhcwmz0p8rqbg48gaavyky4g4hwk-python-3.9.9/lib/python3.9/concurrent/futures/_base.py:390: in __get_result\r\n raise self._exception\r\n/gnu/store/mcclmphjgbrgpa0v037a4nlq336482g8-python-asgiref-3.4.1/lib/python3.9/site-packages/asgiref/sync.py:292: in main_wrap\r\n result = await self.awaitable(*args, **kwargs)\r\n/tmp/guix-build-datasette-0.64.2.drv-0/source/datasette/utils/testing.py:66: in get\r\n return await self._request(\r\n/tmp/guix-build-datasette-0.64.2.drv-0/source/datasette/utils/testing.py:156: in _request\r\n httpx_response = await self.ds.client.request(\r\n/tmp/guix-build-datasette-0.64.2.drv-0/source/datasette/app.py:1602: in request\r\n return await client.request(\r\n/gnu/store/bj5lb299rfb4cbbq5kczq9imdk9a7y64-python-httpx-0.23.0/lib/python3.9/site-packages/httpx/_client.py:1527: in request\r\n return await self.send(request, auth=auth, follow_redirects=follow_redirects)\r\n/gnu/store/bj5lb299rfb4cbbq5kczq9imdk9a7y64-python-httpx-0.23.0/lib/python3.9/site-packages/httpx/_client.py:1614: in send\r\n response = await self._send_handling_auth(\r\n/gnu/store/bj5lb299rfb4cbbq5kczq9imdk9a7y64-python-httpx-0.23.0/lib/python3.9/site-packages/httpx/_client.py:1642: in _send_handling_auth\r\n response = await self._send_handling_redirects(\r\n_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ \r\n\r\nself = \r\nrequest = \r\nfollow_redirects = True\r\nhistory = [, , , , , , ...]\r\n\r\n async def _send_handling_redirects(\r\n self,\r\n request: Request,\r\n follow_redirects: bool,\r\n history: typing.List[Response],\r\n ) -> Response:\r\n while True:\r\n if len(history) > self.max_redirects:\r\n> raise TooManyRedirects(\r\n \"Exceeded maximum allowed redirects.\", request=request\r\n )\r\nE httpx.TooManyRedirects: Exceeded maximum allowed redirects.\r\n\r\n/gnu/store/bj5lb299rfb4cbbq5kczq9imdk9a7y64-python-httpx-0.23.0/lib/python3.9/site-packages/httpx/_client.py:1672: TooManyRedirects\r\n___________________ test_database_with_space_in_name[.json] ____________________\r\n[gw19] linux -- Python 3.9.9 /gnu/store/slsh0qjv5j68xda2bb6h8gsxwyi1j25a-python-wrapper-3.9.9/bin/python\r\n\r\napp_client_two_attached_databases = \r\npath = '.json'\r\n\r\n @pytest.mark.parametrize(\r\n \"path\",\r\n (\r\n \"/\",\r\n \".json\",\r\n \"/searchable\",\r\n \"/searchable.json\",\r\n \"/searchable_view\",\r\n \"/searchable_view.json\",\r\n ),\r\n )\r\n def test_database_with_space_in_name(app_client_two_attached_databases, path):\r\n> response = app_client_two_attached_databases.get(\r\n \"/extra~20database\" + path, follow_redirects=True\r\n )\r\n\r\n/tmp/guix-build-datasette-0.64.2.drv-0/source/tests/test_api.py:920: \r\n_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ \r\n/gnu/store/mcclmphjgbrgpa0v037a4nlq336482g8-python-asgiref-3.4.1/lib/python3.9/site-packages/asgiref/sync.py:223: in __call__\r\n return call_result.result()\r\n/gnu/store/65i3nhcwmz0p8rqbg48gaavyky4g4hwk-python-3.9.9/lib/python3.9/concurrent/futures/_base.py:438: in result\r\n return self.__get_result()\r\n/gnu/store/65i3nhcwmz0p8rqbg48gaavyky4g4hwk-python-3.9.9/lib/python3.9/concurrent/futures/_base.py:390: in __get_result\r\n raise self._exception\r\n/gnu/store/mcclmphjgbrgpa0v037a4nlq336482g8-python-asgiref-3.4.1/lib/python3.9/site-packages/asgiref/sync.py:292: in main_wrap\r\n result = await self.awaitable(*args, **kwargs)\r\n/tmp/guix-build-datasette-0.64.2.drv-0/source/datasette/utils/testing.py:66: in get\r\n return await self._request(\r\n/tmp/guix-build-datasette-0.64.2.drv-0/source/datasette/utils/testing.py:156: in _request\r\n httpx_response = await self.ds.client.request(\r\n/tmp/guix-build-datasette-0.64.2.drv-0/source/datasette/app.py:1602: in request\r\n return await client.request(\r\n/gnu/store/bj5lb299rfb4cbbq5kczq9imdk9a7y64-python-httpx-0.23.0/lib/python3.9/site-packages/httpx/_client.py:1527: in request\r\n return await self.send(request, auth=auth, follow_redirects=follow_redirects)\r\n/gnu/store/bj5lb299rfb4cbbq5kczq9imdk9a7y64-python-httpx-0.23.0/lib/python3.9/site-packages/httpx/_client.py:1614: in send\r\n response = await self._send_handling_auth(\r\n/gnu/store/bj5lb299rfb4cbbq5kczq9imdk9a7y64-python-httpx-0.23.0/lib/python3.9/site-packages/httpx/_client.py:1642: in _send_handling_auth\r\n response = await self._send_handling_redirects(\r\n_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ \r\n\r\nself = \r\nrequest = \r\nfollow_redirects = True\r\nhistory = [, , , , , , ...]\r\n\r\n async def _send_handling_redirects(\r\n self,\r\n request: Request,\r\n follow_redirects: bool,\r\n history: typing.List[Response],\r\n ) -> Response:\r\n while True:\r\n if len(history) > self.max_redirects:\r\n> raise TooManyRedirects(\r\n \"Exceeded maximum allowed redirects.\", request=request\r\n )\r\nE httpx.TooManyRedirects: Exceeded maximum allowed redirects.\r\n\r\n/gnu/store/bj5lb299rfb4cbbq5kczq9imdk9a7y64-python-httpx-0.23.0/lib/python3.9/site-packages/httpx/_client.py:1672: TooManyRedirects\r\n______________ test_database_with_space_in_name[/searchable_view] ______________\r\n[gw22] linux -- Python 3.9.9 /gnu/store/slsh0qjv5j68xda2bb6h8gsxwyi1j25a-python-wrapper-3.9.9/bin/python\r\n\r\napp_client_two_attached_databases = \r\npath = '/searchable_view'\r\n\r\n @pytest.mark.parametrize(\r\n \"path\",\r\n (\r\n \"/\",\r\n \".json\",\r\n \"/searchable\",\r\n \"/searchable.json\",\r\n \"/searchable_view\",\r\n \"/searchable_view.json\",\r\n ),\r\n )\r\n def test_database_with_space_in_name(app_client_two_attached_databases, path):\r\n> response = app_client_two_attached_databases.get(\r\n \"/extra~20database\" + path, follow_redirects=True\r\n )\r\n\r\n/tmp/guix-build-datasette-0.64.2.drv-0/source/tests/test_api.py:920: \r\n_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ \r\n/gnu/store/mcclmphjgbrgpa0v037a4nlq336482g8-python-asgiref-3.4.1/lib/python3.9/site-packages/asgiref/sync.py:223: in __call__\r\n return call_result.result()\r\n/gnu/store/65i3nhcwmz0p8rqbg48gaavyky4g4hwk-python-3.9.9/lib/python3.9/concurrent/futures/_base.py:438: in result\r\n return self.__get_result()\r\n/gnu/store/65i3nhcwmz0p8rqbg48gaavyky4g4hwk-python-3.9.9/lib/python3.9/concurrent/futures/_base.py:390: in __get_result\r\n raise self._exception\r\n/gnu/store/mcclmphjgbrgpa0v037a4nlq336482g8-python-asgiref-3.4.1/lib/python3.9/site-packages/asgiref/sync.py:292: in main_wrap\r\n result = await self.awaitable(*args, **kwargs)\r\n/tmp/guix-build-datasette-0.64.2.drv-0/source/datasette/utils/testing.py:66: in get\r\n return await self._request(\r\n/tmp/guix-build-datasette-0.64.2.drv-0/source/datasette/utils/testing.py:156: in _request\r\n httpx_response = await self.ds.client.request(\r\n/tmp/guix-build-datasette-0.64.2.drv-0/source/datasette/app.py:1602: in request\r\n return await client.request(\r\n/gnu/store/bj5lb299rfb4cbbq5kczq9imdk9a7y64-python-httpx-0.23.0/lib/python3.9/site-packages/httpx/_client.py:1527: in request\r\n return await self.send(request, auth=auth, follow_redirects=follow_redirects)\r\n/gnu/store/bj5lb299rfb4cbbq5kczq9imdk9a7y64-python-httpx-0.23.0/lib/python3.9/site-packages/httpx/_client.py:1614: in send\r\n response = await self._send_handling_auth(\r\n/gnu/store/bj5lb299rfb4cbbq5kczq9imdk9a7y64-python-httpx-0.23.0/lib/python3.9/site-packages/httpx/_client.py:1642: in _send_handling_auth\r\n response = await self._send_handling_redirects(\r\n_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ \r\n\r\nself = \r\nrequest = \r\nfollow_redirects = True\r\nhistory = [, , , , , , ...]\r\n\r\n async def _send_handling_redirects(\r\n self,\r\n request: Request,\r\n follow_redirects: bool,\r\n history: typing.List[Response],\r\n ) -> Response:\r\n while True:\r\n if len(history) > self.max_redirects:\r\n> raise TooManyRedirects(\r\n \"Exceeded maximum allowed redirects.\", request=request\r\n )\r\nE httpx.TooManyRedirects: Exceeded maximum allowed redirects.\r\n\r\n/gnu/store/bj5lb299rfb4cbbq5kczq9imdk9a7y64-python-httpx-0.23.0/lib/python3.9/site-packages/httpx/_client.py:1672: TooManyRedirects\r\n_____________________ test_database_with_space_in_name[/] ______________________\r\n[gw18] linux -- Python 3.9.9 /gnu/store/slsh0qjv5j68xda2bb6h8gsxwyi1j25a-python-wrapper-3.9.9/bin/python\r\n\r\napp_client_two_attached_databases = \r\npath = '/'\r\n\r\n @pytest.mark.parametrize(\r\n \"path\",\r\n (\r\n \"/\",\r\n \".json\",\r\n \"/searchable\",\r\n \"/searchable.json\",\r\n \"/searchable_view\",\r\n \"/searchable_view.json\",\r\n ),\r\n )\r\n def test_database_with_space_in_name(app_client_two_attached_databases, path):\r\n> response = app_client_two_attached_databases.get(\r\n \"/extra~20database\" + path, follow_redirects=True\r\n )\r\n\r\n/tmp/guix-build-datasette-0.64.2.drv-0/source/tests/test_api.py:920: \r\n_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ \r\n/gnu/store/mcclmphjgbrgpa0v037a4nlq336482g8-python-asgiref-3.4.1/lib/python3.9/site-packages/asgiref/sync.py:223: in __call__\r\n return call_result.result()\r\n/gnu/store/65i3nhcwmz0p8rqbg48gaavyky4g4hwk-python-3.9.9/lib/python3.9/concurrent/futures/_base.py:438: in result\r\n return self.__get_result()\r\n/gnu/store/65i3nhcwmz0p8rqbg48gaavyky4g4hwk-python-3.9.9/lib/python3.9/concurrent/futures/_base.py:390: in __get_result\r\n raise self._exception\r\n/gnu/store/mcclmphjgbrgpa0v037a4nlq336482g8-python-asgiref-3.4.1/lib/python3.9/site-packages/asgiref/sync.py:292: in main_wrap\r\n result = await self.awaitable(*args, **kwargs)\r\n/tmp/guix-build-datasette-0.64.2.drv-0/source/datasette/utils/testing.py:66: in get\r\n return await self._request(\r\n/tmp/guix-build-datasette-0.64.2.drv-0/source/datasette/utils/testing.py:156: in _request\r\n httpx_response = await self.ds.client.request(\r\n/tmp/guix-build-datasette-0.64.2.drv-0/source/datasette/app.py:1602: in request\r\n return await client.request(\r\n/gnu/store/bj5lb299rfb4cbbq5kczq9imdk9a7y64-python-httpx-0.23.0/lib/python3.9/site-packages/httpx/_client.py:1527: in request\r\n return await self.send(request, auth=auth, follow_redirects=follow_redirects)\r\n/gnu/store/bj5lb299rfb4cbbq5kczq9imdk9a7y64-python-httpx-0.23.0/lib/python3.9/site-packages/httpx/_client.py:1614: in send\r\n response = await self._send_handling_auth(\r\n/gnu/store/bj5lb299rfb4cbbq5kczq9imdk9a7y64-python-httpx-0.23.0/lib/python3.9/site-packages/httpx/_client.py:1642: in _send_handling_auth\r\n response = await self._send_handling_redirects(\r\n_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ \r\n\r\nself = \r\nrequest = \r\nfollow_redirects = True\r\nhistory = [, , , , , , ...]\r\n\r\n async def _send_handling_redirects(\r\n self,\r\n request: Request,\r\n follow_redirects: bool,\r\n history: typing.List[Response],\r\n ) -> Response:\r\n while True:\r\n if len(history) > self.max_redirects:\r\n> raise TooManyRedirects(\r\n \"Exceeded maximum allowed redirects.\", request=request\r\n )\r\nE httpx.TooManyRedirects: Exceeded maximum allowed redirects.\r\n\r\n/gnu/store/bj5lb299rfb4cbbq5kczq9imdk9a7y64-python-httpx-0.23.0/lib/python3.9/site-packages/httpx/_client.py:1672: TooManyRedirects\r\n________________ test_database_with_space_in_name[/searchable] _________________\r\n[gw20] linux -- Python 3.9.9 /gnu/store/slsh0qjv5j68xda2bb6h8gsxwyi1j25a-python-wrapper-3.9.9/bin/python\r\n\r\napp_client_two_attached_databases = \r\npath = '/searchable'\r\n\r\n @pytest.mark.parametrize(\r\n \"path\",\r\n (\r\n \"/\",\r\n \".json\",\r\n \"/searchable\",\r\n \"/searchable.json\",\r\n \"/searchable_view\",\r\n \"/searchable_view.json\",\r\n ),\r\n )\r\n def test_database_with_space_in_name(app_client_two_attached_databases, path):\r\n> response = app_client_two_attached_databases.get(\r\n \"/extra~20database\" + path, follow_redirects=True\r\n )\r\n\r\n/tmp/guix-build-datasette-0.64.2.drv-0/source/tests/test_api.py:920: \r\n_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ \r\n/gnu/store/mcclmphjgbrgpa0v037a4nlq336482g8-python-asgiref-3.4.1/lib/python3.9/site-packages/asgiref/sync.py:223: in __call__\r\n return call_result.result()\r\n/gnu/store/65i3nhcwmz0p8rqbg48gaavyky4g4hwk-python-3.9.9/lib/python3.9/concurrent/futures/_base.py:438: in result\r\n return self.__get_result()\r\n/gnu/store/65i3nhcwmz0p8rqbg48gaavyky4g4hwk-python-3.9.9/lib/python3.9/concurrent/futures/_base.py:390: in __get_result\r\n raise self._exception\r\n/gnu/store/mcclmphjgbrgpa0v037a4nlq336482g8-python-asgiref-3.4.1/lib/python3.9/site-packages/asgiref/sync.py:292: in main_wrap\r\n result = await self.awaitable(*args, **kwargs)\r\n/tmp/guix-build-datasette-0.64.2.drv-0/source/datasette/utils/testing.py:66: in get\r\n return await self._request(\r\n/tmp/guix-build-datasette-0.64.2.drv-0/source/datasette/utils/testing.py:156: in _request\r\n httpx_response = await self.ds.client.request(\r\n/tmp/guix-build-datasette-0.64.2.drv-0/source/datasette/app.py:1602: in request\r\n return await client.request(\r\n/gnu/store/bj5lb299rfb4cbbq5kczq9imdk9a7y64-python-httpx-0.23.0/lib/python3.9/site-packages/httpx/_client.py:1527: in request\r\n return await self.send(request, auth=auth, follow_redirects=follow_redirects)\r\n/gnu/store/bj5lb299rfb4cbbq5kczq9imdk9a7y64-python-httpx-0.23.0/lib/python3.9/site-packages/httpx/_client.py:1614: in send\r\n response = await self._send_handling_auth(\r\n/gnu/store/bj5lb299rfb4cbbq5kczq9imdk9a7y64-python-httpx-0.23.0/lib/python3.9/site-packages/httpx/_client.py:1642: in _send_handling_auth\r\n response = await self._send_handling_redirects(\r\n_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ \r\n\r\nself = \r\nrequest = \r\nfollow_redirects = True\r\nhistory = [, , , , , , ...]\r\n\r\n async def _send_handling_redirects(\r\n self,\r\n request: Request,\r\n follow_redirects: bool,\r\n history: typing.List[Response],\r\n ) -> Response:\r\n while True:\r\n if len(history) > self.max_redirects:\r\n> raise TooManyRedirects(\r\n \"Exceeded maximum allowed redirects.\", request=request\r\n )\r\nE httpx.TooManyRedirects: Exceeded maximum allowed redirects.\r\n\r\n/gnu/store/bj5lb299rfb4cbbq5kczq9imdk9a7y64-python-httpx-0.23.0/lib/python3.9/site-packages/httpx/_client.py:1672: TooManyRedirects\r\n___________ test_database_with_space_in_name[/searchable_view.json] ____________\r\n[gw23] linux -- Python 3.9.9 /gnu/store/slsh0qjv5j68xda2bb6h8gsxwyi1j25a-python-wrapper-3.9.9/bin/python\r\n\r\napp_client_two_attached_databases = \r\npath = '/searchable_view.json'\r\n\r\n @pytest.mark.parametrize(\r\n \"path\",\r\n (\r\n \"/\",\r\n \".json\",\r\n \"/searchable\",\r\n \"/searchable.json\",\r\n \"/searchable_view\",\r\n \"/searchable_view.json\",\r\n ),\r\n )\r\n def test_database_with_space_in_name(app_client_two_attached_databases, path):\r\n> response = app_client_two_attached_databases.get(\r\n \"/extra~20database\" + path, follow_redirects=True\r\n )\r\n\r\n/tmp/guix-build-datasette-0.64.2.drv-0/source/tests/test_api.py:920: \r\n_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ \r\n/gnu/store/mcclmphjgbrgpa0v037a4nlq336482g8-python-asgiref-3.4.1/lib/python3.9/site-packages/asgiref/sync.py:223: in __call__\r\n return call_result.result()\r\n/gnu/store/65i3nhcwmz0p8rqbg48gaavyky4g4hwk-python-3.9.9/lib/python3.9/concurrent/futures/_base.py:438: in result\r\n return self.__get_result()\r\n/gnu/store/65i3nhcwmz0p8rqbg48gaavyky4g4hwk-python-3.9.9/lib/python3.9/concurrent/futures/_base.py:390: in __get_result\r\n raise self._exception\r\n/gnu/store/mcclmphjgbrgpa0v037a4nlq336482g8-python-asgiref-3.4.1/lib/python3.9/site-packages/asgiref/sync.py:292: in main_wrap\r\n result = await self.awaitable(*args, **kwargs)\r\n/tmp/guix-build-datasette-0.64.2.drv-0/source/datasette/utils/testing.py:66: in get\r\n return await self._request(\r\n/tmp/guix-build-datasette-0.64.2.drv-0/source/datasette/utils/testing.py:156: in _request\r\n httpx_response = await self.ds.client.request(\r\n/tmp/guix-build-datasette-0.64.2.drv-0/source/datasette/app.py:1602: in request\r\n return await client.request(\r\n/gnu/store/bj5lb299rfb4cbbq5kczq9imdk9a7y64-python-httpx-0.23.0/lib/python3.9/site-packages/httpx/_client.py:1527: in request\r\n return await self.send(request, auth=auth, follow_redirects=follow_redirects)\r\n/gnu/store/bj5lb299rfb4cbbq5kczq9imdk9a7y64-python-httpx-0.23.0/lib/python3.9/site-packages/httpx/_client.py:1614: in send\r\n response = await self._send_handling_auth(\r\n/gnu/store/bj5lb299rfb4cbbq5kczq9imdk9a7y64-python-httpx-0.23.0/lib/python3.9/site-packages/httpx/_client.py:1642: in _send_handling_auth\r\n response = await self._send_handling_redirects(\r\n_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ \r\n\r\nself = \r\nrequest = \r\nfollow_redirects = True\r\nhistory = [, , , , , , ...]\r\n\r\n async def _send_handling_redirects(\r\n self,\r\n request: Request,\r\n follow_redirects: bool,\r\n history: typing.List[Response],\r\n ) -> Response:\r\n while True:\r\n if len(history) > self.max_redirects:\r\n> raise TooManyRedirects(\r\n \"Exceeded maximum allowed redirects.\", request=request\r\n )\r\nE httpx.TooManyRedirects: Exceeded maximum allowed redirects.\r\n\r\n/gnu/store/bj5lb299rfb4cbbq5kczq9imdk9a7y64-python-httpx-0.23.0/lib/python3.9/site-packages/httpx/_client.py:1672: TooManyRedirects\r\n________________ test_weird_database_names[database (1).sqlite] ________________\r\n[gw7] linux -- Python 3.9.9 /gnu/store/slsh0qjv5j68xda2bb6h8gsxwyi1j25a-python-wrapper-3.9.9/bin/python\r\n\r\ntmpdir = local('/tmp/guix-build-datasette-0.64.2.drv-0/pytest-of-nixbld/pytest-0/popen-gw7/test_weird_database_names_data0')\r\nfilename = 'database (1).sqlite'\r\n\r\n @pytest.mark.parametrize(\r\n \"filename\", [\"test-database (1).sqlite\", \"database (1).sqlite\"]\r\n )\r\n def test_weird_database_names(tmpdir, filename):\r\n # https://github.com/simonw/datasette/issues/1181\r\n runner = CliRunner()\r\n db_path = str(tmpdir / filename)\r\n sqlite3.connect(db_path).execute(\"vacuum\")\r\n result1 = runner.invoke(cli, [db_path, \"--get\", \"/\"])\r\n assert result1.exit_code == 0, result1.output\r\n filename_no_stem = filename.rsplit(\".\", 1)[0]\r\n expected_link = '{}'.format(\r\n tilde_encode(filename_no_stem), filename_no_stem\r\n )\r\n assert expected_link in result1.output\r\n # Now try hitting that database page\r\n result2 = runner.invoke(\r\n cli, [db_path, \"--get\", \"/{}\".format(tilde_encode(filename_no_stem))]\r\n )\r\n> assert result2.exit_code == 0, result2.output\r\nE AssertionError: \r\nE \r\nE assert 1 == 0\r\nE + where 1 = .exit_code\r\n\r\n/tmp/guix-build-datasette-0.64.2.drv-0/source/tests/test_cli.py:321: AssertionError\r\n_____________ test_weird_database_names[test-database (1).sqlite] ______________\r\n[gw6] linux -- Python 3.9.9 /gnu/store/slsh0qjv5j68xda2bb6h8gsxwyi1j25a-python-wrapper-3.9.9/bin/python\r\n\r\ntmpdir = local('/tmp/guix-build-datasette-0.64.2.drv-0/pytest-of-nixbld/pytest-0/popen-gw6/test_weird_database_names_test0')\r\nfilename = 'test-database (1).sqlite'\r\n\r\n @pytest.mark.parametrize(\r\n \"filename\", [\"test-database (1).sqlite\", \"database (1).sqlite\"]\r\n )\r\n def test_weird_database_names(tmpdir, filename):\r\n # https://github.com/simonw/datasette/issues/1181\r\n runner = CliRunner()\r\n db_path = str(tmpdir / filename)\r\n sqlite3.connect(db_path).execute(\"vacuum\")\r\n result1 = runner.invoke(cli, [db_path, \"--get\", \"/\"])\r\n assert result1.exit_code == 0, result1.output\r\n filename_no_stem = filename.rsplit(\".\", 1)[0]\r\n expected_link = '{}'.format(\r\n tilde_encode(filename_no_stem), filename_no_stem\r\n )\r\n assert expected_link in result1.output\r\n # Now try hitting that database page\r\n result2 = runner.invoke(\r\n cli, [db_path, \"--get\", \"/{}\".format(tilde_encode(filename_no_stem))]\r\n )\r\n> assert result2.exit_code == 0, result2.output\r\nE AssertionError: \r\nE \r\nE assert 1 == 0\r\nE + where 1 = .exit_code\r\n\r\n/tmp/guix-build-datasette-0.64.2.drv-0/source/tests/test_cli.py:321: AssertionError\r\n_ test_row_html_compound_primary_key[/fixtures/compound_primary_key/a~2Fb,~2Ec~2Dd-expected1] _\r\n[gw11] linux -- Python 3.9.9 /gnu/store/slsh0qjv5j68xda2bb6h8gsxwyi1j25a-python-wrapper-3.9.9/bin/python\r\n\r\napp_client = \r\npath = '/fixtures/compound_primary_key/a~2Fb,~2Ec~2Dd'\r\nexpected = [['a/b', '.c-d', 'c']]\r\n\r\n @pytest.mark.parametrize(\r\n \"path,expected\",\r\n (\r\n (\r\n \"/fixtures/compound_primary_key/a,b\",\r\n [\r\n [\r\n 'a',\r\n 'b',\r\n 'c',\r\n ]\r\n ],\r\n ),\r\n (\r\n \"/fixtures/compound_primary_key/a~2Fb,~2Ec~2Dd\",\r\n [\r\n [\r\n 'a/b',\r\n '.c-d',\r\n 'c',\r\n ]\r\n ],\r\n ),\r\n ),\r\n )\r\n def test_row_html_compound_primary_key(app_client, path, expected):\r\n response = app_client.get(path)\r\n> assert response.status == 200\r\nE assert 302 == 200\r\nE + where 302 = .status\r\n\r\n/tmp/guix-build-datasette-0.64.2.drv-0/source/tests/test_html.py:370: AssertionError\r\n_ test_css_classes_on_body[/fixtures/table~2Fwith~2Fslashes~2Ecsv-expected_classes5] _\r\n[gw3] linux -- Python 3.9.9 /gnu/store/slsh0qjv5j68xda2bb6h8gsxwyi1j25a-python-wrapper-3.9.9/bin/python\r\n\r\napp_client = \r\npath = '/fixtures/table~2Fwith~2Fslashes~2Ecsv'\r\nexpected_classes = ['table', 'db-fixtures', 'table-tablewithslashescsv-fa7563']\r\n\r\n @pytest.mark.parametrize(\r\n \"path,expected_classes\",\r\n [\r\n (\"/\", [\"index\"]),\r\n (\"/fixtures\", [\"db\", \"db-fixtures\"]),\r\n (\"/fixtures?sql=select+1\", [\"query\", \"db-fixtures\"]),\r\n (\r\n \"/fixtures/simple_primary_key\",\r\n [\"table\", \"db-fixtures\", \"table-simple_primary_key\"],\r\n ),\r\n (\r\n \"/fixtures/neighborhood_search\",\r\n [\"query\", \"db-fixtures\", \"query-neighborhood_search\"],\r\n ),\r\n (\r\n \"/fixtures/table~2Fwith~2Fslashes~2Ecsv\",\r\n [\"table\", \"db-fixtures\", \"table-tablewithslashescsv-fa7563\"],\r\n ),\r\n (\r\n \"/fixtures/simple_primary_key/1\",\r\n [\"row\", \"db-fixtures\", \"table-simple_primary_key\"],\r\n ),\r\n ],\r\n )\r\n def test_css_classes_on_body(app_client, path, expected_classes):\r\n response = app_client.get(path)\r\n> assert response.status == 200\r\nE assert 302 == 200\r\nE + where 302 = .status\r\n\r\n/tmp/guix-build-datasette-0.64.2.drv-0/source/tests/test_html.py:238: AssertionError\r\n_ test_templates_considered[/fixtures/table~2Fwith~2Fslashes~2Ecsv-table-fixtures-tablewithslashescsv-fa7563.html, *table.html] _\r\n[gw3] linux -- Python 3.9.9 /gnu/store/slsh0qjv5j68xda2bb6h8gsxwyi1j25a-python-wrapper-3.9.9/bin/python\r\n\r\napp_client = \r\npath = '/fixtures/table~2Fwith~2Fslashes~2Ecsv'\r\nexpected_considered = 'table-fixtures-tablewithslashescsv-fa7563.html, *table.html'\r\n\r\n @pytest.mark.parametrize(\r\n \"path,expected_considered\",\r\n [\r\n (\"/\", \"*index.html\"),\r\n (\"/fixtures\", \"database-fixtures.html, *database.html\"),\r\n (\r\n \"/fixtures/simple_primary_key\",\r\n \"table-fixtures-simple_primary_key.html, *table.html\",\r\n ),\r\n (\r\n \"/fixtures/table~2Fwith~2Fslashes~2Ecsv\",\r\n \"table-fixtures-tablewithslashescsv-fa7563.html, *table.html\",\r\n ),\r\n (\r\n \"/fixtures/simple_primary_key/1\",\r\n \"row-fixtures-simple_primary_key.html, *row.html\",\r\n ),\r\n ],\r\n )\r\n def test_templates_considered(app_client, path, expected_considered):\r\n response = app_client.get(path)\r\n> assert response.status == 200\r\nE assert 302 == 200\r\nE + where 302 = .status\r\n\r\n/tmp/guix-build-datasette-0.64.2.drv-0/source/tests/test_html.py:264: AssertionError\r\n_ test_alternate_url_json[/fixtures/table~2Fwith~2Fslashes~2Ecsv-http://localhost/fixtures/table~2Fwith~2Fslashes~2Ecsv.json] _\r\n[gw21] linux -- Python 3.9.9 /gnu/store/slsh0qjv5j68xda2bb6h8gsxwyi1j25a-python-wrapper-3.9.9/bin/python\r\n\r\napp_client = \r\npath = '/fixtures/table~2Fwith~2Fslashes~2Ecsv'\r\nexpected = 'http://localhost/fixtures/table~2Fwith~2Fslashes~2Ecsv.json'\r\n\r\n @pytest.mark.parametrize(\r\n \"path,expected\",\r\n (\r\n # Instance index page\r\n (\"/\", \"http://localhost/.json\"),\r\n # Table page\r\n (\"/fixtures/facetable\", \"http://localhost/fixtures/facetable.json\"),\r\n (\r\n \"/fixtures/table~2Fwith~2Fslashes~2Ecsv\",\r\n \"http://localhost/fixtures/table~2Fwith~2Fslashes~2Ecsv.json\",\r\n ),\r\n # Row page\r\n (\r\n \"/fixtures/no_primary_key/1\",\r\n \"http://localhost/fixtures/no_primary_key/1.json\",\r\n ),\r\n # Database index page\r\n (\r\n \"/fixtures\",\r\n \"http://localhost/fixtures.json\",\r\n ),\r\n # Custom query page\r\n (\r\n \"/fixtures?sql=select+*+from+facetable\",\r\n \"http://localhost/fixtures.json?sql=select+*+from+facetable\",\r\n ),\r\n # Canned query page\r\n (\r\n \"/fixtures/neighborhood_search?text=town\",\r\n \"http://localhost/fixtures/neighborhood_search.json?text=town\",\r\n ),\r\n # /-/ pages\r\n (\r\n \"/-/plugins\",\r\n \"http://localhost/-/plugins.json\",\r\n ),\r\n ),\r\n )\r\n def test_alternate_url_json(app_client, path, expected):\r\n response = app_client.get(path)\r\n> assert response.status == 200\r\nE assert 302 == 200\r\nE + where 302 = .status\r\n\r\n/tmp/guix-build-datasette-0.64.2.drv-0/source/tests/test_html.py:948: AssertionError\r\n_ test_edit_sql_link_on_canned_queries[/fixtures/~F0~9D~90~9C~F0~9D~90~A2~F0~9D~90~AD~F0~9D~90~A2~F0~9D~90~9E~F0~9D~90~AC-/fixtures?sql=select+id%2C+name+from+facet_cities+order+by+id+limit+1%3B] _\r\n[gw18] linux -- Python 3.9.9 /gnu/store/slsh0qjv5j68xda2bb6h8gsxwyi1j25a-python-wrapper-3.9.9/bin/python\r\n\r\napp_client = \r\npath = '/fixtures/~F0~9D~90~9C~F0~9D~90~A2~F0~9D~90~AD~F0~9D~90~A2~F0~9D~90~9E~F0~9D~90~AC'\r\nexpected = '/fixtures?sql=select+id%2C+name+from+facet_cities+order+by+id+limit+1%3B'\r\n\r\n @pytest.mark.parametrize(\r\n \"path,expected\",\r\n [\r\n (\r\n \"/fixtures/neighborhood_search\",\r\n \"/fixtures?sql=%0Aselect+_neighborhood%2C+facet_cities.name%2C+state%0Afrom+facetable%0A++++join+facet_cities%0A++++++++on+facetable._city_id+%3D+facet_cities.id%0Awhere+_neighborhood+like+%27%25%27+%7C%7C+%3Atext+%7C%7C+%27%25%27%0Aorder+by+_neighborhood%3B%0A&text=\",\r\n ),\r\n (\r\n \"/fixtures/neighborhood_search?text=ber\",\r\n \"/fixtures?sql=%0Aselect+_neighborhood%2C+facet_cities.name%2C+state%0Afrom+facetable%0A++++join+facet_cities%0A++++++++on+facetable._city_id+%3D+facet_cities.id%0Awhere+_neighborhood+like+%27%25%27+%7C%7C+%3Atext+%7C%7C+%27%25%27%0Aorder+by+_neighborhood%3B%0A&text=ber\",\r\n ),\r\n (\"/fixtures/pragma_cache_size\", None),\r\n (\r\n # /fixtures/\ud835\udc1c\ud835\udc22\ud835\udc2d\ud835\udc22\ud835\udc1e\ud835\udc2c\r\n \"/fixtures/~F0~9D~90~9C~F0~9D~90~A2~F0~9D~90~AD~F0~9D~90~A2~F0~9D~90~9E~F0~9D~90~AC\",\r\n \"/fixtures?sql=select+id%2C+name+from+facet_cities+order+by+id+limit+1%3B\",\r\n ),\r\n (\"/fixtures/magic_parameters\", None),\r\n ],\r\n )\r\n def test_edit_sql_link_on_canned_queries(app_client, path, expected):\r\n response = app_client.get(path)\r\n> assert response.status == 200\r\nE assert 302 == 200\r\nE + where 302 = .status\r\n\r\n/tmp/guix-build-datasette-0.64.2.drv-0/source/tests/test_html.py:841: AssertionError\r\n_______________________ test_table_with_slashes_in_name ________________________\r\n[gw9] linux -- Python 3.9.9 /gnu/store/slsh0qjv5j68xda2bb6h8gsxwyi1j25a-python-wrapper-3.9.9/bin/python\r\n\r\napp_client = \r\n\r\n def test_table_with_slashes_in_name(app_client):\r\n response = app_client.get(\r\n \"/fixtures/table~2Fwith~2Fslashes~2Ecsv.json?_shape=objects\"\r\n )\r\n> assert response.status == 200\r\nE assert 302 == 200\r\nE + where 302 = .status\r\n\r\n/tmp/guix-build-datasette-0.64.2.drv-0/source/tests/test_table_api.py:141: AssertionError\r\n__________________ test_custom_query_with_unicode_characters ___________________\r\n[gw8] linux -- Python 3.9.9 /gnu/store/slsh0qjv5j68xda2bb6h8gsxwyi1j25a-python-wrapper-3.9.9/bin/python\r\n\r\napp_client = \r\n\r\n def test_custom_query_with_unicode_characters(app_client):\r\n # /fixtures/\ud835\udc1c\ud835\udc22\ud835\udc2d\ud835\udc22\ud835\udc1e\ud835\udc2c.json\r\n response = app_client.get(\r\n \"/fixtures/~F0~9D~90~9C~F0~9D~90~A2~F0~9D~90~AD~F0~9D~90~A2~F0~9D~90~9E~F0~9D~90~AC.json?_shape=array\"\r\n )\r\n> assert [{\"id\": 1, \"name\": \"San Francisco\"}] == response.json\r\n\r\n/tmp/guix-build-datasette-0.64.2.drv-0/source/tests/test_table_api.py:1042: \r\n_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ \r\n/tmp/guix-build-datasette-0.64.2.drv-0/source/datasette/utils/testing.py:40: in json\r\n return json.loads(self.text)\r\n/gnu/store/65i3nhcwmz0p8rqbg48gaavyky4g4hwk-python-3.9.9/lib/python3.9/json/__init__.py:346: in loads\r\n return _default_decoder.decode(s)\r\n/gnu/store/65i3nhcwmz0p8rqbg48gaavyky4g4hwk-python-3.9.9/lib/python3.9/json/decoder.py:337: in decode\r\n obj, end = self.raw_decode(s, idx=_w(s, 0).end())\r\n_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ \r\n\r\nself = , s = '', idx = 0\r\n\r\n def raw_decode(self, s, idx=0):\r\n \"\"\"Decode a JSON document from ``s`` (a ``str`` beginning with\r\n a JSON document) and return a 2-tuple of the Python\r\n representation and the index in ``s`` where the document ended.\r\n \r\n This can be used to decode a JSON document from a string that may\r\n have extraneous data at the end.\r\n \r\n \"\"\"\r\n try:\r\n obj, end = self.scan_once(s, idx)\r\n except StopIteration as err:\r\n> raise JSONDecodeError(\"Expecting value\", s, err.value) from None\r\nE json.decoder.JSONDecodeError: Expecting value: line 1 column 1 (char 0)\r\n\r\n/gnu/store/65i3nhcwmz0p8rqbg48gaavyky4g4hwk-python-3.9.9/lib/python3.9/json/decoder.py:355: JSONDecodeError\r\n_ test_searchable[/fixtures/searchable.json?_search=te*+AND+do*&_searchmode=raw-expected_rows3] _\r\n[gw13] linux -- Python 3.9.9 /gnu/store/slsh0qjv5j68xda2bb6h8gsxwyi1j25a-python-wrapper-3.9.9/bin/python\r\n\r\napp_client = \r\npath = '/fixtures/searchable.json?_search=te*+AND+do*&_searchmode=raw'\r\nexpected_rows = [[1, 'barry cat', 'terry dog', 'panther'], [2, 'terry dog', 'sara weasel', 'puma']]\r\n\r\n @pytest.mark.parametrize(\r\n \"path,expected_rows\",\r\n [\r\n (\r\n \"/fixtures/searchable.json?_search=dog\",\r\n [\r\n [1, \"barry cat\", \"terry dog\", \"panther\"],\r\n [2, \"terry dog\", \"sara weasel\", \"puma\"],\r\n ],\r\n ),\r\n (\r\n # Special keyword shouldn't break FTS query\r\n \"/fixtures/searchable.json?_search=AND\",\r\n [],\r\n ),\r\n (\r\n # Without _searchmode=raw this should return no results\r\n \"/fixtures/searchable.json?_search=te*+AND+do*\",\r\n [],\r\n ),\r\n (\r\n # _searchmode=raw\r\n \"/fixtures/searchable.json?_search=te*+AND+do*&_searchmode=raw\",\r\n [\r\n [1, \"barry cat\", \"terry dog\", \"panther\"],\r\n [2, \"terry dog\", \"sara weasel\", \"puma\"],\r\n ],\r\n ),\r\n (\r\n # _searchmode=raw combined with _search_COLUMN\r\n \"/fixtures/searchable.json?_search_text2=te*&_searchmode=raw\",\r\n [\r\n [1, \"barry cat\", \"terry dog\", \"panther\"],\r\n ],\r\n ),\r\n (\r\n \"/fixtures/searchable.json?_search=weasel\",\r\n [[2, \"terry dog\", \"sara weasel\", \"puma\"]],\r\n ),\r\n (\r\n \"/fixtures/searchable.json?_search_text2=dog\",\r\n [[1, \"barry cat\", \"terry dog\", \"panther\"]],\r\n ),\r\n (\r\n \"/fixtures/searchable.json?_search_name%20with%20.%20and%20spaces=panther\",\r\n [[1, \"barry cat\", \"terry dog\", \"panther\"]],\r\n ),\r\n ],\r\n )\r\n def test_searchable(app_client, path, expected_rows):\r\n response = app_client.get(path)\r\n> assert expected_rows == response.json[\"rows\"]\r\nE AssertionError: assert [[1, 'barry cat', 'terry dog', 'panther'],\\n [2, 'terry dog', 'sara weasel', 'puma']] == []\r\nE Left contains 2 more items, first extra item: [1, 'barry cat', 'terry dog', 'panther']\r\nE Full diff:\r\nE [\r\nE - ,\r\nE + [1,\r\nE + 'barry cat',\r\nE + 'terry dog',\r\nE + 'panther'],\r\nE + [2,\r\nE + 'terry dog',\r\nE + 'sara weasel',\r\nE + 'puma'],\r\nE ]\r\n\r\n/tmp/guix-build-datasette-0.64.2.drv-0/source/tests/test_table_api.py:402: AssertionError\r\n_____ test_searchmode[table_metadata1-_search=te*+AND+do*-expected_rows1] ______\r\n[gw20] linux -- Python 3.9.9 /gnu/store/slsh0qjv5j68xda2bb6h8gsxwyi1j25a-python-wrapper-3.9.9/bin/python\r\n\r\ntable_metadata = {'searchmode': 'raw'}, querystring = '_search=te*+AND+do*'\r\nexpected_rows = [[1, 'barry cat', 'terry dog', 'panther'], [2, 'terry dog', 'sara weasel', 'puma']]\r\n\r\n @pytest.mark.parametrize(\r\n \"table_metadata,querystring,expected_rows\",\r\n [\r\n (\r\n {},\r\n \"_search=te*+AND+do*\",\r\n [],\r\n ),\r\n (\r\n {\"searchmode\": \"raw\"},\r\n \"_search=te*+AND+do*\",\r\n _SEARCHMODE_RAW_RESULTS,\r\n ),\r\n (\r\n {},\r\n \"_search=te*+AND+do*&_searchmode=raw\",\r\n _SEARCHMODE_RAW_RESULTS,\r\n ),\r\n # Can be over-ridden with _searchmode=escaped\r\n (\r\n {\"searchmode\": \"raw\"},\r\n \"_search=te*+AND+do*&_searchmode=escaped\",\r\n [],\r\n ),\r\n ],\r\n )\r\n def test_searchmode(table_metadata, querystring, expected_rows):\r\n with make_app_client(\r\n metadata={\"databases\": {\"fixtures\": {\"tables\": {\"searchable\": table_metadata}}}}\r\n ) as client:\r\n response = client.get(\"/fixtures/searchable.json?\" + querystring)\r\n> assert expected_rows == response.json[\"rows\"]\r\nE AssertionError: assert [[1, 'barry cat', 'terry dog', 'panther'],\\n [2, 'terry dog', 'sara weasel', 'puma']] == []\r\nE Left contains 2 more items, first extra item: [1, 'barry cat', 'terry dog', 'panther']\r\nE Full diff:\r\nE [\r\nE - ,\r\nE + [1,\r\nE + 'barry cat',\r\nE + 'terry dog',\r\nE + 'panther'],\r\nE + [2,\r\nE + 'terry dog',\r\nE + 'sara weasel',\r\nE + 'puma'],\r\nE ]\r\n\r\n/tmp/guix-build-datasette-0.64.2.drv-0/source/tests/test_table_api.py:442: AssertionError\r\n_ test_searchmode[table_metadata2-_search=te*+AND+do*&_searchmode=raw-expected_rows2] _\r\n[gw20] linux -- Python 3.9.9 /gnu/store/slsh0qjv5j68xda2bb6h8gsxwyi1j25a-python-wrapper-3.9.9/bin/python\r\n\r\ntable_metadata = {}, querystring = '_search=te*+AND+do*&_searchmode=raw'\r\nexpected_rows = [[1, 'barry cat', 'terry dog', 'panther'], [2, 'terry dog', 'sara weasel', 'puma']]\r\n\r\n @pytest.mark.parametrize(\r\n \"table_metadata,querystring,expected_rows\",\r\n [\r\n (\r\n {},\r\n \"_search=te*+AND+do*\",\r\n [],\r\n ),\r\n (\r\n {\"searchmode\": \"raw\"},\r\n \"_search=te*+AND+do*\",\r\n _SEARCHMODE_RAW_RESULTS,\r\n ),\r\n (\r\n {},\r\n \"_search=te*+AND+do*&_searchmode=raw\",\r\n _SEARCHMODE_RAW_RESULTS,\r\n ),\r\n # Can be over-ridden with _searchmode=escaped\r\n (\r\n {\"searchmode\": \"raw\"},\r\n \"_search=te*+AND+do*&_searchmode=escaped\",\r\n [],\r\n ),\r\n ],\r\n )\r\n def test_searchmode(table_metadata, querystring, expected_rows):\r\n with make_app_client(\r\n metadata={\"databases\": {\"fixtures\": {\"tables\": {\"searchable\": table_metadata}}}}\r\n ) as client:\r\n response = client.get(\"/fixtures/searchable.json?\" + querystring)\r\n> assert expected_rows == response.json[\"rows\"]\r\nE AssertionError: assert [[1, 'barry cat', 'terry dog', 'panther'],\\n [2, 'terry dog', 'sara weasel', 'puma']] == []\r\nE Left contains 2 more items, first extra item: [1, 'barry cat', 'terry dog', 'panther']\r\nE Full diff:\r\nE [\r\nE - ,\r\nE + [1,\r\nE + 'barry cat',\r\nE + 'terry dog',\r\nE + 'panther'],\r\nE + [2,\r\nE + 'terry dog',\r\nE + 'sara weasel',\r\nE + 'puma'],\r\nE ]\r\n\r\n/tmp/guix-build-datasette-0.64.2.drv-0/source/tests/test_table_api.py:442: AssertionError\r\n=========================== short test summary info ============================\r\nFAILED tests/test_api.py::test_row_strange_table_name - assert 400 == 200\r\nFAILED tests/test_api.py::test_database_page_for_database_with_dot_in_name - ...\r\nFAILED tests/test_api.py::test_tilde_encoded_database_names[fo%o] - assert 30...\r\nFAILED tests/test_api.py::test_tilde_encoded_database_names[f~/c.d] - assert ...\r\nFAILED tests/test_api.py::test_database_with_space_in_name[/searchable.json]\r\nFAILED tests/test_api.py::test_database_with_space_in_name[.json] - httpx.Too...\r\nFAILED tests/test_api.py::test_database_with_space_in_name[/searchable_view]\r\nFAILED tests/test_api.py::test_database_with_space_in_name[/] - httpx.TooMany...\r\nFAILED tests/test_api.py::test_database_with_space_in_name[/searchable] - htt...\r\nFAILED tests/test_api.py::test_database_with_space_in_name[/searchable_view.json]\r\nFAILED tests/test_cli.py::test_weird_database_names[database (1).sqlite] - As...\r\nFAILED tests/test_cli.py::test_weird_database_names[test-database (1).sqlite]\r\nFAILED tests/test_html.py::test_row_html_compound_primary_key[/fixtures/compound_primary_key/a~2Fb,~2Ec~2Dd-expected1]\r\nFAILED tests/test_html.py::test_css_classes_on_body[/fixtures/table~2Fwith~2Fslashes~2Ecsv-expected_classes5]\r\nFAILED tests/test_html.py::test_templates_considered[/fixtures/table~2Fwith~2Fslashes~2Ecsv-table-fixtures-tablewithslashescsv-fa7563.html, *table.html]\r\nFAILED tests/test_html.py::test_alternate_url_json[/fixtures/table~2Fwith~2Fslashes~2Ecsv-http://localhost/fixtures/table~2Fwith~2Fslashes~2Ecsv.json]\r\nFAILED tests/test_html.py::test_edit_sql_link_on_canned_queries[/fixtures/~F0~9D~90~9C~F0~9D~90~A2~F0~9D~90~AD~F0~9D~90~A2~F0~9D~90~9E~F0~9D~90~AC-/fixtures?sql=select+id%2C+name+from+facet_cities+order+by+id+limit+1%3B]\r\nFAILED tests/test_table_api.py::test_table_with_slashes_in_name - assert 302 ...\r\nFAILED tests/test_table_api.py::test_custom_query_with_unicode_characters - j...\r\nFAILED tests/test_table_api.py::test_searchable[/fixtures/searchable.json?_search=te*+AND+do*&_searchmode=raw-expected_rows3]\r\nFAILED tests/test_table_api.py::test_searchmode[table_metadata1-_search=te*+AND+do*-expected_rows1]\r\nFAILED tests/test_table_api.py::test_searchmode[table_metadata2-_search=te*+AND+do*&_searchmode=raw-expected_rows2]\r\n=========== 22 failed, 1049 passed, 3 skipped in 1522.28s (0:25:22) ============\r\nerror: in phase 'check': uncaught exception:\r\n%exception #<&invoke-error program: \"/gnu/store/ziqwkzz6znb5d3c245xn0cq5ra2ly0w3-python-pytest-7.1.3/bin/pytest\" arguments: (\"-vv\" \"-n\" \"24\" \"-m\" \"not serial\") exit-status: 1 term-signal: #f stop-signal: #f> \r\nphase `check' failed after 1523.3 seconds\r\n```\r\nThe tests run in a private namespace without internet connectivity, and the Python dependencies are at:\r\n```\r\npython-aiofiles@0.6.0 python-asgi-csrf@0.9 python-asgiref@3.4.1\r\n+ python-beautifulsoup4@4.11.1 python-black@22.3.0 python-click-default-group@1.2.2 python-click@8.1.3\r\n+ python-cogapp@3.3.0 python-httpx@0.23.0 python-hupper@1.10.3 python-itsdangerous@2.0.1\r\n+ python-janus@1.0.0 python-jinja2@3.1.1 python-mergedeep@1.3.4 python-pint@0.20.1 python-pluggy@1.0.0\r\n+ python-pytest-asyncio@0.17.2 python-pytest-runner@5.2 python-pytest-timeout@2.0.2\r\n+ python-pytest-xdist@2.5.0 python-pytest@7.1.3 python-pyyaml@6.0 python-setuptools@64.0.3\r\n+ python-trustme@0.9.0 python-uvicorn@0.17.6\r\n```\r\nWith Python 3.9.9.\r\n\r\nThank you!", "repo": {"value": 107914493, "label": "datasette"}, "type": "issue", "active_lock_reason": null, "performed_via_github_app": null, "reactions": "{\"url\": \"https://api.github.com/repos/simonw/datasette/issues/2048/reactions\", \"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "draft": null, "state_reason": null} {"id": 1590183272, "node_id": "I_kwDOBm6k_c5eyEVo", "number": 2027, "title": "How to redirect from \"/\" to a specific db/table", "user": {"value": 1350673, "label": "dmick"}, "state": "open", "locked": 0, "assignee": null, "milestone": null, "comments": 4, "created_at": "2023-02-18T03:14:01Z", "updated_at": "2023-03-08T04:42:22Z", "closed_at": null, "author_association": "NONE", "pull_request": null, "body": "Using nginx to redirect public IP to the local uvicorn server as 'normal'. I can't figure out how to redirect such that '/' results in accessing the one db/table I want to serve; redirecting / to /db/table breaks some of the CSS; fooling with base_url doesn't seem to help. Can someone explain this, if it's possible?", "repo": {"value": 107914493, "label": "datasette"}, "type": "issue", "active_lock_reason": null, "performed_via_github_app": null, "reactions": "{\"url\": \"https://api.github.com/repos/simonw/datasette/issues/2027/reactions\", \"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "draft": null, "state_reason": null} {"id": 1594383280, "node_id": "I_kwDOBm6k_c5fCFuw", "number": 2030, "title": "How to use Datasette with apache webserver on GCP?", "user": {"value": 19700859, "label": "gk7279"}, "state": "closed", "locked": 0, "assignee": null, "milestone": null, "comments": 2, "created_at": "2023-02-22T03:08:49Z", "updated_at": "2023-02-22T21:54:39Z", "closed_at": "2023-02-22T21:54:39Z", "author_association": "NONE", "pull_request": null, "body": "Hi Simon and Datasette team-\r\n\r\nI have installed apache2 webserver inside GCP VM using apt.\r\n\r\nI can see my \"Hello World\" index.html if I use the external IP of this GCP in a browser.\r\n\r\nHowever, when I try to run datasette with different combinations of -h and -p, I am still unable to access the webpage.\r\n\r\nI cannot invest Docker on this VM.\r\n\r\nAny pointers to use datasette with already existing apache2 webserver on GCP is appreciated.\r\n\r\nThanks.", "repo": {"value": 107914493, "label": "datasette"}, "type": "issue", "active_lock_reason": null, "performed_via_github_app": null, "reactions": "{\"url\": \"https://api.github.com/repos/simonw/datasette/issues/2030/reactions\", \"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "draft": null, "state_reason": "completed"} {"id": 828858421, "node_id": "MDU6SXNzdWU4Mjg4NTg0MjE=", "number": 1258, "title": "Allow canned query params to specify default values", "user": {"value": 1385831, "label": "wdccdw"}, "state": "open", "locked": 0, "assignee": null, "milestone": null, "comments": 5, "created_at": "2021-03-11T07:19:02Z", "updated_at": "2023-02-20T23:39:58Z", "closed_at": null, "author_association": "NONE", "pull_request": null, "body": "If I call a canned query that includes named parameters, without passing any parameters, datasette runs the query anyway, resulting in an HTTP status code 400, and a visible error in the browser, with only a link back to home. This means that one of the default links on https://site/database/ will lead to a broken page with no apparent way out.\r\n\r\n![image](https://user-images.githubusercontent.com/1385831/110748683-13e72300-820e-11eb-855c-32e03dfef5bf.png)\r\n\r\nIs there any way to skip performing the query when parameters aren't supplied, but otherwise render the usual canned query page? Alternatively, can I supply default values for my parameters, either when defining my canned queries or when linking to the canned query page from the default database template.", "repo": {"value": 107914493, "label": "datasette"}, "type": "issue", "active_lock_reason": null, "performed_via_github_app": null, "reactions": "{\"url\": \"https://api.github.com/repos/simonw/datasette/issues/1258/reactions\", \"total_count\": 1, \"+1\": 1, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "draft": null, "state_reason": null} {"id": 1592327343, "node_id": "I_kwDOBm6k_c5e6Pyv", "number": 2029, "title": "Sorry Simon, didn't know how else to contact you", "user": {"value": 5804626, "label": "llchristopherson"}, "state": "open", "locked": 0, "assignee": null, "milestone": null, "comments": 0, "created_at": "2023-02-20T19:02:53Z", "updated_at": "2023-02-20T19:02:53Z", "closed_at": null, "author_association": "NONE", "pull_request": null, "body": "Hi Simon,\r\n\r\nWould you be willing to chat with me about Datasette? I have some questions. I am working on a project to evaluate data ingestion tools for a research organization and I ran across Datasette. I have looked through a lot of your documentation, but still have some questions, which are very specific. If you would be willing to write me back about this, my email is laura@renci.org.\r\n\r\nThanks,\r\nLaura", "repo": {"value": 107914493, "label": "datasette"}, "type": "issue", "active_lock_reason": null, "performed_via_github_app": null, "reactions": "{\"url\": \"https://api.github.com/repos/simonw/datasette/issues/2029/reactions\", \"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "draft": null, "state_reason": null} {"id": 1323346408, "node_id": "I_kwDOBm6k_c5O4Kno", "number": 1775, "title": "i18n support", "user": {"value": 428820, "label": "johnfelipe"}, "state": "open", "locked": 0, "assignee": null, "milestone": null, "comments": 9, "created_at": "2022-07-31T02:51:04Z", "updated_at": "2023-02-10T18:04:40Z", "closed_at": null, "author_association": "NONE", "pull_request": null, "body": "I want contribute for translate UI to es, de, de and it if you share strings", "repo": {"value": 107914493, "label": "datasette"}, "type": "issue", "active_lock_reason": null, "performed_via_github_app": null, "reactions": "{\"url\": \"https://api.github.com/repos/simonw/datasette/issues/1775/reactions\", \"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "draft": null, "state_reason": null} {"id": 1579695809, "node_id": "I_kwDOBm6k_c5eKD7B", "number": 2023, "title": "Error: Invalid setting 'hash_urls' in settings.json in 0.64.1", "user": {"value": 80409402, "label": "mlaparie"}, "state": "closed", "locked": 0, "assignee": null, "milestone": null, "comments": 2, "created_at": "2023-02-10T13:35:01Z", "updated_at": "2023-02-10T15:40:00Z", "closed_at": "2023-02-10T15:39:59Z", "author_association": "NONE", "pull_request": null, "body": "On a Debian machine, using datasette 0.64.1 installed with `pip3`, I am getting a `datasette[114272]: Error: Invalid setting 'hash_urls' in settings.json` in `journalctl -xe`. The same settings work on 0.54.1 on another Debian server.\r\n\r\nThis is my `settings.json`:\r\n\r\n```json\r\n{\r\n \"default_page_size\": 200,\r\n \"max_returned_rows\": 8000,\r\n \"num_sql_threads\": 3,\r\n \"sql_time_limit_ms\": 1000,\r\n \"default_facet_size\": 30,\r\n \"facet_time_limit_ms\": 200,\r\n \"facet_suggest_time_limit_ms\": 50,\r\n \"hash_urls\": false,\r\n \"allow_facet\": true,\r\n \"allow_download\": true,\r\n \"suggest_facets\": true,\r\n \"default_cache_ttl\": 5,\r\n \"default_cache_ttl_hashed\": 31536000,\r\n \"cache_size_kb\": 0,\r\n \"allow_csv_stream\": true,\r\n \"max_csv_mb\": 100,\r\n \"truncate_cells_html\": 2048,\r\n \"force_https_urls\": false,\r\n \"template_debug\": false,\r\n \"base_url\": \"/pclim/db/\"\r\n}\r\n```\r\n\r\nThis looks ok to me. Would you have any ideas?", "repo": {"value": 107914493, "label": "datasette"}, "type": "issue", "active_lock_reason": null, "performed_via_github_app": null, "reactions": "{\"url\": \"https://api.github.com/repos/simonw/datasette/issues/2023/reactions\", \"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "draft": null, "state_reason": "completed"} {"id": 1578609658, "node_id": "I_kwDOBm6k_c5eF6v6", "number": 2022, "title": "Error 500 - not clear the cause", "user": {"value": 1667631, "label": "DavidPratten"}, "state": "closed", "locked": 0, "assignee": null, "milestone": null, "comments": 1, "created_at": "2023-02-09T20:57:17Z", "updated_at": "2023-02-09T21:13:50Z", "closed_at": "2023-02-09T21:13:50Z", "author_association": "NONE", "pull_request": null, "body": "On the database that I have sent via linkedIn, datasette works great, but the following URL gives a 500 error.\r\n\r\nhttp://127.0.0.1:8001/literature/authors_papers?authorId=100550354\r\n\r\nThe cause of the error is not apparent.\r\n\r\nIs this expected behaviour?\r\n\r\nDavid", "repo": {"value": 107914493, "label": "datasette"}, "type": "issue", "active_lock_reason": null, "performed_via_github_app": null, "reactions": "{\"url\": \"https://api.github.com/repos/simonw/datasette/issues/2022/reactions\", \"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "draft": null, "state_reason": "completed"} {"id": 1577548579, "node_id": "I_kwDOBm6k_c5eB3sj", "number": 2021, "title": "Docker images for 1.0 alphas?", "user": {"value": 1563881, "label": "meowcat"}, "state": "open", "locked": 0, "assignee": null, "milestone": null, "comments": 0, "created_at": "2023-02-09T09:35:52Z", "updated_at": "2023-02-09T09:35:52Z", "closed_at": null, "author_association": "NONE", "pull_request": null, "body": "Hi,\r\nwould you consider putting 1.0alpha images on Dockerhub? \r\n\r\n(Also, how usable are the alphas?)", "repo": {"value": 107914493, "label": "datasette"}, "type": "issue", "active_lock_reason": null, "performed_via_github_app": null, "reactions": "{\"url\": \"https://api.github.com/repos/simonw/datasette/issues/2021/reactions\", \"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "draft": null, "state_reason": null} {"id": 1575880841, "node_id": "I_kwDOBm6k_c5d7giJ", "number": 2020, "title": "Documentation refers to \"off\" setting; doesn't seem to work, \"false\" does", "user": {"value": 1350673, "label": "dmick"}, "state": "open", "locked": 0, "assignee": null, "milestone": null, "comments": 0, "created_at": "2023-02-08T10:38:10Z", "updated_at": "2023-02-08T10:38:10Z", "closed_at": null, "author_association": "NONE", "pull_request": null, "body": "https://docs.datasette.io/en/stable/settings.html#suggest-facets, among others, suggests using \"off\" to disable the setting; however, this doesn't appear to work in the JSON config files, where it apparently needs to be a \"JSON boolean\" and have the values \"true\" or \"false\". Perhaps the Python code is more flexible?...but either way, the documentation probably should mention it.", "repo": {"value": 107914493, "label": "datasette"}, "type": "issue", "active_lock_reason": null, "performed_via_github_app": null, "reactions": "{\"url\": \"https://api.github.com/repos/simonw/datasette/issues/2020/reactions\", \"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "draft": null, "state_reason": null} {"id": 1571207083, "node_id": "I_kwDOBm6k_c5dprer", "number": 2016, "title": "Database metadata fields like description are not available in the index page template's context", "user": {"value": 9993, "label": "palewire"}, "state": "open", "locked": 0, "assignee": null, "milestone": {"value": 3268330, "label": "Datasette 1.0"}, "comments": 1, "created_at": "2023-02-05T02:25:53Z", "updated_at": "2023-02-05T22:56:43Z", "closed_at": null, "author_association": "NONE", "pull_request": null, "body": "When looping through `databases` in the index.html template, I'd like to print the description of each database alongside its name. But it appears that isn't passed in from the view, unless I'm missing it. It would be great to have that.", "repo": {"value": 107914493, "label": "datasette"}, "type": "issue", "active_lock_reason": null, "performed_via_github_app": null, "reactions": "{\"url\": \"https://api.github.com/repos/simonw/datasette/issues/2016/reactions\", \"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "draft": null, "state_reason": null} {"id": 1553615704, "node_id": "I_kwDOBm6k_c5cmktY", "number": 2001, "title": "Datasette is not compatible with SQLite's strict quoting compilation option", "user": {"value": 406380, "label": "gwk"}, "state": "open", "locked": 0, "assignee": null, "milestone": null, "comments": 4, "created_at": "2023-01-23T19:10:07Z", "updated_at": "2023-01-25T04:59:58Z", "closed_at": null, "author_association": "NONE", "pull_request": null, "body": "I have linked Python3.11 on macOS against recent SQLite that was compiled using `-DSQLITE_DQS=0`. This option disables interpretation of double-quoted identifiers as string literals, described in the SQLite docs as a \"MySQL 3.x misfeature\". See https://www.sqlite.org/quirks.html#dblquote for background.\r\n\r\nDatasette uses the double-quote syntax in a number of key places, and is thus completely broken in this environment.\r\n\r\nMy experience was to `pip install datasette`, then run `datasette serve -I my-data.db`. When I visit `http://127.0.0.1:8001` I get a 500 response.\r\n\r\nThe error: `sqlite3.OperationalError: no such column: geometry_columns`\r\n\r\nThe responsible SQL: `'select 1 from sqlite_master where tbl_name = \"geometry_columns\"'`\r\n\r\nI then installed datasette from GitHub master in development mode and changed the offending SQL to use correct quotes: `\"select 1 from sqlite_master where tbl_name = 'geometry_columns'\"`.\r\n\r\nWith this change, I get a little further, but have the same problem with the first table name in my database (in my case, \"Meta\"):\r\n```\r\nOperationalError: no such column: Meta\r\nTraceback (most recent call last):\r\n File \"/Users/gwk/external/datasette/datasette/app.py\", line 1522, in route_path\r\n response = await view(request, send)\r\n ^^^^^^^^^^^^^^^^^^^^^^^^^\r\n File \"/Users/gwk/external/datasette/datasette/views/base.py\", line 151, in view\r\n return await self.dispatch_request(request)\r\n ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\r\n File \"/Users/gwk/external/datasette/datasette/views/base.py\", line 105, in dispatch_request\r\n response = await handler(request)\r\n ^^^^^^^^^^^^^^^^^^^^^^\r\n File \"/Users/gwk/external/datasette/datasette/views/index.py\", line 70, in get\r\n \"fts_table\": await db.fts_table(table),\r\n ^^^^^^^^^^^^^^^^^^^^^^^^^\r\n File \"/Users/gwk/external/datasette/datasette/database.py\", line 363, in fts_table\r\n return await self.execute_fn(lambda conn: detect_fts(conn, table))\r\n ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\r\n File \"/Users/gwk/external/datasette/datasette/database.py\", line 213, in execute_fn\r\n return await asyncio.get_event_loop().run_in_executor(\r\n ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\r\n File \"/usr/local/py/Python.framework/Versions/3.11/lib/python3.11/concurrent/futures/thread.py\", line 58, in run\r\n result = self.fn(*self.args, **self.kwargs)\r\n ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\r\n File \"/Users/gwk/external/datasette/datasette/database.py\", line 211, in in_thread\r\n return fn(conn)\r\n ^^^^^^^^\r\n File \"/Users/gwk/external/datasette/datasette/database.py\", line 363, in \r\n return await self.execute_fn(lambda conn: detect_fts(conn, table))\r\n ^^^^^^^^^^^^^^^^^^^^^^^\r\n File \"/Users/gwk/external/datasette/datasette/utils/__init__.py\", line 588, in detect_fts\r\n rows = conn.execute(detect_fts_sql(table)).fetchall()\r\n ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\r\nsqlite3.OperationalError: no such column: Meta\r\nINFO: 127.0.0.1:50258 - \"GET / HTTP/1.1\" 500 Internal Server Error\r\n```\r\n\r\nI will try to continue playing with this, but I also hope that the datasette developers will enable this mode in a test environment as I am unlikely to be able to exercise all of the SQL in the codebase, or make a pull request very soon.\r\n\r\nNote that the DQS setting compile-time option can be overridden at runtime with calls to the C API:\r\n```\r\nsqlite3_db_config(db, SQLITE_DBCONFIG_DQS_DDL, 0, (void*)0);\r\nsqlite3_db_config(db, SQLITE_DBCONFIG_DQS_DML, 0, (void*)0);\r\n```\r\n\r\nAs far as I can tell, `sqlite3_db_config` is not exposed in Python, but perhaps we could figure out how to invoke it using `ctypes`.\r\n", "repo": {"value": 107914493, "label": "datasette"}, "type": "issue", "active_lock_reason": null, "performed_via_github_app": null, "reactions": "{\"url\": \"https://api.github.com/repos/simonw/datasette/issues/2001/reactions\", \"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "draft": null, "state_reason": null} {"id": 1536851861, "node_id": "I_kwDOBm6k_c5bmn-V", "number": 1994, "title": "Stuck on loading screen", "user": {"value": 10913053, "label": "jackhagley"}, "state": "open", "locked": 0, "assignee": null, "milestone": null, "comments": 1, "created_at": "2023-01-17T18:33:49Z", "updated_at": "2023-01-23T08:21:08Z", "closed_at": null, "author_association": "NONE", "pull_request": null, "body": "Can\u2019t actually open it!\r\n\r\nDownloaded today from the releases tab\r\n\r\nRunning macOS13.1 \r\n\r\n```\r\nbin/python3.9 --version\r\nPython 3.9.6\r\nTook 83ms\r\nbin/python3.9 --version\r\nPython 3.9.6\r\nTook 113ms\r\nbin/pip install datasette>=0.59 datasette-app-support>=0.11.6 datasette-vega>=0.6.2 datasette-cluster-map>=0.17.1 datasette-pretty-json>=0.2.1 datasette-edit-schema>=0.4 datasette-configure-fts>=1.1 datasette-leaflet>=0.2.2 --disable-pip-version-check\r\nRequirement already satisfied: datasette>=0.59 in lib/python3.9/site-packages (0.63)\r\nRequirement already satisfied: datasette-app-support>=0.11.6 in lib/python3.9/site-packages (0.11.6)\r\nRequirement already satisfied: datasette-vega>=0.6.2 in lib/python3.9/site-packages (0.6.2)\r\nRequirement already satisfied: datasette-cluster-map>=0.17.1 in lib/python3.9/site-packages (0.17.2)\r\nRequirement already satisfied: datasette-pretty-json>=0.2.1 in lib/python3.9/site-packages (0.2.2)\r\nRequirement already satisfied: datasette-edit-schema>=0.4 in lib/python3.9/site-packages (0.5.1)\r\nRequirement already satisfied: datasette-configure-fts>=1.1 in lib/python3.9/site-packages (1.1)\r\nRequirement already satisfied: datasette-leaflet>=0.2.2 in lib/python3.9/site-packages (0.2.2)\r\nRequirement already satisfied: click>=7.1.1 in lib/python3.9/site-packages (from datasette>=0.59) (8.1.3)\r\nRequirement already satisfied: hupper>=1.9 in lib/python3.9/site-packages (from datasette>=0.59) (1.10.3)\r\nRequirement already satisfied: pint>=0.9 in lib/python3.9/site-packages (from datasette>=0.59) (0.20.1)\r\nRequirement already satisfied: PyYAML>=5.3 in lib/python3.9/site-packages (from datasette>=0.59) (6.0)\r\nRequirement already satisfied: httpx>=0.20 in lib/python3.9/site-packages (from datasette>=0.59) (0.23.0)\r\nRequirement already satisfied: aiofiles>=0.4 in lib/python3.9/site-packages (from datasette>=0.59) (22.1.0)\r\nRequirement already satisfied: asgi-csrf>=0.9 in lib/python3.9/site-packages (from datasette>=0.59) (0.9)\r\nRequirement already satisfied: asgiref>=3.2.10 in lib/python3.9/site-packages (from datasette>=0.59) (3.5.2)\r\nRequirement already satisfied: uvicorn>=0.11 in lib/python3.9/site-packages (from datasette>=0.59) (0.19.0)\r\nRequirement already satisfied: itsdangerous>=1.1 in lib/python3.9/site-packages (from datasette>=0.59) (2.1.2)\r\nRequirement already satisfied: click-default-group-wheel>=1.2.2 in lib/python3.9/site-packages (from datasette>=0.59) (1.2.2)\r\nRequirement already satisfied: janus>=0.6.2 in lib/python3.9/site-packages (from datasette>=0.59) (1.0.0)\r\nRequirement already satisfied: pluggy>=1.0 in lib/python3.9/site-packages (from datasette>=0.59) (1.0.0)\r\nRequirement already satisfied: Jinja2>=2.10.3 in lib/python3.9/site-packages (from datasette>=0.59) (3.1.2)\r\nRequirement already satisfied: mergedeep>=1.1.1 in lib/python3.9/site-packages (from datasette>=0.59) (1.3.4)\r\nRequirement already satisfied: sqlite-utils in lib/python3.9/site-packages (from datasette-app-support>=0.11.6) (3.30)\r\nRequirement already satisfied: packaging in lib/python3.9/site-packages (from datasette-app-support>=0.11.6) (21.3)\r\nRequirement already satisfied: python-multipart in lib/python3.9/site-packages (from asgi-csrf>=0.9->datasette>=0.59) (0.0.5)\r\nRequirement already satisfied: httpcore<0.16.0,>=0.15.0 in lib/python3.9/site-packages (from httpx>=0.20->datasette>=0.59) (0.15.0)\r\nRequirement already satisfied: certifi in lib/python3.9/site-packages (from httpx>=0.20->datasette>=0.59) (2022.9.24)\r\nRequirement already satisfied: rfc3986[idna2008]<2,>=1.3 in lib/python3.9/site-packages (from httpx>=0.20->datasette>=0.59) (1.5.0)\r\nRequirement already satisfied: sniffio in lib/python3.9/site-packages (from httpx>=0.20->datasette>=0.59) (1.3.0)\r\nRequirement already satisfied: h11<0.13,>=0.11 in lib/python3.9/site-packages (from httpcore<0.16.0,>=0.15.0->httpx>=0.20->datasette>=0.59) (0.12.0)\r\nRequirement already satisfied: anyio==3.* in lib/python3.9/site-packages (from httpcore<0.16.0,>=0.15.0->httpx>=0.20->datasette>=0.59) (3.6.2)\r\nRequirement already satisfied: idna>=2.8 in lib/python3.9/site-packages (from anyio==3.*->httpcore<0.16.0,>=0.15.0->httpx>=0.20->datasette>=0.59) (3.4)\r\nRequirement already satisfied: typing-extensions>=3.7.4.3 in lib/python3.9/site-packages (from janus>=0.6.2->datasette>=0.59) (4.4.0)\r\nRequirement already satisfied: MarkupSafe>=2.0 in lib/python3.9/site-packages (from Jinja2>=2.10.3->datasette>=0.59) (2.1.1)\r\nRequirement already satisfied: tabulate in lib/python3.9/site-packages (from sqlite-utils->datasette-app-support>=0.11.6) (0.9.0)\r\nRequirement already satisfied: python-dateutil in lib/python3.9/site-packages (from sqlite-utils->datasette-app-support>=0.11.6) (2.8.2)\r\nRequirement already satisfied: sqlite-fts4 in lib/python3.9/site-packages (from sqlite-utils->datasette-app-support>=0.11.6) (1.0.3)\r\nRequirement already satisfied: pyparsing!=3.0.5,>=2.0.2 in lib/python3.9/site-packages (from packaging->datasette-app-support>=0.11.6) (3.0.9)\r\nRequirement already satisfied: six>=1.5 in lib/python3.9/site-packages (from python-dateutil->sqlite-utils->datasette-app-support>=0.11.6) (1.16.0)\r\nTook 784ms\r\n```\r\nSTUCK", "repo": {"value": 107914493, "label": "datasette"}, "type": "issue", "active_lock_reason": null, "performed_via_github_app": null, "reactions": "{\"url\": \"https://api.github.com/repos/simonw/datasette/issues/1994/reactions\", \"total_count\": 1, \"+1\": 1, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "draft": null, "state_reason": null} {"id": 1533673397, "node_id": "I_kwDOBm6k_c5baf-1", "number": 1991, "title": "fts5 tables are not auto-detected and hidden", "user": {"value": 83819, "label": "keturn"}, "state": "open", "locked": 0, "assignee": null, "milestone": null, "comments": 0, "created_at": "2023-01-15T06:00:42Z", "updated_at": "2023-01-20T04:54:24Z", "closed_at": null, "author_association": "NONE", "pull_request": null, "body": "I set up a [Datasette instance](https://huggingface.co/spaces/Sygil/INE-dataset-explorer/tree/main) and was following the docs on full-text search.\r\n\r\nWhen I used fts4, datasette automatically hid the FTS tables and added the FTS search box where appropriate, but when I changed to fts5 it no longer does either.\r\n\r\nIf I [manually set](https://huggingface.co/spaces/keturn/INED-datasette/blob/main/metadata.json#L9) `fts_table` for a view, then search does work as expected.\r\n\r\nMy table and view creation code looks like this:\r\n```py\r\nconnection.execute(\"\"\"CREATE TABLE IF NOT EXISTS\r\n captions(image_key text PRIMARY KEY, caption text NOT NULL)\r\n\"\"\")\r\n\u00a0\r\nconnection.execute(\"\"\"CREATE VIRTUAL TABLE\r\n captions_fts USING\r\n fts5(caption, image_key UNINDEXED, content=captions)\r\n\"\"\")\r\n```", "repo": {"value": 107914493, "label": "datasette"}, "type": "issue", "active_lock_reason": null, "performed_via_github_app": null, "reactions": "{\"url\": \"https://api.github.com/repos/simonw/datasette/issues/1991/reactions\", \"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "draft": null, "state_reason": null} {"id": 1538197093, "node_id": "I_kwDOBm6k_c5brwZl", "number": 1995, "title": "foreign_keys error 500", "user": {"value": 137183, "label": "jonschoning"}, "state": "open", "locked": 0, "assignee": null, "milestone": null, "comments": 0, "created_at": "2023-01-18T15:27:36Z", "updated_at": "2023-01-18T16:44:01Z", "closed_at": null, "author_association": "NONE", "pull_request": null, "body": "**Error 500 expected string or bytes-like object**\r\n\r\n[espial-new.sqlite3.zip](https://github.com/simonw/datasette/files/10447965/espial-new.sqlite3.zip)\r\n\r\nrun `datasette espial-new.sqlite3` & click on any table other than `User`\r\n\r\n```\r\n/home/jon/.local/lib/python3.10/site-packages/datasette/app.py:814 in \u2502\r\n\u2502 expand_foreign_keys \u2502\r\n\u2502 \u2502\r\n\u2502 811 \u2502 \u2502 \u2502 from {other_table} \u2502\r\n\u2502 812 \u2502 \u2502 \u2502 where {other_column} in ({placeholders}) \u2502\r\n\u2502 813 \u2502 \u2502 \"\"\".format( \u2502\r\n\u2502 \u2771 814 \u2502 \u2502 \u2502 other_column=escape_sqlite(fk[\"other_column\"]), \u2502\r\n\u2502 815 \u2502 \u2502 \u2502 label_column=escape_sqlite(label_column), \u2502\r\n\u2502 816 \u2502 \u2502 \u2502 other_table=escape_sqlite(fk[\"other_table\"]), \u2502\r\n\u2502 817 \u2502 \u2502 \u2502 placeholders=\", \".join([\"?\"] * len(set(values))), \u2502\r\n\u2502 \u2502\r\n\u2502 \u256d\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 locals \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u256e \u2502\r\n\u2502 \u2502 column = 'user_id' \u2502 \u2502\r\n\u2502 \u2502 database = 'espial-new' \u2502 \u2502\r\n\u2502 \u2502 db = \u2502 \u2502\r\n\u2502 \u2502 fk = { \u2502 \u2502\r\n\u2502 \u2502 \u2502 'column': 'user_id', \u2502 \u2502\r\n\u2502 \u2502 \u2502 'other_table': 'user', \u2502 \u2502\r\n\u2502 \u2502 \u2502 'other_column': None \u2502 \u2502\r\n\u2502 \u2502 } \u2502 \u2502\r\n\u2502 \u2502 foreign_keys = [ \u2502 \u2502\r\n\u2502 \u2502 \u2502 { \u2502 \u2502\r\n\u2502 \u2502 \u2502 \u2502 'column': 'user_id', \u2502 \u2502\r\n\u2502 \u2502 \u2502 \u2502 'other_table': 'user', \u2502 \u2502\r\n\u2502 \u2502 \u2502 \u2502 'other_column': None \u2502 \u2502\r\n\u2502 \u2502 \u2502 } \u2502 \u2502\r\n\u2502 \u2502 ] \u2502 \u2502\r\n\u2502 \u2502 label_column = 'name' \u2502 \u2502\r\n\u2502 \u2502 labeled_fks = {} \u2502 \u2502\r\n\u2502 \u2502 self = \u2502 \u2502\r\n\u2502 \u2502 table = 'bookmark' \u2502 \u2502\r\n\u2502 \u2502 values = [] \u2502 \u2502\r\n\u2502 \u2570\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u256f \u2502\r\n\u2502 \u2502\r\n\u2502 /home/jon/.local/lib/python3.10/site-packages/datasette/utils/__init__.py:346 \u2502\r\n\u2502 in escape_sqlite \u2502\r\n\u2502 \u2502\r\n\u2502 343 \u2502\r\n\u2502 344 \u2502\r\n\u2502 345 def escape_sqlite(s): \u2502\r\n\u2502 \u2771 346 \u2502 if _boring_keyword_re.match(s) and (s.lower() not in reserved_words) \u2502\r\n\u2502 347 \u2502 \u2502 return s \u2502\r\n\u2502 348 \u2502 else: \u2502\r\n\u2502 349 \u2502 \u2502 return f\"[{s}]\" \u2502\r\n\u2502 \u2502\r\n\u2502 \u256d\u2500 locals \u2500\u256e \u2502\r\n\u2502 \u2502 s = None \u2502 \u2502\r\n\u2502 \u2570\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u256f \u2502\r\n\u2570\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u256f\r\nTypeError: expected string or bytes-like object\r\nTraceback (most recent call last):\r\n File \"/home/jon/.local/lib/python3.10/site-packages/datasette/app.py\", line 1354, in route_path\r\n response = await view(request, send)\r\n File \"/home/jon/.local/lib/python3.10/site-packages/datasette/views/base.py\", line 134, in view\r\n return await self.dispatch_request(request)\r\n File \"/home/jon/.local/lib/python3.10/site-packages/datasette/views/base.py\", line 91, in dispatch_request\r\n return await handler(request)\r\n File \"/home/jon/.local/lib/python3.10/site-packages/datasette/views/base.py\", line 361, in get\r\n response_or_template_contexts = await self.data(request, **data_kwargs)\r\n File \"/home/jon/.local/lib/python3.10/site-packages/datasette/views/table.py\", line 158, in data\r\n return await self._data_traced(request, default_labels, _next, _size)\r\n File \"/home/jon/.local/lib/python3.10/site-packages/datasette/views/table.py\", line 603, in _data_traced\r\n await self.ds.expand_foreign_keys(\r\n File \"/home/jon/.local/lib/python3.10/site-packages/datasette/app.py\", line 814, in expand_foreign_keys\r\n other_column=escape_sqlite(fk[\"other_column\"]),\r\n File \"/home/jon/.local/lib/python3.10/site-packages/datasette/utils/__init__.py\", line 346, in escape_sqlite\r\n if _boring_keyword_re.match(s) and (s.lower() not in reserved_words):\r\nTypeError: expected string or bytes-like object\r\nINFO: 127.0.0.1:38574 - \"GET /espial-new/bookmark HTTP/1.1\" 500 Internal Server Error\r\nINFO: 127.0.0.1:38574 - \"GET /-/static/app.css?d59929 HTTP/1.1\" 200 OK\r\n```\r\n\r\nSchema:\r\n```\r\nCREATE TABLE IF NOT EXISTS \"user\"\r\n (\r\n \"id\" INTEGER PRIMARY KEY,\r\n \"name\" VARCHAR NOT NULL,\r\n \"password_hash\" VARCHAR NOT NULL,\r\n \"api_token\" VARCHAR NULL,\r\n \"private_default\" BOOLEAN NOT NULL,\r\n \"archive_default\" BOOLEAN NOT NULL,\r\n \"privacy_lock\" BOOLEAN NOT NULL,\r\n CONSTRAINT \"unique_user_name\" UNIQUE (\"name\")\r\n );\r\n\r\nCREATE TABLE IF NOT EXISTS \"bookmark\"\r\n (\r\n \"id\" INTEGER PRIMARY KEY,\r\n \"user_id\" INTEGER NOT NULL REFERENCES \"user\" ON DELETE RESTRICT ON UPDATE RESTRICT,\r\n \"slug\" VARCHAR NOT NULL DEFAULT (Lower(Hex(Randomblob(6)))),\r\n \"href\" VARCHAR NOT NULL,\r\n \"description\" VARCHAR NOT NULL,\r\n \"extended\" VARCHAR NOT NULL,\r\n \"time\" TIMESTAMP NOT NULL,\r\n \"shared\" BOOLEAN NOT NULL,\r\n \"to_read\" BOOLEAN NOT NULL,\r\n \"selected\" BOOLEAN NOT NULL,\r\n \"archive_href\" VARCHAR NULL,\r\n CONSTRAINT \"unique_user_href\" UNIQUE (\"user_id\", \"href\"),\r\n CONSTRAINT \"unique_user_slug\" UNIQUE (\"user_id\", \"slug\")\r\n );\r\n\r\nCREATE TABLE IF NOT EXISTS \"bookmark_tag\"\r\n (\r\n \"id\" INTEGER PRIMARY KEY,\r\n \"user_id\" INTEGER NOT NULL REFERENCES \"user\" ON DELETE RESTRICT ON UPDATE RESTRICT,\r\n \"tag\" VARCHAR NOT NULL,\r\n \"bookmark_id\" INTEGER NOT NULL REFERENCES \"bookmark\" ON DELETE RESTRICT ON UPDATE RESTRICT,\r\n \"seq\" INTEGER NOT NULL,\r\n CONSTRAINT \"unique_user_tag_bookmark_id\" UNIQUE (\"user_id\", \"tag\", \"bookmark_id\"),\r\n CONSTRAINT \"unique_user_bookmark_id_tag_seq\" UNIQUE (\"user_id\", \"bookmark_id\", \"tag\", \"seq\")\r\n );\r\n\r\nCREATE TABLE IF NOT EXISTS \"note\"\r\n (\r\n \"id\" INTEGER PRIMARY KEY,\r\n \"user_id\" INTEGER NOT NULL REFERENCES \"user\" ON DELETE RESTRICT ON UPDATE RESTRICT,\r\n \"slug\" VARCHAR NOT NULL DEFAULT (Lower(Hex(Randomblob(10)))),\r\n \"length\" INTEGER NOT NULL,\r\n \"title\" VARCHAR NOT NULL,\r\n \"text\" VARCHAR NOT NULL,\r\n \"is_markdown\" BOOLEAN NOT NULL,\r\n \"shared\" BOOLEAN NOT NULL DEFAULT false,\r\n \"created\" TIMESTAMP NOT NULL,\r\n \"updated\" TIMESTAMP NOT NULL\r\n );\r\nCREATE INDEX idx_bookmark_time ON bookmark (user_id, time DESC);\r\nCREATE INDEX idx_bookmark_tag_bookmark_id ON bookmark_tag (bookmark_id, id, tag, seq);\r\nCREATE INDEX idx_note_user_created ON note (user_id, created DESC); \r\n```\r\n", "repo": {"value": 107914493, "label": "datasette"}, "type": "issue", "active_lock_reason": null, "performed_via_github_app": null, "reactions": "{\"url\": \"https://api.github.com/repos/simonw/datasette/issues/1995/reactions\", \"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "draft": null, "state_reason": null} {"id": 1532000914, "node_id": "I_kwDOBm6k_c5bUHqS", "number": 1990, "title": "Suggestion: Highlight error messages ('These facets timed out')", "user": {"value": 116795, "label": "pax"}, "state": "open", "locked": 0, "assignee": null, "milestone": null, "comments": 0, "created_at": "2023-01-13T09:40:58Z", "updated_at": "2023-01-13T09:40:58Z", "closed_at": null, "author_association": "NONE", "pull_request": null, "body": "I had trouble figuring out why faceting didn't work in some instances, it took a while before I noticed the _These facets timed out_ notice. \r\n\r\nIt might help if that would be highlighted, or fading out highlight - if one might think it would be too visually disturbing.", "repo": {"value": 107914493, "label": "datasette"}, "type": "issue", "active_lock_reason": null, "performed_via_github_app": null, "reactions": "{\"url\": \"https://api.github.com/repos/simonw/datasette/issues/1990/reactions\", \"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "draft": null, "state_reason": null} {"id": 1501900064, "node_id": "I_kwDOBm6k_c5ZhS0g", "number": 1966, "title": "Broken link to live demo in Getting started docs", "user": {"value": 7551922, "label": "lbellomo"}, "state": "closed", "locked": 0, "assignee": null, "milestone": null, "comments": 1, "created_at": "2022-12-18T13:17:00Z", "updated_at": "2022-12-31T19:15:19Z", "closed_at": "2022-12-31T19:15:10Z", "author_association": "NONE", "pull_request": null, "body": "The link in [Play with a live demo in Getting started](https://github.com/simonw/datasette/blob/main/docs/getting_started.rst#play-with-a-live-demo) to [https://fivethirtyeight.datasettes.com/fivethirtyeight](https://fivethirtyeight.datasettes.com/fivethirtyeight) is broken and the datasette is no longer working (maybe due to the end of the free tier).", "repo": {"value": 107914493, "label": "datasette"}, "type": "issue", "active_lock_reason": null, "performed_via_github_app": null, "reactions": "{\"url\": \"https://api.github.com/repos/simonw/datasette/issues/1966/reactions\", \"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "draft": null, "state_reason": "completed"} {"id": 1504352503, "node_id": "I_kwDOBm6k_c5Zqpj3", "number": 1968, "title": "Allow to hide some queries in metadata.yml", "user": {"value": 562352, "label": "CharlesNepote"}, "state": "open", "locked": 0, "assignee": null, "milestone": null, "comments": 0, "created_at": "2022-12-20T10:45:41Z", "updated_at": "2022-12-20T10:45:41Z", "closed_at": null, "author_association": "NONE", "pull_request": null, "body": "By default all queries are displayed.\r\n\r\nBut there are many cases where it would be interesting to hide the queries by default:\r\n* the website is targeting non-tech people\r\n* the query is veeeeeery long ([eg.](https://mirabelle.openfoodfacts.org/products/energy_calculator))\r\n* reading the query is not important for the users, they only want to see the result\r\n\r\nOf course, the user still could have the option to see the query.\r\n\r\nIt could be an option in the metadata file:\r\n```yml\r\ndatabases:\r\n awesome_db:\r\n tables:\r\n products:\r\n hide_sql: true\r\n queries:\r\n great_query:\r\n hide_sql: true\r\n sql: select * from products where code = :barcode\r\n```\r\n\r\nThe priority could be:\r\n* no option in the metadata and nothing in the URL: query displayed\r\n* hide_sql in the metadata and nothing in the URL: query displayed as asked in the metadata\r\n* hide_sql in the metadata and &_hide_sql= in the URL: query as asked in the URL\r\n\r\nSee also: #1824\r\n", "repo": {"value": 107914493, "label": "datasette"}, "type": "issue", "active_lock_reason": null, "performed_via_github_app": null, "reactions": "{\"url\": \"https://api.github.com/repos/simonw/datasette/issues/1968/reactions\", \"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "draft": null, "state_reason": null} {"id": 1496652622, "node_id": "I_kwDOBm6k_c5ZNRtO", "number": 1955, "title": "invoke_startup() is not run in some conditions, e.g. gunicorn/uvicorn workers, breaking lots of things", "user": {"value": 32839123, "label": "Rik-de-Kort"}, "state": "closed", "locked": 0, "assignee": null, "milestone": null, "comments": 36, "created_at": "2022-12-14T13:39:56Z", "updated_at": "2022-12-19T04:34:16Z", "closed_at": "2022-12-18T02:45:18Z", "author_association": "NONE", "pull_request": null, "body": "In the past (pre-september 14, #1809) I had a running deployment of Datasette on Azure WebApps by emulating the call in cli.py to Gunicorn: `gunicorn -w 2 -k uvicorn.workers.UvicornWorker app:app`.\r\n\r\nMy most recent deployment, however, fails loudly by shouting that `Datasette.invoke_startup()` was not called. It does not seem to be possible to call `invoke_startup` when running using a uvicorn command directly like this (I've reproduced this locally using `uvicorn`). Two candidates that I have tried:\r\n* Uvicorn has a `--factory` option, but the app factory has to be synchronous, so no `await invoke_startup` there\r\n* `asyncio.get_event_loop().run_until_complete` is also not an option because `uvicorn` already has the event loop running.\r\n\r\nOne additional option is:\r\n* Use Gunicorn's [server hooks](https://docs.gunicorn.org/en/stable/settings.html#server-hooks) to call `invoke_startup`. These are also synchronous, but I might be able to get ahead of the event loop starting here.\r\n\r\nIn my current deployment setup, it does not appear to be possible to use `datasette serve` directly, so I'm stuck either\r\n* Trying to rework my complete deployment setup, for instance, using Azure functions as described [here](https://github.com/simonw/azure-functions-datasette))\r\n* Or dig into the ASGI spec and write a wrapper for the sole purpose of launching Datasette using a direct Uvicorn invocation.\r\n\r\nQuestions for the maintainers:\r\n* Is this intended behaviour/will not support/etc.? If so, I'd be happy to add a PR with a couple lines in the documentation.\r\n* if this is not intended behaviour, what is a good way to fix it? I could have a go at the ASGI spec thing (I think the Azure Functions thing is related) and provide a PR with the wrapper here, but I'm all ears!\r\n\r\nAlmost forgot, minimal reproducer:\r\n```python\r\nfrom datasette import Datasette\r\n\r\nds = Datasette(files=['./global-power-plants.db'])]\r\napp = ds.app()\r\n```\r\n\r\nSave as app.py in the same folder as global-power-plants.db, and then try running\r\n`uvicorn app:app`.\r\n\r\nOpening the resulting Datasette instance in the browser will show the error message.", "repo": {"value": 107914493, "label": "datasette"}, "type": "issue", "active_lock_reason": null, "performed_via_github_app": null, "reactions": "{\"url\": \"https://api.github.com/repos/simonw/datasette/issues/1955/reactions\", \"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "draft": null, "state_reason": "completed"} {"id": 1306984363, "node_id": "I_kwDOBm6k_c5N5v-r", "number": 1771, "title": "minor a11y: