{"html_url": "https://github.com/simonw/datasette/issues/2079#issuecomment-1563565407", "issue_url": "https://api.github.com/repos/simonw/datasette/issues/2079", "id": 1563565407, "node_id": "IC_kwDOBm6k_c5dMh1f", "user": {"value": 9599, "label": "simonw"}, "created_at": "2023-05-25T22:09:53Z", "updated_at": "2023-05-25T22:09:53Z", "author_association": "OWNER", "body": "Updated docs: https://docs.datasette.io/en/latest/json_api.html#enabling-cors", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 1726531350, "label": "Datasette should serve Access-Control-Max-Age"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/datasette/issues/2079#issuecomment-1563563438", "issue_url": "https://api.github.com/repos/simonw/datasette/issues/2079", "id": 1563563438, "node_id": "IC_kwDOBm6k_c5dMhWu", "user": {"value": 9599, "label": "simonw"}, "created_at": "2023-05-25T22:08:28Z", "updated_at": "2023-05-25T22:08:28Z", "author_association": "OWNER", "body": "I ran this on https://www.example.com/ twice using the console:\r\n```javascript\r\nfetch(\r\n `https://latest.datasette.io/ephemeral/foo/1/-/update`,\r\n {\r\n method: \"POST\",\r\n mode: \"cors\",\r\n headers: {\r\n Authorization: `Bearer tok`,\r\n \"Content-Type\": \"application/json\",\r\n },\r\n body: JSON.stringify({update: {blah: 1}}),\r\n }\r\n)\r\n .then((r) => r.json())\r\n .then((data) => {\r\n console.log(data);\r\n });\r\n```\r\nAnd got this in the network pane:\r\n\r\n\"image\"\r\n", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 1726531350, "label": "Datasette should serve Access-Control-Max-Age"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/datasette/issues/2079#issuecomment-1563558915", "issue_url": "https://api.github.com/repos/simonw/datasette/issues/2079", "id": 1563558915, "node_id": "IC_kwDOBm6k_c5dMgQD", "user": {"value": 9599, "label": "simonw"}, "created_at": "2023-05-25T22:04:41Z", "updated_at": "2023-05-25T22:04:41Z", "author_association": "OWNER", "body": "I'm going with 3600 for 1 hour instead of 600 for 10 minutes.", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 1726531350, "label": "Datasette should serve Access-Control-Max-Age"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/datasette/issues/2079#issuecomment-1563547097", "issue_url": "https://api.github.com/repos/simonw/datasette/issues/2079", "id": 1563547097, "node_id": "IC_kwDOBm6k_c5dMdXZ", "user": {"value": 9599, "label": "simonw"}, "created_at": "2023-05-25T21:51:38Z", "updated_at": "2023-05-25T21:51:38Z", "author_association": "OWNER", "body": "Also need to update this documentation:\r\n\r\nhttps://github.com/simonw/datasette/blob/9584879534ff0556e04e4c420262972884cac87b/docs/json_api.rst?plain=1#L453-L465\r\n\r\nOr maybe make that automated via `cog`.", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 1726531350, "label": "Datasette should serve Access-Control-Max-Age"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/datasette/issues/2078#issuecomment-1563522011", "issue_url": "https://api.github.com/repos/simonw/datasette/issues/2078", "id": 1563522011, "node_id": "IC_kwDOBm6k_c5dMXPb", "user": {"value": 9599, "label": "simonw"}, "created_at": "2023-05-25T21:22:30Z", "updated_at": "2023-05-25T21:22:30Z", "author_association": "OWNER", "body": "This is bad:\r\n```python\r\n async def __call__(self, request, datasette):\r\n try:\r\n handler = getattr(self, request.method.lower())\r\n return await handler(request, datasette)\r\n except AttributeError:\r\n return await self.method_not_allowed(request)\r\n```\r\nBecause it hides any `AttributeError` exceptions that might occur in the view code.", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 1726236847, "label": "Resolve the difference between `wrap_view()` and `BaseView`"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/datasette/issues/2078#issuecomment-1563511171", "issue_url": "https://api.github.com/repos/simonw/datasette/issues/2078", "id": 1563511171, "node_id": "IC_kwDOBm6k_c5dMUmD", "user": {"value": 9599, "label": "simonw"}, "created_at": "2023-05-25T21:11:20Z", "updated_at": "2023-05-25T21:13:05Z", "author_association": "OWNER", "body": "I'm going to call this `VerbView` for the moment. Might even rename it to `View` later.", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 1726236847, "label": "Resolve the difference between `wrap_view()` and `BaseView`"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/datasette/issues/2078#issuecomment-1563498048", "issue_url": "https://api.github.com/repos/simonw/datasette/issues/2078", "id": 1563498048, "node_id": "IC_kwDOBm6k_c5dMRZA", "user": {"value": 9599, "label": "simonw"}, "created_at": "2023-05-25T20:57:52Z", "updated_at": "2023-05-25T20:58:13Z", "author_association": "OWNER", "body": "Here's a new `BaseView` class that automatically populates `OPTIONS` based on available methods:\r\n```python\r\nclass BaseView:\r\n async def head(self, *args, **kwargs):\r\n try:\r\n response = await self.get(*args, **kwargs)\r\n response.body = b\"\"\r\n return response\r\n except AttributeError:\r\n raise\r\n\r\n async def method_not_allowed(self, request):\r\n if (\r\n request.path.endswith(\".json\")\r\n or request.headers.get(\"content-type\") == \"application/json\"\r\n ):\r\n response = Response.json(\r\n {\"ok\": False, \"error\": \"Method not allowed\"}, status=405\r\n )\r\n else:\r\n response = Response.text(\"Method not allowed\", status=405)\r\n return response\r\n\r\n async def options(self, request, *args, **kwargs):\r\n response = Response.text(\"ok\")\r\n response.headers[\"allow\"] = \", \".join(\r\n method.upper()\r\n for method in (\"head\", \"get\", \"post\", \"put\", \"patch\", \"delete\")\r\n if hasattr(self, method)\r\n )\r\n return response\r\n\r\n async def __call__(self, request, datasette):\r\n try:\r\n handler = getattr(self, request.method.lower())\r\n return await handler(request, datasette)\r\n except AttributeError:\r\n return await self.method_not_allowed(request)\r\n\r\n\r\nclass DemoView(BaseView):\r\n async def get(self, datasette, request):\r\n return Response.text(\"Hello there! {} - {}\".format(datasette, request))\r\n\r\n post = get\r\n```", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 1726236847, "label": "Resolve the difference between `wrap_view()` and `BaseView`"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/datasette/issues/2078#issuecomment-1563488929", "issue_url": "https://api.github.com/repos/simonw/datasette/issues/2078", "id": 1563488929, "node_id": "IC_kwDOBm6k_c5dMPKh", "user": {"value": 9599, "label": "simonw"}, "created_at": "2023-05-25T20:48:12Z", "updated_at": "2023-05-25T20:48:39Z", "author_association": "OWNER", "body": "Actually no need for that extra level of parameter detection: `BaseView.__call__` should _always_ take `datasette, request` - `scope` and `receive` are both available on `request`, and `send` is only needed if you're not planning on returning a `Response` object.\r\n\r\nSo the `get` and `post` and suchlike methods should take `datasette` and `request` too.", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 1726236847, "label": "Resolve the difference between `wrap_view()` and `BaseView`"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/datasette/issues/2078#issuecomment-1563444296", "issue_url": "https://api.github.com/repos/simonw/datasette/issues/2078", "id": 1563444296, "node_id": "IC_kwDOBm6k_c5dMERI", "user": {"value": 9599, "label": "simonw"}, "created_at": "2023-05-25T20:06:08Z", "updated_at": "2023-05-25T20:06:08Z", "author_association": "OWNER", "body": "This prototype seems to work well:\r\n\r\n```diff\r\ndiff --git a/datasette/app.py b/datasette/app.py\r\nindex d7dace67..ed0edf28 100644\r\n--- a/datasette/app.py\r\n+++ b/datasette/app.py\r\n@@ -17,6 +17,7 @@ import secrets\r\n import sys\r\n import threading\r\n import time\r\n+import types\r\n import urllib.parse\r\n from concurrent import futures\r\n from pathlib import Path\r\n@@ -1266,6 +1267,8 @@ class Datasette:\r\n # TODO: /favicon.ico and /-/static/ deserve far-future cache expires\r\n add_route(favicon, \"/favicon.ico\")\r\n \r\n+ add_route(wrap_view(DemoView, self), '/demo')\r\n+\r\n add_route(\r\n asgi_static(app_root / \"datasette\" / \"static\"), r\"/-/static/(?P.*)$\"\r\n )\r\n@@ -1673,8 +1676,46 @@ def _cleaner_task_str(task):\r\n return _cleaner_task_str_re.sub(\"\", s)\r\n \r\n \r\n-def wrap_view(view_fn, datasette):\r\n- @functools.wraps(view_fn)\r\n+class DemoView:\r\n+ async def __call__(self, datasette, request):\r\n+ return Response.text(\"Hello there! {} - {}\".format(datasette, request))\r\n+\r\n+def wrap_view(view_fn_or_class, datasette):\r\n+ is_function = isinstance(view_fn_or_class, types.FunctionType)\r\n+ if is_function:\r\n+ return wrap_view_function(view_fn_or_class, datasette)\r\n+ else:\r\n+ if not isinstance(view_fn_or_class, type):\r\n+ raise ValueError(\"view_fn_or_class must be a function or a class\")\r\n+ return wrap_view_class(view_fn_or_class, datasette)\r\n+\r\n+\r\n+def wrap_view_class(view_class, datasette):\r\n+ async def async_view_for_class(request, send):\r\n+ instance = view_class()\r\n+ if inspect.iscoroutinefunction(instance.__call__):\r\n+ return await async_call_with_supported_arguments(\r\n+ instance.__call__,\r\n+ scope=request.scope,\r\n+ receive=request.receive,\r\n+ send=send,\r\n+ request=request,\r\n+ datasette=datasette,\r\n+ )\r\n+ else:\r\n+ return call_with_supported_arguments(\r\n+ instance.__call__,\r\n+ scope=request.scope,\r\n+ receive=request.receive,\r\n+ send=send,\r\n+ request=request,\r\n+ datasette=datasette,\r\n+ )\r\n+\r\n+ return async_view_for_class\r\n+\r\n+\r\n+def wrap_view_function(view_fn, datasette):\r\n async def async_view_fn(request, send):\r\n if inspect.iscoroutinefunction(view_fn):\r\n response = await async_call_with_supported_arguments(\r\n```", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 1726236847, "label": "Resolve the difference between `wrap_view()` and `BaseView`"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/datasette/issues/2078#issuecomment-1563419066", "issue_url": "https://api.github.com/repos/simonw/datasette/issues/2078", "id": 1563419066, "node_id": "IC_kwDOBm6k_c5dL-G6", "user": {"value": 9599, "label": "simonw"}, "created_at": "2023-05-25T19:42:16Z", "updated_at": "2023-05-25T19:43:08Z", "author_association": "OWNER", "body": "Maybe what I want here is the ability to register classes with the router - and have the router know that if it's a class it should instantiate it via its constructor and then await `__call__` it.\r\n\r\nThe neat thing about it is that it can reduce the risk of having a class instance that accidentally shares state between requests.\r\n\r\nIt also encourages that each class only responds based on the `datasette, request, ...` objects that are passed to its methods.", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 1726236847, "label": "Resolve the difference between `wrap_view()` and `BaseView`"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/datasette/issues/2078#issuecomment-1563359114", "issue_url": "https://api.github.com/repos/simonw/datasette/issues/2078", "id": 1563359114, "node_id": "IC_kwDOBm6k_c5dLveK", "user": {"value": 9599, "label": "simonw"}, "created_at": "2023-05-25T18:47:57Z", "updated_at": "2023-05-25T18:47:57Z", "author_association": "OWNER", "body": "Oops, that broke everything:\r\n```\r\n @documented\r\n async def await_me_maybe(value: typing.Any) -> typing.Any:\r\n \"If value is callable, call it. If awaitable, await it. Otherwise return it.\"\r\n> if callable(value):\r\nE TypeError: 'module' object is not callable\r\n```", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 1726236847, "label": "Resolve the difference between `wrap_view()` and `BaseView`"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/datasette/issues/2078#issuecomment-1563329245", "issue_url": "https://api.github.com/repos/simonw/datasette/issues/2078", "id": 1563329245, "node_id": "IC_kwDOBm6k_c5dLoLd", "user": {"value": 9599, "label": "simonw"}, "created_at": "2023-05-25T18:26:47Z", "updated_at": "2023-05-25T18:28:08Z", "author_association": "OWNER", "body": "With type hints and a namedtuple:\r\n```python\r\nimport asyncio\r\nimport types\r\nfrom typing import NamedTuple, Any\r\n\r\n\r\nclass CallableStatus(NamedTuple):\r\n is_callable: bool\r\n is_async_callable: bool\r\n\r\n\r\ndef check_callable(obj: Any) -> CallableStatus:\r\n if not callable(obj):\r\n return CallableStatus(False, False)\r\n\r\n if isinstance(obj, type):\r\n # It's a class\r\n return CallableStatus(True, False)\r\n\r\n if isinstance(obj, types.FunctionType):\r\n return CallableStatus(True, asyncio.iscoroutinefunction(obj))\r\n\r\n if hasattr(obj, \"__call__\"):\r\n return CallableStatus(True, asyncio.iscoroutinefunction(obj.__call__))\r\n\r\n assert False, \"obj {} is somehow callable with no __call__ method\".format(repr(obj))\r\n```\r\n```python\r\nfor thing in (\r\n async_func,\r\n non_async_func,\r\n AsyncClass(),\r\n NotAsyncClass(),\r\n ClassNoCall(),\r\n AsyncClass,\r\n NotAsyncClass,\r\n ClassNoCall,\r\n):\r\n print(thing, check_callable(thing))\r\n```\r\n```\r\n CallableStatus(is_callable=True, is_async_callable=True)\r\n CallableStatus(is_callable=True, is_async_callable=False)\r\n<__main__.AsyncClass object at 0x106ba7490> CallableStatus(is_callable=True, is_async_callable=True)\r\n<__main__.NotAsyncClass object at 0x106740150> CallableStatus(is_callable=True, is_async_callable=False)\r\n<__main__.ClassNoCall object at 0x10676d910> CallableStatus(is_callable=False, is_async_callable=False)\r\n CallableStatus(is_callable=True, is_async_callable=False)\r\n CallableStatus(is_callable=True, is_async_callable=False)\r\n CallableStatus(is_callable=True, is_async_callable=False)\r\n```", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 1726236847, "label": "Resolve the difference between `wrap_view()` and `BaseView`"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/datasette/issues/2078#issuecomment-1563326000", "issue_url": "https://api.github.com/repos/simonw/datasette/issues/2078", "id": 1563326000, "node_id": "IC_kwDOBm6k_c5dLnYw", "user": {"value": 9599, "label": "simonw"}, "created_at": "2023-05-25T18:23:38Z", "updated_at": "2023-05-25T18:23:38Z", "author_association": "OWNER", "body": "I don't like that `is_callable()` implies a single boolean result but actually returns a pair. I'll call it `check_callable(obj)` instead.", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 1726236847, "label": "Resolve the difference between `wrap_view()` and `BaseView`"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/datasette/issues/2078#issuecomment-1563318598", "issue_url": "https://api.github.com/repos/simonw/datasette/issues/2078", "id": 1563318598, "node_id": "IC_kwDOBm6k_c5dLllG", "user": {"value": 9599, "label": "simonw"}, "created_at": "2023-05-25T18:17:03Z", "updated_at": "2023-05-25T18:21:25Z", "author_association": "OWNER", "body": "I think I want that to return `(is_callable, is_async)` - so I can both test if the thing can be called AND if it should be awaited in the same operation (without any exceptions).\r\n\r\nI tried this:\r\n```python\r\ndef is_callable(obj):\r\n \"Returns (is_callable, is_async_callable)\"\r\n if not callable(obj):\r\n return False, False\r\n\r\n if isinstance(obj, types.FunctionType):\r\n return True, asyncio.iscoroutinefunction(obj)\r\n \r\n if hasattr(obj, '__call__'):\r\n return True, asyncio.iscoroutinefunction(obj.__call__)\r\n\r\n return False, False\r\n```\r\n```python\r\nfor thing in (\r\n async_func, non_async_func, AsyncClass(), NotAsyncClass(), ClassNoCall(),\r\n AsyncClass, NotAsyncClass, ClassNoCall\r\n):\r\n print(thing, is_callable(thing))\r\n```\r\nAnd got:\r\n```\r\n (True, True)\r\n (True, False)\r\n<__main__.AsyncClass object at 0x106cce490> (True, True)\r\n<__main__.NotAsyncClass object at 0x106ccf710> (True, False)\r\n<__main__.ClassNoCall object at 0x106ccc810> (False, False)\r\n (True, True)\r\n (True, False)\r\n (True, False)\r\n```\r\nWhich is almost right, but I don't like that `AsyncClass` is shown as callable (which it is, since it's a class) and awaitable (which it is not - the `__call__` method may be async but calling the class constructor is not).\r\n\r\nSo I'm going to detect classes using `isinstance(obj, type)`.\r\n\r\n```python\r\ndef is_callable(obj):\r\n \"Returns (is_callable, is_async_callable)\"\r\n if not callable(obj):\r\n return False, False\r\n\r\n if isinstance(obj, type):\r\n # It's a class\r\n return True, False\r\n \r\n if isinstance(obj, types.FunctionType):\r\n return True, asyncio.iscoroutinefunction(obj)\r\n \r\n if hasattr(obj, '__call__'):\r\n return True, asyncio.iscoroutinefunction(obj.__call__)\r\n\r\n assert False, \"obj {} somehow is callable with no __call__ method\".format(obj)\r\n```\r\nI am reasonably confident the `AssertionError` can never be raised.\r\n\r\nAnd now:\r\n```\r\n (True, True)\r\n (True, False)\r\n<__main__.AsyncClass object at 0x106ccfa50> (True, True)\r\n<__main__.NotAsyncClass object at 0x106ccc8d0> (True, False)\r\n<__main__.ClassNoCall object at 0x106cd7690> (False, False)\r\n (True, False)\r\n (True, False)\r\n (True, False)\r\n```\r\nWhich is what I wanted.", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 1726236847, "label": "Resolve the difference between `wrap_view()` and `BaseView`"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/datasette/issues/2078#issuecomment-1563308919", "issue_url": "https://api.github.com/repos/simonw/datasette/issues/2078", "id": 1563308919, "node_id": "IC_kwDOBm6k_c5dLjN3", "user": {"value": 9599, "label": "simonw"}, "created_at": "2023-05-25T18:08:34Z", "updated_at": "2023-05-25T18:08:34Z", "author_association": "OWNER", "body": "After much fiddling this seems to work:\r\n```python\r\nimport asyncio, types\r\n\r\ndef is_async_callable(obj):\r\n if not callable(obj):\r\n raise ValueError(\"Object is not callable\")\r\n\r\n if isinstance(obj, types.FunctionType):\r\n return asyncio.iscoroutinefunction(obj)\r\n \r\n if hasattr(obj, '__call__'):\r\n return asyncio.iscoroutinefunction(obj.__call__)\r\n\r\n raise ValueError(\"Not a function and has no __call__ attribute\")\r\n```\r\nTested like so:\r\n```python\r\nclass AsyncClass:\r\n async def __call__(self):\r\n pass\r\n\r\nclass NotAsyncClass:\r\n def __call__(self):\r\n pass\r\n\r\nclass ClassNoCall:\r\n pass\r\n \r\nasync def async_func():\r\n pass\r\n\r\ndef non_async_func():\r\n pass\r\n\r\nfor thing in (AsyncClass(), NotAsyncClass(), ClassNoCall(), async_func, non_async_func):\r\n try:\r\n print(thing, is_async_callable(thing))\r\n except Exception as ex:\r\n print(thing, ex)\r\n```\r\n```\r\n<__main__.AsyncClass object at 0x106c32150> True\r\n<__main__.NotAsyncClass object at 0x106c32390> False\r\n<__main__.ClassNoCall object at 0x106c32750> Object is not callable\r\n True\r\n False\r\n```", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 1726236847, "label": "Resolve the difference between `wrap_view()` and `BaseView`"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/datasette/issues/2078#issuecomment-1563294669", "issue_url": "https://api.github.com/repos/simonw/datasette/issues/2078", "id": 1563294669, "node_id": "IC_kwDOBm6k_c5dLfvN", "user": {"value": 9599, "label": "simonw"}, "created_at": "2023-05-25T17:57:06Z", "updated_at": "2023-05-25T17:57:06Z", "author_association": "OWNER", "body": "I may need to be able to detect if a class instance has an `async def __call__` method - I think I can do that like so:\r\n\r\n```python\r\ndef iscoroutinefunction(obj):\r\n if inspect.iscoroutinefunction(obj):\r\n return True\r\n if hasattr(obj, '__call__') and inspect.iscoroutinefunction(obj.__call__):\r\n return True\r\n return False\r\n```\r\nFrom https://github.com/encode/starlette/issues/886#issuecomment-606585152", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 1726236847, "label": "Resolve the difference between `wrap_view()` and `BaseView`"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/datasette/issues/2078#issuecomment-1563292373", "issue_url": "https://api.github.com/repos/simonw/datasette/issues/2078", "id": 1563292373, "node_id": "IC_kwDOBm6k_c5dLfLV", "user": {"value": 9599, "label": "simonw"}, "created_at": "2023-05-25T17:55:12Z", "updated_at": "2023-05-25T17:55:30Z", "author_association": "OWNER", "body": "So I think subclasses of `BaseView` need to offer a callable which accepts all five of the DI arguments - `datasette`, `request`, `scope`, `send`, `receive` - and then makes a decision based on the HTTP verb as to which method of the class to call. Those methods themselves can accept a subset of those parameters and will only be sent on to them.\r\n\r\nHaving two layers of parameter detection feels a little bit untidy, but I think it will work.", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 1726236847, "label": "Resolve the difference between `wrap_view()` and `BaseView`"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/datasette/pull/2053#issuecomment-1563285150", "issue_url": "https://api.github.com/repos/simonw/datasette/issues/2053", "id": 1563285150, "node_id": "IC_kwDOBm6k_c5dLdae", "user": {"value": 9599, "label": "simonw"}, "created_at": "2023-05-25T17:48:50Z", "updated_at": "2023-05-25T17:49:52Z", "author_association": "OWNER", "body": "Uncommitted experimental code:\r\n```diff\r\ndiff --git a/datasette/views/database.py b/datasette/views/database.py\r\nindex 455ebd1f..85775433 100644\r\n--- a/datasette/views/database.py\r\n+++ b/datasette/views/database.py\r\n@@ -909,12 +909,13 @@ async def query_view(\r\n elif format_ in datasette.renderers.keys():\r\n # Dispatch request to the correct output format renderer\r\n # (CSV is not handled here due to streaming)\r\n+ print(data)\r\n result = call_with_supported_arguments(\r\n datasette.renderers[format_][0],\r\n datasette=datasette,\r\n- columns=columns,\r\n- rows=rows,\r\n- sql=sql,\r\n+ columns=data[\"rows\"][0].keys(),\r\n+ rows=data[\"rows\"],\r\n+ sql='',\r\n query_name=None,\r\n database=db.name,\r\n table=None,\r\n@@ -923,7 +924,7 @@ async def query_view(\r\n # These will be deprecated in Datasette 1.0:\r\n args=request.args,\r\n data={\r\n- \"rows\": rows,\r\n+ \"rows\": data[\"rows\"],\r\n }, # TODO what should this be?\r\n )\r\n result = await await_me_maybe(result)\r\ndiff --git a/docs/index.rst b/docs/index.rst\r\nindex 5a9cc7ed..254ed3da 100644\r\n--- a/docs/index.rst\r\n+++ b/docs/index.rst\r\n@@ -57,6 +57,7 @@ Contents\r\n settings\r\n introspection\r\n custom_templates\r\n+ template_context\r\n plugins\r\n writing_plugins\r\n plugin_hooks\r\n```\r\nWhere `docs/template_context.rst` looked like this:\r\n```rst\r\n.. _template_context:\r\n\r\nTemplate context\r\n================\r\n\r\n.. currentmodule:: datasette.context\r\n\r\nThis page describes the variables made available to templates used by Datasette to render different pages of the application.\r\n\r\n.. autoclass:: QueryContext\r\n :members:\r\n```\r\nAnd `datasette/context.py` had this:\r\n```python\r\nfrom dataclasses import dataclass\r\n\r\n@dataclass\r\nclass QueryContext:\r\n \"\"\"\r\n Used by the ``/database`` page when showing the results of a SQL query\r\n \"\"\"\r\n id: int\r\n \"Id is a thing\"\r\n rows: list[dict]\r\n \"Name is another thing\"\r\n```", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 1656432059, "label": "WIP new JSON for queries"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/datasette/issues/2078#issuecomment-1563283939", "issue_url": "https://api.github.com/repos/simonw/datasette/issues/2078", "id": 1563283939, "node_id": "IC_kwDOBm6k_c5dLdHj", "user": {"value": 9599, "label": "simonw"}, "created_at": "2023-05-25T17:47:38Z", "updated_at": "2023-05-25T17:47:38Z", "author_association": "OWNER", "body": "The idea behind `wrap_view()` is dependency injection - it's mainly used by plugins:\r\n\r\nhttps://docs.datasette.io/en/0.64.3/plugin_hooks.html#register-routes-datasette\r\n\r\nBut I like the pattern so I started using it for some of Datasette's own features.\r\n\r\nI should use it for _all_ of Datasette's own features.\r\n\r\nBut I still like the way `BaseView` helps with running different code for GET/POST/etc verbs.", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 1726236847, "label": "Resolve the difference between `wrap_view()` and `BaseView`"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/datasette/issues/2078#issuecomment-1563282327", "issue_url": "https://api.github.com/repos/simonw/datasette/issues/2078", "id": 1563282327, "node_id": "IC_kwDOBm6k_c5dLcuX", "user": {"value": 9599, "label": "simonw"}, "created_at": "2023-05-25T17:46:05Z", "updated_at": "2023-05-25T17:46:05Z", "author_association": "OWNER", "body": "Here's what `wrap_view()` does:\r\n\r\nhttps://github.com/simonw/datasette/blob/49184c569cd70efbda4f3f062afef3a34401d8d5/datasette/app.py#L1676-L1700\r\n\r\nIt's used e.g. here:\r\n\r\nhttps://github.com/simonw/datasette/blob/49184c569cd70efbda4f3f062afef3a34401d8d5/datasette/app.py#L1371-L1375\r\n\r\nThe `BaseView` thing meanwhile works like this:\r\n\r\nhttps://github.com/simonw/datasette/blob/d97e82df3c8a3f2e97038d7080167be9bb74a68d/datasette/views/base.py#L56-L157", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 1726236847, "label": "Resolve the difference between `wrap_view()` and `BaseView`"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/sqlite-utils/issues/554#issuecomment-1557607516", "issue_url": "https://api.github.com/repos/simonw/sqlite-utils/issues/554", "id": 1557607516, "node_id": "IC_kwDOCGYnMM5c1zRc", "user": {"value": 1231935, "label": "xavdid"}, "created_at": "2023-05-22T17:18:33Z", "updated_at": "2023-05-22T17:18:33Z", "author_association": "NONE", "body": "Oh and for context - this goes away if I use `.upsert` instead of `insert(..., ignore=True)`, but I don't want to update the value if it's written, just do an insert if it's new. The code is basically:\r\n\r\n```py\r\ndef save_items(table, items):\r\n db[\"users\"].insert(build_user(items[0]), pk=\"id\",ignore=True)\r\n db[table].insert_all(items)\r\n\r\nif comments := fetch_comments():\r\n save_items('comments', comments)\r\n\r\nif posts := fetch_posts():\r\n save_items('posts', posts)\r\n```\r\n\r\nSo either `comments` or `post` could create the relevant user if those items exist. In cases where they _both_ exist, I get this error. I need the `pk` because either call could create the table.", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 1720096994, "label": "`IndexError` when doing `.insert(..., pk='id')` after `insert_all`"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/datasette/pull/2077#issuecomment-1557289070", "issue_url": "https://api.github.com/repos/simonw/datasette/issues/2077", "id": 1557289070, "node_id": "IC_kwDOBm6k_c5c0lhu", "user": {"value": 22429695, "label": "codecov[bot]"}, "created_at": "2023-05-22T14:08:33Z", "updated_at": "2023-06-29T14:40:35Z", "author_association": "NONE", "body": "## [Codecov](https://app.codecov.io/gh/simonw/datasette/pull/2077?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Simon+Willison) Report\nPatch and project coverage have no change.\n> Comparison is base [(`ede6203`)](https://app.codecov.io/gh/simonw/datasette/commit/ede62036180993dbd9d4e5d280fc21c183cda1c3?el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Simon+Willison) 92.40% compared to head [(`9785c4f`)](https://app.codecov.io/gh/simonw/datasette/pull/2077?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Simon+Willison) 92.40%.\n\n
Additional details and impacted files\n\n\n```diff\n@@ Coverage Diff @@\n## main #2077 +/- ##\n=======================================\n Coverage 92.40% 92.40% \n=======================================\n Files 39 39 \n Lines 5803 5803 \n=======================================\n Hits 5362 5362 \n Misses 441 441 \n```\n\n\n\n\n
\n\n[:umbrella: View full report in Codecov by Sentry](https://app.codecov.io/gh/simonw/datasette/pull/2077?src=pr&el=continue&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Simon+Willison). \n:loudspeaker: Do you have feedback about the report comment? [Let us know in this issue](https://about.codecov.io/codecov-pr-comment-feedback/?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Simon+Willison).\n", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 1719759468, "label": "Bump furo from 2023.3.27 to 2023.5.20"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/sqlite-utils/issues/552#issuecomment-1556292204", "issue_url": "https://api.github.com/repos/simonw/sqlite-utils/issues/552", "id": 1556292204, "node_id": "IC_kwDOCGYnMM5cwyJs", "user": {"value": 9599, "label": "simonw"}, "created_at": "2023-05-21T21:05:15Z", "updated_at": "2023-05-21T21:05:15Z", "author_association": "OWNER", "body": "Now live at https://sqlite-utils.datasette.io/en/latest/installation.html#setting-up-shell-completion", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 1718612569, "label": "Document how to setup shell auto-completion"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/sqlite-utils/issues/551#issuecomment-1556291915", "issue_url": "https://api.github.com/repos/simonw/sqlite-utils/issues/551", "id": 1556291915, "node_id": "IC_kwDOCGYnMM5cwyFL", "user": {"value": 9599, "label": "simonw"}, "created_at": "2023-05-21T21:04:03Z", "updated_at": "2023-05-21T21:04:03Z", "author_association": "OWNER", "body": "Now live at https://sqlite-utils.datasette.io/en/latest/cli.html", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 1718607907, "label": "Make as many examples in the CLI docs as possible copy-and-pastable"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/sqlite-utils/pull/553#issuecomment-1556288300", "issue_url": "https://api.github.com/repos/simonw/sqlite-utils/issues/553", "id": 1556288300, "node_id": "IC_kwDOCGYnMM5cwxMs", "user": {"value": 9599, "label": "simonw"}, "created_at": "2023-05-21T20:48:01Z", "updated_at": "2023-05-21T20:48:01Z", "author_association": "OWNER", "body": "If https://sqlite-utils--553.org.readthedocs.build/en/553/cli.html#running-sql-queries looks good I can merge this.", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 1718635018, "label": "Reformatted CLI examples in docs"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/sqlite-utils/issues/551#issuecomment-1556288270", "issue_url": "https://api.github.com/repos/simonw/sqlite-utils/issues/551", "id": 1556288270, "node_id": "IC_kwDOCGYnMM5cwxMO", "user": {"value": 9599, "label": "simonw"}, "created_at": "2023-05-21T20:47:51Z", "updated_at": "2023-05-21T20:47:51Z", "author_association": "OWNER", "body": "This page has all of the changes: https://sqlite-utils--553.org.readthedocs.build/en/553/cli.html#running-sql-queries", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 1718607907, "label": "Make as many examples in the CLI docs as possible copy-and-pastable"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/sqlite-utils/pull/553#issuecomment-1556287870", "issue_url": "https://api.github.com/repos/simonw/sqlite-utils/issues/553", "id": 1556287870, "node_id": "IC_kwDOCGYnMM5cwxF-", "user": {"value": 22429695, "label": "codecov[bot]"}, "created_at": "2023-05-21T20:45:58Z", "updated_at": "2023-05-21T20:57:08Z", "author_association": "NONE", "body": "## [Codecov](https://app.codecov.io/gh/simonw/sqlite-utils/pull/553?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Simon+Willison) Report\nPatch and project coverage have no change.\n> Comparison is base [(`e240133`)](https://app.codecov.io/gh/simonw/sqlite-utils/commit/e240133b11588d31dc22c632f7a7ca636c72978d?el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Simon+Willison) 96.36% compared to head [(`0b81794`)](https://app.codecov.io/gh/simonw/sqlite-utils/pull/553?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Simon+Willison) 96.36%.\n\n> :exclamation: Current head 0b81794 differs from pull request most recent head 21036a5. Consider uploading reports for the commit 21036a5 to get more accurate results\n\n
Additional details and impacted files\n\n\n```diff\n@@ Coverage Diff @@\n## main #553 +/- ##\n=======================================\n Coverage 96.36% 96.36% \n=======================================\n Files 6 6 \n Lines 2726 2726 \n=======================================\n Hits 2627 2627 \n Misses 99 99 \n```\n\n\n\n\n
\n\n[:umbrella: View full report in Codecov by Sentry](https://app.codecov.io/gh/simonw/sqlite-utils/pull/553?src=pr&el=continue&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Simon+Willison). \n:loudspeaker: Do you have feedback about the report comment? [Let us know in this issue](https://about.codecov.io/codecov-pr-comment-feedback/?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Simon+Willison).\n", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 1718635018, "label": "Reformatted CLI examples in docs"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/sqlite-utils/issues/551#issuecomment-1556287599", "issue_url": "https://api.github.com/repos/simonw/sqlite-utils/issues/551", "id": 1556287599, "node_id": "IC_kwDOCGYnMM5cwxBv", "user": {"value": 9599, "label": "simonw"}, "created_at": "2023-05-21T20:44:55Z", "updated_at": "2023-05-21T20:44:55Z", "author_association": "OWNER", "body": "Put this in a PR so I can preview it: \r\n- #553\r\n", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 1718607907, "label": "Make as many examples in the CLI docs as possible copy-and-pastable"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/sqlite-utils/issues/545#issuecomment-1556269616", "issue_url": "https://api.github.com/repos/simonw/sqlite-utils/issues/545", "id": 1556269616, "node_id": "IC_kwDOCGYnMM5cwsow", "user": {"value": 9599, "label": "simonw"}, "created_at": "2023-05-21T19:33:13Z", "updated_at": "2023-05-21T19:33:13Z", "author_association": "OWNER", "body": "Now released: https://sqlite-utils.datasette.io/en/stable/changelog.html#v3-32", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 1718517882, "label": "Try out Trogon for a tui interface"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/sqlite-utils/issues/551#issuecomment-1556265772", "issue_url": "https://api.github.com/repos/simonw/sqlite-utils/issues/551", "id": 1556265772, "node_id": "IC_kwDOCGYnMM5cwrss", "user": {"value": 9599, "label": "simonw"}, "created_at": "2023-05-21T19:16:15Z", "updated_at": "2023-05-21T19:16:15Z", "author_association": "OWNER", "body": "Another option:\r\n\r\n\"image\"\r\n\r\nThat's using this markup:\r\n\r\n```\r\nNewline-delimited JSON\r\n~~~~~~~~~~~~~~~~~~~~~~\r\n\r\nUse ``--nl`` to get back newline-delimited JSON objects:\r\n\r\n.. code-block:: bash\r\n\r\n sqlite-utils dogs.db \"select * from dogs\" --nl\r\n\r\n.. code-block:: output\r\n\r\n {\"id\": 1, \"age\": 4, \"name\": \"Cleo\"}\r\n {\"id\": 2, \"age\": 2, \"name\": \"Pancakes\"}\r\n```\r\nAnd this extra CSS:\r\n\r\n```css\r\n.highlight-output .highlight {\r\n border-left: 9px solid #30c94f;\r\n}\r\n```", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 1718607907, "label": "Make as many examples in the CLI docs as possible copy-and-pastable"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/sqlite-utils/issues/551#issuecomment-1556263182", "issue_url": "https://api.github.com/repos/simonw/sqlite-utils/issues/551", "id": 1556263182, "node_id": "IC_kwDOCGYnMM5cwrEO", "user": {"value": 9599, "label": "simonw"}, "created_at": "2023-05-21T19:06:48Z", "updated_at": "2023-05-21T19:06:48Z", "author_association": "OWNER", "body": "I could split them up into two blocks like this:\r\n\r\n\"image\"\r\n\r\nI do miss the visual indication that one of these is the command and one is the output though.", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 1718607907, "label": "Make as many examples in the CLI docs as possible copy-and-pastable"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/sqlite-utils/issues/551#issuecomment-1556262574", "issue_url": "https://api.github.com/repos/simonw/sqlite-utils/issues/551", "id": 1556262574, "node_id": "IC_kwDOCGYnMM5cwq6u", "user": {"value": 9599, "label": "simonw"}, "created_at": "2023-05-21T19:04:59Z", "updated_at": "2023-05-21T19:04:59Z", "author_association": "OWNER", "body": "I wrote the docs like this because early examples include both the command and its output:\r\n\r\nhttps://sqlite-utils.datasette.io/en/stable/cli.html#returning-json\r\n\r\n\"image\"", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 1718607907, "label": "Make as many examples in the CLI docs as possible copy-and-pastable"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/sqlite-utils/issues/550#issuecomment-1556255309", "issue_url": "https://api.github.com/repos/simonw/sqlite-utils/issues/550", "id": 1556255309, "node_id": "IC_kwDOCGYnMM5cwpJN", "user": {"value": 9599, "label": "simonw"}, "created_at": "2023-05-21T18:42:25Z", "updated_at": "2023-05-21T18:42:25Z", "author_association": "OWNER", "body": "Tests passed here: https://github.com/simonw/sqlite-utils/actions/runs/5039119716", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 1718595700, "label": "AttributeError: 'EntryPoints' object has no attribute 'get' for flake8 on Python 3.7"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/sqlite-utils/issues/550#issuecomment-1556250236", "issue_url": "https://api.github.com/repos/simonw/sqlite-utils/issues/550", "id": 1556250236, "node_id": "IC_kwDOCGYnMM5cwn58", "user": {"value": 9599, "label": "simonw"}, "created_at": "2023-05-21T18:25:26Z", "updated_at": "2023-05-21T18:25:26Z", "author_association": "OWNER", "body": "Relevant issues:\r\n- https://github.com/python/importlib_metadata/issues/406\r\n- https://github.com/PyCQA/flake8/issues/1701\r\n\r\nIt looks to me like this is only a problem for `flake8` on Python 3.7 - 3.8 and higher work OK.", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 1718595700, "label": "AttributeError: 'EntryPoints' object has no attribute 'get' for flake8 on Python 3.7"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/sqlite-utils/issues/550#issuecomment-1556249984", "issue_url": "https://api.github.com/repos/simonw/sqlite-utils/issues/550", "id": 1556249984, "node_id": "IC_kwDOCGYnMM5cwn2A", "user": {"value": 9599, "label": "simonw"}, "created_at": "2023-05-21T18:24:48Z", "updated_at": "2023-05-21T18:24:48Z", "author_association": "OWNER", "body": "This is blocking:\r\n- #549", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 1718595700, "label": "AttributeError: 'EntryPoints' object has no attribute 'get' for flake8 on Python 3.7"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/sqlite-utils/issues/545#issuecomment-1556247818", "issue_url": "https://api.github.com/repos/simonw/sqlite-utils/issues/545", "id": 1556247818, "node_id": "IC_kwDOCGYnMM5cwnUK", "user": {"value": 9599, "label": "simonw"}, "created_at": "2023-05-21T18:17:46Z", "updated_at": "2023-05-21T18:17:46Z", "author_association": "OWNER", "body": "Draft documentation: https://sqlite-utils--549.org.readthedocs.build/en/549/cli.html#cli-tui", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 1718517882, "label": "Try out Trogon for a tui interface"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/sqlite-utils/pull/549#issuecomment-1556242262", "issue_url": "https://api.github.com/repos/simonw/sqlite-utils/issues/549", "id": 1556242262, "node_id": "IC_kwDOCGYnMM5cwl9W", "user": {"value": 9599, "label": "simonw"}, "created_at": "2023-05-21T18:00:05Z", "updated_at": "2023-05-21T18:00:05Z", "author_association": "OWNER", "body": "Failing `mypy` test: https://github.com/simonw/sqlite-utils/actions/runs/5038983349/jobs/9036828465\r\n```\r\nsqlite_utils/cli.py:37: error: Skipping analyzing \"trogon\": module is installed, but missing library stubs or py.typed marker [import]\r\nsqlite_utils/cli.py:37: note: See https://mypy.readthedocs.io/en/stable/running_mypy.html#missing-imports\r\nFound 1 error in 1 file (checked 52 source files)\r\n```", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 1718586377, "label": "TUI powered by Trogon"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/sqlite-utils/pull/549#issuecomment-1556241812", "issue_url": "https://api.github.com/repos/simonw/sqlite-utils/issues/549", "id": 1556241812, "node_id": "IC_kwDOCGYnMM5cwl2U", "user": {"value": 9599, "label": "simonw"}, "created_at": "2023-05-21T17:58:25Z", "updated_at": "2023-05-21T17:58:25Z", "author_association": "OWNER", "body": "Documentation: https://sqlite-utils--549.org.readthedocs.build/en/549/cli.html#cli-tui", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 1718586377, "label": "TUI powered by Trogon"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/sqlite-utils/pull/549#issuecomment-1556241555", "issue_url": "https://api.github.com/repos/simonw/sqlite-utils/issues/549", "id": 1556241555, "node_id": "IC_kwDOCGYnMM5cwlyT", "user": {"value": 22429695, "label": "codecov[bot]"}, "created_at": "2023-05-21T17:57:24Z", "updated_at": "2023-05-21T18:28:44Z", "author_association": "NONE", "body": "## [Codecov](https://app.codecov.io/gh/simonw/sqlite-utils/pull/549?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Simon+Willison) Report\nPatch coverage: **`83.33`**% and project coverage change: **`+0.06`** :tada:\n> Comparison is base [(`b3b100d`)](https://app.codecov.io/gh/simonw/sqlite-utils/commit/b3b100d7f5b2a76ccd4bfe8b0301a29e321d0375?el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Simon+Willison) 96.30% compared to head [(`948692a`)](https://app.codecov.io/gh/simonw/sqlite-utils/pull/549?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Simon+Willison) 96.36%.\n\n
Additional details and impacted files\n\n\n```diff\n@@ Coverage Diff @@\n## main #549 +/- ##\n==========================================\n+ Coverage 96.30% 96.36% +0.06% \n==========================================\n Files 6 6 \n Lines 2707 2726 +19 \n==========================================\n+ Hits 2607 2627 +20 \n+ Misses 100 99 -1 \n```\n\n\n| [Impacted Files](https://app.codecov.io/gh/simonw/sqlite-utils/pull/549?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Simon+Willison) | Coverage \u0394 | |\n|---|---|---|\n| [sqlite\\_utils/cli.py](https://app.codecov.io/gh/simonw/sqlite-utils/pull/549?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Simon+Willison#diff-c3FsaXRlX3V0aWxzL2NsaS5weQ==) | `95.22% <83.33%> (-0.03%)` | :arrow_down: |\n\n... and [1 file with indirect coverage changes](https://app.codecov.io/gh/simonw/sqlite-utils/pull/549/indirect-changes?src=pr&el=tree-more&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Simon+Willison)\n\n\n
\n\n[:umbrella: View full report in Codecov by Sentry](https://app.codecov.io/gh/simonw/sqlite-utils/pull/549?src=pr&el=continue&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Simon+Willison). \n:loudspeaker: Do you have feedback about the report comment? [Let us know in this issue](https://about.codecov.io/codecov-pr-comment-feedback/?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Simon+Willison).\n", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 1718586377, "label": "TUI powered by Trogon"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/sqlite-utils/issues/548#issuecomment-1556231832", "issue_url": "https://api.github.com/repos/simonw/sqlite-utils/issues/548", "id": 1556231832, "node_id": "IC_kwDOCGYnMM5cwjaY", "user": {"value": 9599, "label": "simonw"}, "created_at": "2023-05-21T17:24:13Z", "updated_at": "2023-05-21T17:24:13Z", "author_association": "OWNER", "body": "Oh, I see why that is now:\r\n\r\nhttps://github.com/simonw/sqlite-utils/blob/6027f3ea6939a399aeef2578fca17efec0e539df/sqlite_utils/cli.py#L2670-L2679\r\n\r\nThis is because of the following command:\r\n\r\n sqlite-utils analyze-tables table1 table2 --column x\r\n\r\nSince you can pass multiple tables AND multiple columns, the tool currently assumes that the column(s) you specify may be available on a subset of the provided tables.\r\n\r\nI'm going to change this so if the column is not on ANY of those tables you get an error.", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 1718576761, "label": "analyze-tables should validate provide --column names"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/sqlite-utils/issues/547#issuecomment-1556228395", "issue_url": "https://api.github.com/repos/simonw/sqlite-utils/issues/547", "id": 1556228395, "node_id": "IC_kwDOCGYnMM5cwikr", "user": {"value": 9599, "label": "simonw"}, "created_at": "2023-05-21T17:11:15Z", "updated_at": "2023-05-21T17:11:15Z", "author_association": "OWNER", "body": "This will be a cosmetic change to the CLI output only - the options to save data to the database and the Python API function will continue to return `[(None, 158)]`.\r\n\r\nI can add an optimization though to avoid running the SQL count query if we know that it's all `null`.", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 1718572201, "label": "No need to show common values if everything is null"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/sqlite-utils/issues/544#issuecomment-1556225788", "issue_url": "https://api.github.com/repos/simonw/sqlite-utils/issues/544", "id": 1556225788, "node_id": "IC_kwDOCGYnMM5cwh78", "user": {"value": 9599, "label": "simonw"}, "created_at": "2023-05-21T17:02:05Z", "updated_at": "2023-05-21T17:02:05Z", "author_association": "OWNER", "body": "New docs:\r\n\r\n- https://sqlite-utils.datasette.io/en/latest/cli.html#cli-analyze-tables\r\n- https://sqlite-utils.datasette.io/en/latest/cli-reference.html#analyze-tables\r\n- https://sqlite-utils.datasette.io/en/latest/python-api.html#analyzing-a-column\r\n- https://sqlite-utils.datasette.io/en/latest/reference.html#sqlite_utils.db.Table.analyze_column\r\n\r\nNew help output:\r\n\r\n```\r\n % sqlite-utils analyze-tables --help\r\nUsage: sqlite-utils analyze-tables [OPTIONS] PATH [TABLES]...\r\n\r\n Analyze the columns in one or more tables\r\n\r\n Example:\r\n\r\n sqlite-utils analyze-tables data.db trees\r\n\r\nOptions:\r\n -c, --column TEXT Specific columns to analyze\r\n --save Save results to _analyze_tables table\r\n --common-limit INTEGER How many common values\r\n --no-most Skip most common values\r\n --no-least Skip least common values\r\n --load-extension TEXT Path to SQLite extension, with optional :entrypoint\r\n -h, --help Show this message and exit.\r\n```", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 1718515590, "label": "New options for analyze-tables --common-limit --no-most and --no-least"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/sqlite-utils/pull/546#issuecomment-1556213396", "issue_url": "https://api.github.com/repos/simonw/sqlite-utils/issues/546", "id": 1556213396, "node_id": "IC_kwDOCGYnMM5cwe6U", "user": {"value": 9599, "label": "simonw"}, "created_at": "2023-05-21T15:58:12Z", "updated_at": "2023-05-21T16:18:46Z", "author_association": "OWNER", "body": "Documentation preview:\r\n- https://sqlite-utils--546.org.readthedocs.build/en/546/cli.html#cli-analyze-tables\r\n- https://sqlite-utils--546.org.readthedocs.build/en/546/cli-reference.html#analyze-tables\r\n- https://sqlite-utils--546.org.readthedocs.build/en/546/python-api.html#analyzing-a-column\r\n- https://sqlite-utils--546.org.readthedocs.build/en/546/reference.html#sqlite_utils.db.Table.analyze_column", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 1718550688, "label": "Analyze tables options: --common-limit, --no-most, --no-least"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/sqlite-utils/pull/546#issuecomment-1556213031", "issue_url": "https://api.github.com/repos/simonw/sqlite-utils/issues/546", "id": 1556213031, "node_id": "IC_kwDOCGYnMM5cwe0n", "user": {"value": 22429695, "label": "codecov[bot]"}, "created_at": "2023-05-21T15:56:05Z", "updated_at": "2023-05-21T16:18:03Z", "author_association": "NONE", "body": "## [Codecov](https://app.codecov.io/gh/simonw/sqlite-utils/pull/546?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Simon+Willison) Report\nPatch coverage: **`93.75`**% and no project coverage change.\n> Comparison is base [(`b3b100d`)](https://app.codecov.io/gh/simonw/sqlite-utils/commit/b3b100d7f5b2a76ccd4bfe8b0301a29e321d0375?el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Simon+Willison) 96.30% compared to head [(`9f23e68`)](https://app.codecov.io/gh/simonw/sqlite-utils/pull/546?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Simon+Willison) 96.31%.\n\n> :exclamation: Current head 9f23e68 differs from pull request most recent head 2eca17d. Consider uploading reports for the commit 2eca17d to get more accurate results\n\n
Additional details and impacted files\n\n\n```diff\n@@ Coverage Diff @@\n## main #546 +/- ##\n=======================================\n Coverage 96.30% 96.31% \n=======================================\n Files 6 6 \n Lines 2707 2712 +5 \n=======================================\n+ Hits 2607 2612 +5 \n Misses 100 100 \n```\n\n\n| [Impacted Files](https://app.codecov.io/gh/simonw/sqlite-utils/pull/546?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Simon+Willison) | Coverage \u0394 | |\n|---|---|---|\n| [sqlite\\_utils/db.py](https://app.codecov.io/gh/simonw/sqlite-utils/pull/546?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Simon+Willison#diff-c3FsaXRlX3V0aWxzL2RiLnB5) | `97.37% <90.90%> (+<0.01%)` | :arrow_up: |\n| [sqlite\\_utils/cli.py](https://app.codecov.io/gh/simonw/sqlite-utils/pull/546?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Simon+Willison#diff-c3FsaXRlX3V0aWxzL2NsaS5weQ==) | `95.26% <100.00%> (+0.01%)` | :arrow_up: |\n\n\n
\n\n[:umbrella: View full report in Codecov by Sentry](https://app.codecov.io/gh/simonw/sqlite-utils/pull/546?src=pr&el=continue&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Simon+Willison). \n:loudspeaker: Do you have feedback about the report comment? [Let us know in this issue](https://about.codecov.io/codecov-pr-comment-feedback/?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Simon+Willison).\n", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 1718550688, "label": "Analyze tables options: --common-limit, --no-most, --no-least"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/sqlite-utils/issues/544#issuecomment-1556211643", "issue_url": "https://api.github.com/repos/simonw/sqlite-utils/issues/544", "id": 1556211643, "node_id": "IC_kwDOCGYnMM5cwee7", "user": {"value": 9599, "label": "simonw"}, "created_at": "2023-05-21T15:48:17Z", "updated_at": "2023-05-21T15:48:17Z", "author_association": "OWNER", "body": "I generated the commit message in https://github.com/simonw/sqlite-utils/commit/1c1991b447a1ddd3d61d9d4a8a1d6a9da47ced20 using `git diff | llm --system 'describe this change'`.", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 1718515590, "label": "New options for analyze-tables --common-limit --no-most and --no-least"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/sqlite-utils/issues/545#issuecomment-1556210844", "issue_url": "https://api.github.com/repos/simonw/sqlite-utils/issues/545", "id": 1556210844, "node_id": "IC_kwDOCGYnMM5cweSc", "user": {"value": 9599, "label": "simonw"}, "created_at": "2023-05-21T15:44:10Z", "updated_at": "2023-05-21T15:44:10Z", "author_association": "OWNER", "body": "It looks like `nargs=-1` on a positional argument isn't yet supported - opened an issue here:\r\n- https://github.com/Textualize/trogon/issues/4", "reactions": "{\"total_count\": 1, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 1, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 1718517882, "label": "Try out Trogon for a tui interface"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/sqlite-utils/issues/545#issuecomment-1556191894", "issue_url": "https://api.github.com/repos/simonw/sqlite-utils/issues/545", "id": 1556191894, "node_id": "IC_kwDOCGYnMM5cwZqW", "user": {"value": 9599, "label": "simonw"}, "created_at": "2023-05-21T14:20:14Z", "updated_at": "2023-05-21T14:20:14Z", "author_association": "OWNER", "body": "Opened a feature request for customizing the help and command name:\r\n\r\n- https://github.com/Textualize/trogon/issues/2", "reactions": "{\"total_count\": 2, \"+1\": 1, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 1, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 1718517882, "label": "Try out Trogon for a tui interface"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/sqlite-utils/issues/545#issuecomment-1556190531", "issue_url": "https://api.github.com/repos/simonw/sqlite-utils/issues/545", "id": 1556190531, "node_id": "IC_kwDOCGYnMM5cwZVD", "user": {"value": 9599, "label": "simonw"}, "created_at": "2023-05-21T14:13:43Z", "updated_at": "2023-05-21T14:13:43Z", "author_association": "OWNER", "body": "OK, this works!\r\n\r\n![trogon](https://github.com/simonw/sqlite-utils/assets/9599/2ae194c5-ec82-471a-9d1b-b01b3f2632f3)\r\n\r\nTo try it out, install that branch from GitHub:\r\n\r\n pip install https://github.com/simonw/sqlite-utils/archive/refs/heads/trogon.zip\r\n\r\nThen run this:\r\n\r\n sqlite-utils install trogon\r\n\r\nAnd this:\r\n\r\n sqlite-utils tui\r\n", "reactions": "{\"total_count\": 5, \"+1\": 2, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 3, \"eyes\": 0}", "issue": {"value": 1718517882, "label": "Try out Trogon for a tui interface"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/sqlite-utils/issues/545#issuecomment-1556189823", "issue_url": "https://api.github.com/repos/simonw/sqlite-utils/issues/545", "id": 1556189823, "node_id": "IC_kwDOCGYnMM5cwZJ_", "user": {"value": 9599, "label": "simonw"}, "created_at": "2023-05-21T14:09:59Z", "updated_at": "2023-05-21T14:09:59Z", "author_association": "OWNER", "body": "I don't want to add `trogon` as a default dependency because it's a little heavy - it pulls in all of Rich and Textual as well. People who use `sqlite-utils` just for its Python API won't benefit from this - it's a CLI feature only.\r\n\r\nBut I have a `sqlite-utils install ...` command for helping people to install packages into the same virtual environment as `sqlite-utils` no matter how they installed that tool: https://sqlite-utils.datasette.io/en/stable/cli.html#cli-install\r\n\r\nSo I can treat Trogon as an optional dependency and add the `sqlite-utils tui` command only if that package is also installed.", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 1718517882, "label": "Try out Trogon for a tui interface"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/sqlite-utils/issues/399#issuecomment-1548913065", "issue_url": "https://api.github.com/repos/simonw/sqlite-utils/issues/399", "id": 1548913065, "node_id": "IC_kwDOCGYnMM5cUomp", "user": {"value": 433780, "label": "chrislkeller"}, "created_at": "2023-05-16T03:11:03Z", "updated_at": "2023-05-16T03:11:52Z", "author_association": "NONE", "body": "Using this thread and some [other resources](https://sqlite-utils.datasette.io/en/stable/cli.html#spatialite-helpers) I managed to cobble together a couple of sqlite-utils lines to add a geometry column for a table that already has a lat/lng column.\r\n\r\n```\r\n# add a geometry column\r\nsqlite-utils add-geometry-column [db name] [table name] geometry --type POINT --srid 4326\r\n\r\n# add a point for each row to geometry column\r\nsqlite-utils --load-extension=spatialite [db name] 'update [table name] SET Geometry=MakePoint(longitude, latitude, 4326);'\r\n```", "reactions": "{\"total_count\": 1, \"+1\": 1, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 1124731464, "label": "Make it easier to insert geometries, with documentation and maybe code"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/datasette/pull/2052#issuecomment-1548617257", "issue_url": "https://api.github.com/repos/simonw/datasette/issues/2052", "id": 1548617257, "node_id": "IC_kwDOBm6k_c5cTgYp", "user": {"value": 193185, "label": "cldellow"}, "created_at": "2023-05-15T21:32:20Z", "updated_at": "2023-05-15T21:32:20Z", "author_association": "CONTRIBUTOR", "body": "> Were you picturing that the whole plugin config object could be returned as a promise, or that the individual hooks (like makeColumnActions or makeAboveTablePanelConfigs supported returning a promise of arrays instead only returning plain arrays?\r\n\r\nThe latter - that you could return a promise of arrays, so it parallels the [\"await me maybe\" pattern in Datasette](https://simonwillison.net/2020/Sep/2/await-me-maybe/), where you can return either a value, a callable or an awaitable.\r\n\r\n> I have a hunch that what you're describing might be achievable without adding Promises to the API with something\r\n\r\nOops, I did a poor job explaining. Yes, this would work - but it requires me to continue to communicate the column names out of band (in order to fetch the facet data per-column before registering my plugin), vs being able to re-use them from the plugin implementation.\r\n\r\nThis isn't that big of a deal - it'd be a nice ergonomic improvement, but nowhere near as a big of an improvement as having an officially sanctioned way to add stuff to the column menus in the first place.\r\n\r\nThis could also be layered on in a future commit without breaking v1 users, too, so it's not at all urgent.\r\n\r\n> especially if those lines are encapsulated by a function we provide (maybe something that's available on the window provided by Datasette as an inline script tag\r\n\r\nAh, this is maybe the the key point. Since it's all hosted inside Datasette, Datasette can provide some arbitrary sugar to make it easier to work with.\r\n\r\nMy experience with async scripts in JS is that people sometimes don't understand the race conditions inherent to them. If they copy/paste from a tutorial, it does just work. But then they'll delete half the code, and by chance it still works on their machine/Datasette templates, and now someone's headed for an annoying debugging session -- maybe them, maybe someone else who tries to re-use their plugin.\r\n\r\nAgain, a fairly minor thing, though.", "reactions": "{\"total_count\": 1, \"+1\": 1, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 1651082214, "label": "feat: Javascript Plugin API (Custom panels, column menu items with JS actions)"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/datasette/pull/2075#issuecomment-1547944971", "issue_url": "https://api.github.com/repos/simonw/datasette/issues/2075", "id": 1547944971, "node_id": "IC_kwDOBm6k_c5cQ8QL", "user": {"value": 22429695, "label": "codecov[bot]"}, "created_at": "2023-05-15T14:12:20Z", "updated_at": "2023-05-15T14:12:20Z", "author_association": "NONE", "body": "## [Codecov](https://app.codecov.io/gh/simonw/datasette/pull/2075?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Simon+Willison) Report\nPatch and project coverage have no change.\n> Comparison is base [(`49184c5`)](https://app.codecov.io/gh/simonw/datasette/commit/49184c569cd70efbda4f3f062afef3a34401d8d5?el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Simon+Willison) 92.40% compared to head [(`b99e1d3`)](https://app.codecov.io/gh/simonw/datasette/pull/2075?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Simon+Willison) 92.40%.\n\n
Additional details and impacted files\n\n\n```diff\n@@ Coverage Diff @@\n## main #2075 +/- ##\n=======================================\n Coverage 92.40% 92.40% \n=======================================\n Files 38 38 \n Lines 5751 5751 \n=======================================\n Hits 5314 5314 \n Misses 437 437 \n```\n\n\n\n\n
\n\n[:umbrella: View full report in Codecov by Sentry](https://app.codecov.io/gh/simonw/datasette/pull/2075?src=pr&el=continue&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Simon+Willison). \n:loudspeaker: Do you have feedback about the report comment? [Let us know in this issue](https://about.codecov.io/codecov-pr-comment-feedback/?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Simon+Willison).\n", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 1710164693, "label": "Bump sphinx from 6.1.3 to 7.0.1"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/datasette/pull/2068#issuecomment-1547911570", "issue_url": "https://api.github.com/repos/simonw/datasette/issues/2068", "id": 1547911570, "node_id": "IC_kwDOBm6k_c5cQ0GS", "user": {"value": 49699333, "label": "dependabot[bot]"}, "created_at": "2023-05-15T13:59:35Z", "updated_at": "2023-05-15T13:59:35Z", "author_association": "CONTRIBUTOR", "body": "Superseded by #2075.", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 1690842199, "label": "Bump sphinx from 6.1.3 to 7.0.0"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/datasette/pull/2052#issuecomment-1546362374", "issue_url": "https://api.github.com/repos/simonw/datasette/issues/2052", "id": 1546362374, "node_id": "IC_kwDOBm6k_c5cK54G", "user": {"value": 9020979, "label": "hydrosquall"}, "created_at": "2023-05-12T22:09:03Z", "updated_at": "2023-05-12T22:09:03Z", "author_association": "NONE", "body": "Hey @cldellow , thanks for the thoughtful feedback and describing the \"lazy facets\" feature! \r\n\r\nIt sounds like the [postTask](https://developer.mozilla.org/en-US/docs/Web/API/Scheduler/postTask) API might be relevant for the types of network request scheduling you have in mind. \r\n\r\nAddressing your points inline below:\r\n\r\n> It might also be nice if the plugins could return Promises.\r\n\r\nWere you picturing that the whole plugin config object could be returned as a promise, or that the individual hooks (like `makeColumnActions` or `makeAboveTablePanelConfigs` supported returning a promise of arrays instead only returning plain arrays?\r\n\r\nI think what you're describing can be achievable, but I want to make sure I do so in a way that addresses your need / keeps the complexity of the plugin core system at a level this is approachable . \r\n\r\nI have a hunch that what you're describing might be achievable without adding Promises to the API with something like\r\n\r\n```\r\nfetch('/api/with-custom-facets').then(myFacets => {\r\n // reusing the go() idiom\r\n go(manager, myFacets);\r\n})\r\n```\r\n\r\nbut I'd like to confirm if that's the case before investigating adding support.\r\n\r\n> bulletproof plugin registration code that is robust against the order in which the script tags load\r\n\r\nYes, I think what you wrote looks right to me! While it looks a little bit verbose compared to the second example, I'm hoping we can mitigate the cost of that during this API incubation phase by making it an easy-to-copy paste code snippet.\r\n\r\nI haven't heard of the GA queing pattern before, thanks for the example. I won't have time to implement of proof of concept in the next few weeks, but I took some time to think through the pros/cons to decide whether we may want to add this in a future release:\r\n\r\nI can see that this approach brings advantages\r\n\r\n- Plugin developers don't need to know the name of the datasette initialization event to start their plugin\r\n- Pushing a function to an array probably is easier (definitely more concise) than adding a document event listener\r\n- One less event listener sitting in memory\r\n\r\nIt also has some minor costs\r\n\r\n- A malicious plugin could choose to (or accidentally) mess with the order of the queue if multiple scripts are lined up\r\n- Some risk in encouraging people to mutate global state\r\n- (not a cost, more a moot point): changing this API may not make a meaningful difference if we're discussing whether people enter 2 vs 5 lines of code, especially if those lines are encapsulated by a function we provide (maybe something that's available on the `window` provided by Datasette as an inline script tag). \r\n\r\n\r\n", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 1651082214, "label": "feat: Javascript Plugin API (Custom panels, column menu items with JS actions)"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/datasette/issues/2073#issuecomment-1546119773", "issue_url": "https://api.github.com/repos/simonw/datasette/issues/2073", "id": 1546119773, "node_id": "IC_kwDOBm6k_c5cJ-pd", "user": {"value": 9599, "label": "simonw"}, "created_at": "2023-05-12T18:24:07Z", "updated_at": "2023-05-12T18:24:07Z", "author_association": "OWNER", "body": "Here's a demo of this breaking in Datasette Lite:\r\n\r\nhttps://lite.datasette.io/?sql=https://gist.github.com/simonw/261564c0ca01567df6eeb9b222b8be84&json=https%3A%2F%2Fcdn.jsdelivr.net%2Fnpm%2Fweb-features%2Findex.json#/data/baseline?_filter_column_1=is_baseline&_filter_op_1=exact&_filter_value_1=1&_filter_column_2=&_filter_op_2=notnull__1&_filter_value_2=1&_filter_column=&_filter_op=exact&_filter_value=&_sort=&_facet=is_baseline\r\n\r\n\"image\"\r\n\r\nHere's a SQL query that demonstrates the underlying issue:\r\n\r\n```sql\r\nselect 'working', count(*) from baseline where is_baseline = 1\r\nunion all\r\nselect 'broken', count(*) from baseline where is_baseline = '1'\r\n```\r\nhttps://lite.datasette.io/?sql=https://gist.github.com/simonw/261564c0ca01567df6eeb9b222b8be84&json=https%3A%2F%2Fcdn.jsdelivr.net%2Fnpm%2Fweb-features%2Findex.json#/data?sql=select+%27working%27%2C+count%28*%29+from+baseline+where+is_baseline+%3D+1%0Aunion+all%0Aselect+%27broken%27%2C+count%28*%29+from+baseline+where+is_baseline+%3D+%271%27\r\n\r\n\"image\"", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 1708030220, "label": "Faceting doesn't work against integer columns in views"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/datasette/issues/2073#issuecomment-1546117538", "issue_url": "https://api.github.com/repos/simonw/datasette/issues/2073", "id": 1546117538, "node_id": "IC_kwDOBm6k_c5cJ-Gi", "user": {"value": 9599, "label": "simonw"}, "created_at": "2023-05-12T18:21:38Z", "updated_at": "2023-05-12T18:21:38Z", "author_association": "OWNER", "body": "https://latest.datasette.io/fixtures doesn't currently have a view with any integer columns in it, making this bug harder to demonstrate there.\r\n\r\nI can't replicate the bug using https://datasette.io/content/plugins?_facet=stargazers_count&stargazers_count=3 - I would expect that not to work correctly.", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 1708030220, "label": "Faceting doesn't work against integer columns in views"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/sqlite-utils/issues/527#issuecomment-1540900733", "issue_url": "https://api.github.com/repos/simonw/sqlite-utils/issues/527", "id": 1540900733, "node_id": "IC_kwDOCGYnMM5b2Ed9", "user": {"value": 167893, "label": "mcarpenter"}, "created_at": "2023-05-09T21:15:05Z", "updated_at": "2023-05-09T21:15:05Z", "author_association": "CONTRIBUTOR", "body": "Sorry, I completely missed your first comment whilst on Easter break.\r\n\r\nThis looks like a good practical compromise before v4. Thanks!", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 1578790070, "label": "`Table.convert()` skips falsey values"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/datasette/issues/2070#issuecomment-1540494121", "issue_url": "https://api.github.com/repos/simonw/datasette/issues/2070", "id": 1540494121, "node_id": "IC_kwDOBm6k_c5b0hMp", "user": {"value": 9599, "label": "simonw"}, "created_at": "2023-05-09T16:25:00Z", "updated_at": "2023-05-09T16:25:00Z", "author_association": "OWNER", "body": "Can now be used here: https://github.com/simonw/datasette/actions/workflows/deploy-branch-preview.yml", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 1702354223, "label": "Mechanism for deploying a preview of a branch using Vercel"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/datasette/issues/2070#issuecomment-1540491751", "issue_url": "https://api.github.com/repos/simonw/datasette/issues/2070", "id": 1540491751, "node_id": "IC_kwDOBm6k_c5b0gnn", "user": {"value": 9599, "label": "simonw"}, "created_at": "2023-05-09T16:23:12Z", "updated_at": "2023-05-09T16:23:12Z", "author_association": "OWNER", "body": "Added a actions `BRANCH_PREVIEW_VERCEL_TOKEN` secret to this repository.", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 1702354223, "label": "Mechanism for deploying a preview of a branch using Vercel"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/sqlite-utils/pull/537#issuecomment-1539157643", "issue_url": "https://api.github.com/repos/simonw/sqlite-utils/issues/537", "id": 1539157643, "node_id": "IC_kwDOCGYnMM5bva6L", "user": {"value": 9599, "label": "simonw"}, "created_at": "2023-05-08T22:45:09Z", "updated_at": "2023-05-08T22:45:21Z", "author_association": "OWNER", "body": "Here's an example from the new tests:\r\n\r\nhttps://github.com/simonw/sqlite-utils/blob/a75abeb61b91a28650d3b9933e7ec80ad0d92529/tests/test_create.py#L291-L307", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 1665200812, "label": "Support self-referencing FKs in `Table.create`"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/sqlite-utils/issues/448#issuecomment-1539109816", "issue_url": "https://api.github.com/repos/simonw/sqlite-utils/issues/448", "id": 1539109816, "node_id": "IC_kwDOCGYnMM5bvPO4", "user": {"value": 9599, "label": "simonw"}, "created_at": "2023-05-08T22:01:00Z", "updated_at": "2023-05-08T22:01:00Z", "author_association": "OWNER", "body": "This is being handled in:\r\n- #520", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 1279144769, "label": "Reading rows from a file => AttributeError: '_io.StringIO' object has no attribute 'readinto'"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/sqlite-utils/issues/520#issuecomment-1539109587", "issue_url": "https://api.github.com/repos/simonw/sqlite-utils/issues/520", "id": 1539109587, "node_id": "IC_kwDOCGYnMM5bvPLT", "user": {"value": 9599, "label": "simonw"}, "created_at": "2023-05-08T22:00:46Z", "updated_at": "2023-05-08T22:00:46Z", "author_association": "OWNER", "body": "> Hey, isn't this essentially the same issue as #448 ?\r\n\r\nYes it is, good catch!", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 1516644980, "label": "rows_from_file() raises confusing error if file-like object is not in binary mode"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/sqlite-utils/issues/525#issuecomment-1539108140", "issue_url": "https://api.github.com/repos/simonw/sqlite-utils/issues/525", "id": 1539108140, "node_id": "IC_kwDOCGYnMM5bvO0s", "user": {"value": 9599, "label": "simonw"}, "created_at": "2023-05-08T21:59:41Z", "updated_at": "2023-05-08T21:59:41Z", "author_association": "OWNER", "body": "That original example passes against `main` now.", "reactions": "{\"total_count\": 1, \"+1\": 1, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 1575131737, "label": "Repeated calls to `Table.convert()` fail"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/sqlite-utils/issues/525#issuecomment-1539101853", "issue_url": "https://api.github.com/repos/simonw/sqlite-utils/issues/525", "id": 1539101853, "node_id": "IC_kwDOCGYnMM5bvNSd", "user": {"value": 9599, "label": "simonw"}, "created_at": "2023-05-08T21:52:44Z", "updated_at": "2023-05-08T21:52:44Z", "author_association": "OWNER", "body": "I like the `lambda-{uuid}` idea.", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 1575131737, "label": "Repeated calls to `Table.convert()` fail"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/sqlite-utils/issues/514#issuecomment-1539100300", "issue_url": "https://api.github.com/repos/simonw/sqlite-utils/issues/514", "id": 1539100300, "node_id": "IC_kwDOCGYnMM5bvM6M", "user": {"value": 9599, "label": "simonw"}, "created_at": "2023-05-08T21:50:51Z", "updated_at": "2023-05-08T21:50:51Z", "author_association": "OWNER", "body": "Seeing as `sqlite-utils` doesn't currently provide mechanisms for adding `check` constraints like this I'm going to leave this - I'm happy with the fix I put in for the `not null` constraints.", "reactions": "{\"total_count\": 1, \"+1\": 1, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 1465194249, "label": "upsert of new row with check constraints fails"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/sqlite-utils/issues/514#issuecomment-1539099703", "issue_url": "https://api.github.com/repos/simonw/sqlite-utils/issues/514", "id": 1539099703, "node_id": "IC_kwDOCGYnMM5bvMw3", "user": {"value": 9599, "label": "simonw"}, "created_at": "2023-05-08T21:50:06Z", "updated_at": "2023-05-08T21:50:06Z", "author_association": "OWNER", "body": "Applying the fix from the PR here doesn't fix the above problem either:\r\n- https://github.com/simonw/sqlite-utils/pull/515\r\n\r\nSo it looks like these kinds of `check` constraints currently aren't compatible with how `upsert()` works.", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 1465194249, "label": "upsert of new row with check constraints fails"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/sqlite-utils/issues/514#issuecomment-1539094287", "issue_url": "https://api.github.com/repos/simonw/sqlite-utils/issues/514", "id": 1539094287, "node_id": "IC_kwDOCGYnMM5bvLcP", "user": {"value": 9599, "label": "simonw"}, "created_at": "2023-05-08T21:44:11Z", "updated_at": "2023-05-08T21:44:11Z", "author_association": "OWNER", "body": "OK, this fails silently:\r\n```python\r\nimport sqlite_utils\r\ndb = sqlite_utils.Database(memory=True)\r\ndb.execute('''CREATE TABLE employees (\r\n id INTEGER PRIMARY KEY,\r\n name TEXT,\r\n age INTEGER,\r\n salary REAL,\r\n CHECK (salary is not null and salary > 0)\r\n);''')\r\ndb[\"employees\"].upsert({\"id\": 1, \"name\": \"Bob\"}, pk=\"id\")\r\nlist(db[\"employees\"].rows)\r\n````\r\nIt outputs:\r\n```python\r\n[]\r\n```", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 1465194249, "label": "upsert of new row with check constraints fails"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/sqlite-utils/issues/514#issuecomment-1539079507", "issue_url": "https://api.github.com/repos/simonw/sqlite-utils/issues/514", "id": 1539079507, "node_id": "IC_kwDOCGYnMM5bvH1T", "user": {"value": 9599, "label": "simonw"}, "created_at": "2023-05-08T21:28:37Z", "updated_at": "2023-05-08T21:28:37Z", "author_association": "OWNER", "body": "> This means that a table with NON NULL (or other constraint) columns that aren't part of the pkey can't have new rows upserted.\r\n\r\nHuh... on that basis, it's possible my fix in https://github.com/simonw/sqlite-utils/commit/2376c452a56b0c3e75e7ca698273434e32945304 is incomplete. I only covered the 'not null' case.", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 1465194249, "label": "upsert of new row with check constraints fails"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/sqlite-utils/issues/514#issuecomment-1539078429", "issue_url": "https://api.github.com/repos/simonw/sqlite-utils/issues/514", "id": 1539078429, "node_id": "IC_kwDOCGYnMM5bvHkd", "user": {"value": 9599, "label": "simonw"}, "created_at": "2023-05-08T21:27:40Z", "updated_at": "2023-05-08T21:27:40Z", "author_association": "OWNER", "body": "Dupe of:\r\n- #538", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 1465194249, "label": "upsert of new row with check constraints fails"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/sqlite-utils/pull/515#issuecomment-1539077777", "issue_url": "https://api.github.com/repos/simonw/sqlite-utils/issues/515", "id": 1539077777, "node_id": "IC_kwDOCGYnMM5bvHaR", "user": {"value": 9599, "label": "simonw"}, "created_at": "2023-05-08T21:27:10Z", "updated_at": "2023-05-08T21:27:10Z", "author_association": "OWNER", "body": "I should have spotted this PR before I shipped my own fix! https://github.com/simonw/sqlite-utils/commit/2376c452a56b0c3e75e7ca698273434e32945304", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 1465194930, "label": "upsert new rows with constraints, fixes #514"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/sqlite-utils/pull/519#issuecomment-1539058795", "issue_url": "https://api.github.com/repos/simonw/sqlite-utils/issues/519", "id": 1539058795, "node_id": "IC_kwDOCGYnMM5bvCxr", "user": {"value": 9599, "label": "simonw"}, "created_at": "2023-05-08T21:12:52Z", "updated_at": "2023-05-08T21:12:52Z", "author_association": "OWNER", "body": "This is a really neat fix, thank you.", "reactions": "{\"total_count\": 1, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 1, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 1505568103, "label": "Fixes breaking DEFAULT values"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/sqlite-utils/pull/537#issuecomment-1539055393", "issue_url": "https://api.github.com/repos/simonw/sqlite-utils/issues/537", "id": 1539055393, "node_id": "IC_kwDOCGYnMM5bvB8h", "user": {"value": 9599, "label": "simonw"}, "created_at": "2023-05-08T21:10:06Z", "updated_at": "2023-05-08T21:10:06Z", "author_association": "OWNER", "body": "Thanks!", "reactions": "{\"total_count\": 1, \"+1\": 1, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 1665200812, "label": "Support self-referencing FKs in `Table.create`"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/sqlite-utils/pull/528#issuecomment-1539053230", "issue_url": "https://api.github.com/repos/simonw/sqlite-utils/issues/528", "id": 1539053230, "node_id": "IC_kwDOCGYnMM5bvBau", "user": {"value": 9599, "label": "simonw"}, "created_at": "2023-05-08T21:08:23Z", "updated_at": "2023-05-08T21:08:23Z", "author_association": "OWNER", "body": "I fixed this in:\r\n- #527\r\n\r\nWill fully remove this misfeature in:\r\n- #542", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 1578793661, "label": "Enable `Table.convert()` on falsey values"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/sqlite-utils/issues/542#issuecomment-1539052467", "issue_url": "https://api.github.com/repos/simonw/sqlite-utils/issues/542", "id": 1539052467, "node_id": "IC_kwDOCGYnMM5bvBOz", "user": {"value": 9599, "label": "simonw"}, "created_at": "2023-05-08T21:07:41Z", "updated_at": "2023-05-08T21:07:41Z", "author_association": "OWNER", "body": "Relevant commits (will mostly revert these):\r\n- https://github.com/simonw/sqlite-utils/commit/455c35b512895c19bf922c2b804d750d27cb8dbd\r\n- https://github.com/simonw/sqlite-utils/commit/e0ec4c345129996011951e400388fd74865f65a2", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 1700936245, "label": "Remove `skip_false=True` and `--no-skip-false` in `sqlite-utils` 4.0"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/sqlite-utils/issues/527#issuecomment-1539051724", "issue_url": "https://api.github.com/repos/simonw/sqlite-utils/issues/527", "id": 1539051724, "node_id": "IC_kwDOCGYnMM5bvBDM", "user": {"value": 9599, "label": "simonw"}, "created_at": "2023-05-08T21:07:04Z", "updated_at": "2023-05-08T21:07:04Z", "author_association": "OWNER", "body": "Updated documentation:\r\n- https://sqlite-utils.datasette.io/en/latest/python-api.html#converting-data-in-columns\r\n- https://sqlite-utils.datasette.io/en/latest/cli.html#converting-data-in-columns\r\n- https://sqlite-utils.datasette.io/en/latest/cli-reference.html#convert", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 1578790070, "label": "`Table.convert()` skips falsey values"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/sqlite-utils/issues/527#issuecomment-1539035838", "issue_url": "https://api.github.com/repos/simonw/sqlite-utils/issues/527", "id": 1539035838, "node_id": "IC_kwDOCGYnMM5bu9K-", "user": {"value": 9599, "label": "simonw"}, "created_at": "2023-05-08T20:55:00Z", "updated_at": "2023-05-08T20:55:00Z", "author_association": "OWNER", "body": "I'm going to go with `--no-skip-false` as the CLI option. It's ugly, but this whole thing is ugly. I'm going to make a note to remove this misfeature in `sqlite-utils` 4.", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 1578790070, "label": "`Table.convert()` skips falsey values"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/sqlite-utils/issues/527#issuecomment-1539033736", "issue_url": "https://api.github.com/repos/simonw/sqlite-utils/issues/527", "id": 1539033736, "node_id": "IC_kwDOCGYnMM5bu8qI", "user": {"value": 9599, "label": "simonw"}, "created_at": "2023-05-08T20:52:51Z", "updated_at": "2023-05-08T20:52:51Z", "author_association": "OWNER", "body": "OK, I implemented that at the Python API level. I need to decide how it should work for the `sqlite-utils convert` command too: https://sqlite-utils.datasette.io/en/stable/cli-reference.html#convert", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 1578790070, "label": "`Table.convert()` skips falsey values"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/sqlite-utils/issues/530#issuecomment-1539018912", "issue_url": "https://api.github.com/repos/simonw/sqlite-utils/issues/530", "id": 1539018912, "node_id": "IC_kwDOCGYnMM5bu5Cg", "user": {"value": 9599, "label": "simonw"}, "created_at": "2023-05-08T20:39:00Z", "updated_at": "2023-05-08T20:39:00Z", "author_association": "OWNER", "body": "I think the natural place to add these in the Python library API would be https://sqlite-utils.datasette.io/en/stable/python-api.html#adding-foreign-key-constraints - maybe something like this:\r\n\r\n```python\r\ndb[\"books\"].add_foreign_key(\"author_id\", \"authors\", \"id\", on_delete=RESTRICT)\r\n```\r\n\r\nThen for the CLI tool could be added to https://sqlite-utils.datasette.io/en/stable/cli-reference.html#add-foreign-key\r\n\r\n```\r\nsqlite-utils add-foreign-key my.db books author_id authors id --on-update SET_NULL\r\n```\r\nI wouldn't support these for the other methods of adding foreign keys - `foreign_keys(...)` for the various `.insert()` etc methods and the `add_foreign_keys(...)` bulk menthod.", "reactions": "{\"total_count\": 1, \"+1\": 0, \"-1\": 0, \"laugh\": 1, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 1595340692, "label": "add ability to configure \"on delete\" and \"on update\" attributes of foreign keys:"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/sqlite-utils/issues/530#issuecomment-1539015064", "issue_url": "https://api.github.com/repos/simonw/sqlite-utils/issues/530", "id": 1539015064, "node_id": "IC_kwDOCGYnMM5bu4GY", "user": {"value": 9599, "label": "simonw"}, "created_at": "2023-05-08T20:35:07Z", "updated_at": "2023-05-08T20:35:07Z", "author_association": "OWNER", "body": "Wow, this is a neat feature I didn't know about. Looks like there are a bunch of options:\r\n\r\n- NO ACTION (default)\r\n- RESTRICT: application is prohibited from deleting a parent key when there exists one or more child keys mapped to it\r\n- SET NULL: when a parent key is deleted the child key columns of all rows in the child table that mapped to the parent key are set to contain SQL NULL values\r\n- SET DEFAULT: set a specific default\r\n- CASCADE: propagates the delete or update operation on the parent key to each dependent child key", "reactions": "{\"total_count\": 1, \"+1\": 1, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 1595340692, "label": "add ability to configure \"on delete\" and \"on update\" attributes of foreign keys:"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/sqlite-utils/issues/532#issuecomment-1539009453", "issue_url": "https://api.github.com/repos/simonw/sqlite-utils/issues/532", "id": 1539009453, "node_id": "IC_kwDOCGYnMM5bu2ut", "user": {"value": 9599, "label": "simonw"}, "created_at": "2023-05-08T20:30:29Z", "updated_at": "2023-05-08T20:30:42Z", "author_association": "OWNER", "body": "Here's an improvement:\r\n```\r\n% sqlite-utils insert /tmp/b.db blah /tmp/blah.txt\r\n [####################################] 100%\r\nError: Invalid JSON - use --csv for CSV or --tsv for TSV files\r\n\r\nJSON error: Expecting value: line 1 column 1 (char 0)\r\n```", "reactions": "{\"total_count\": 1, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 1, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 1620254998, "label": "Show more information when JSON can't be imported with sqlite-utils insert"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/sqlite-utils/issues/532#issuecomment-1539006509", "issue_url": "https://api.github.com/repos/simonw/sqlite-utils/issues/532", "id": 1539006509, "node_id": "IC_kwDOCGYnMM5bu2At", "user": {"value": 9599, "label": "simonw"}, "created_at": "2023-05-08T20:28:56Z", "updated_at": "2023-05-08T20:28:56Z", "author_association": "OWNER", "body": "Was this a newline-delimited JSON file perhaps?", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 1620254998, "label": "Show more information when JSON can't be imported with sqlite-utils insert"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/sqlite-utils/issues/538#issuecomment-1538975545", "issue_url": "https://api.github.com/repos/simonw/sqlite-utils/issues/538", "id": 1538975545, "node_id": "IC_kwDOCGYnMM5buuc5", "user": {"value": 1231935, "label": "xavdid"}, "created_at": "2023-05-08T20:06:35Z", "updated_at": "2023-05-08T20:06:35Z", "author_association": "NONE", "body": "perfect, thank you!", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 1695428235, "label": "`table.upsert_all` fails to write rows when `not_null` is present"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/sqlite-utils/issues/541#issuecomment-1538963959", "issue_url": "https://api.github.com/repos/simonw/sqlite-utils/issues/541", "id": 1538963959, "node_id": "IC_kwDOCGYnMM5burn3", "user": {"value": 9599, "label": "simonw"}, "created_at": "2023-05-08T19:59:34Z", "updated_at": "2023-05-08T19:59:34Z", "author_association": "OWNER", "body": "There are 8 failing tests left:\r\n\r\n```\r\n==== short test summary info ====\r\nFAILED tests/test_cli_memory.py::test_memory_csv[False-test] - pytest.PytestUnraisableExceptionWarning: Exception ignored in: <_io.FileIO [closed]>\r\nFAILED tests/test_cli_memory.py::test_memory_csv[False-t] - pytest.PytestUnraisableExceptionWarning: Exception ignored in: <_io.FileIO [closed]>\r\nFAILED tests/test_cli_memory.py::test_memory_csv[False-t1] - pytest.PytestUnraisableExceptionWarning: Exception ignored in: <_io.FileIO [closed]>\r\nFAILED tests/test_cli_memory.py::test_memory_tsv[False] - pytest.PytestUnraisableExceptionWarning: Exception ignored in: <_io.FileIO [closed]>\r\nFAILED tests/test_cli_memory.py::test_memory_dump[extra_args0] - pytest.PytestUnraisableExceptionWarning: Exception ignored in: <_io.FileIO [closed]>\r\nFAILED tests/test_cli_memory.py::test_memory_two_files_with_same_stem - pytest.PytestUnraisableExceptionWarning: Exception ignored in: <_io.FileIO [closed]>\r\nFAILED tests/test_recipes.py::test_dateparse_errors[None-parsedate] - pytest.PytestUnraisableExceptionWarning: Exception ignored in: .convert_value at 0x106bcaca0>\r\nFAILED tests/test_recipes.py::test_dateparse_errors[None-parsedatetime] - pytest.PytestUnraisableExceptionWarning: Exception ignored in: .convert_value at 0x106bc9620>\r\nERROR tests/test_cli.py::test_upsert_analyze - pytest.PytestUnraisableExceptionWarning: Exception ignored in: <_io.FileIO [closed]>\r\n==== 8 failed, 894 passed, 4 skipped, 1 error in 6.27s ====\r\n```\r\nFull traceback here: https://gist.github.com/simonw/b40b3e814729d6c02a0302a84ce54d9e", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 1700840265, "label": "Get tests to pass with `pytest -Werror`"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/sqlite-utils/issues/534#issuecomment-1538933540", "issue_url": "https://api.github.com/repos/simonw/sqlite-utils/issues/534", "id": 1538933540, "node_id": "IC_kwDOCGYnMM5bukMk", "user": {"value": 9599, "label": "simonw"}, "created_at": "2023-05-08T19:34:37Z", "updated_at": "2023-05-08T19:34:37Z", "author_association": "OWNER", "body": "On macOS this shows the same warning:\r\n```\r\n% python -Wdefault $(which sqlite-utils) insert dogs.db dogs dogs.csv --csv\r\n [############------------------------] 35%\r\n [####################################] 100%/Users/simon/Dropbox/Development/sqlite-utils/sqlite_utils/cli.py:1187: ResourceWarning: unclosed file <_io.TextIOWrapper name='dogs.csv' encoding='utf-8-sig'>\r\n insert_upsert_implementation(\r\nResourceWarning: Enable tracemalloc to get the object allocation traceback\r\n```\r\nThe file itself is a `click.File` which is automatically closed - https://click.palletsprojects.com/en/8.1.x/api/#click.File - but it looks like it's the `_io.TextIOWrapper` which is not being closed:\r\n\r\nhttps://github.com/simonw/sqlite-utils/blob/2376c452a56b0c3e75e7ca698273434e32945304/sqlite_utils/cli.py#L949-L956", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 1622640374, "label": " ResourceWarning: unclosed file"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/sqlite-utils/issues/538#issuecomment-1538921774", "issue_url": "https://api.github.com/repos/simonw/sqlite-utils/issues/538", "id": 1538921774, "node_id": "IC_kwDOCGYnMM5buhUu", "user": {"value": 9599, "label": "simonw"}, "created_at": "2023-05-08T19:24:41Z", "updated_at": "2023-05-08T19:24:41Z", "author_association": "OWNER", "body": "That fix seems to work!", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 1695428235, "label": "`table.upsert_all` fails to write rows when `not_null` is present"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/sqlite-utils/issues/538#issuecomment-1538910894", "issue_url": "https://api.github.com/repos/simonw/sqlite-utils/issues/538", "id": 1538910894, "node_id": "IC_kwDOCGYnMM5buequ", "user": {"value": 9599, "label": "simonw"}, "created_at": "2023-05-08T19:16:52Z", "updated_at": "2023-05-08T19:17:00Z", "author_association": "OWNER", "body": "How about if I had logic which checked that all not-null columns were provided in the call to `upsert_all()` - and if they were, modified the `INSERT OR IGNORE INTO` to include a placeholder value for those columns that would then be fixed by the later `UPDATE`?\r\n\r\nSomething like this:\r\n\r\n```python\r\n[\r\n ('INSERT OR IGNORE INTO [comments]([id], name) VALUES(?, ?);', [1, '']),\r\n ('UPDATE [comments] SET [name] = ? WHERE [id] = ?', ['Cleo', 1])\r\n]\r\n```", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 1695428235, "label": "`table.upsert_all` fails to write rows when `not_null` is present"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/sqlite-utils/issues/538#issuecomment-1538903556", "issue_url": "https://api.github.com/repos/simonw/sqlite-utils/issues/538", "id": 1538903556, "node_id": "IC_kwDOCGYnMM5buc4E", "user": {"value": 9599, "label": "simonw"}, "created_at": "2023-05-08T19:11:24Z", "updated_at": "2023-05-08T19:13:23Z", "author_association": "OWNER", "body": "I could detect if this happens using `cursor.rowcount` - not sure how I would recover from it though.\r\n\r\nThis would also require some major re-engineering, since currently it all works by generating a list of SQL queries in advance and applying them inside a loop in `.insert_chunk()`:\r\n\r\nhttps://github.com/simonw/sqlite-utils/blob/80763edaa2bdaf1113717378b8d62075c4dcbcfb/sqlite_utils/db.py#L2839-L2878\r\n", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 1695428235, "label": "`table.upsert_all` fails to write rows when `not_null` is present"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/sqlite-utils/issues/538#issuecomment-1538893329", "issue_url": "https://api.github.com/repos/simonw/sqlite-utils/issues/538", "id": 1538893329, "node_id": "IC_kwDOCGYnMM5buaYR", "user": {"value": 9599, "label": "simonw"}, "created_at": "2023-05-08T19:04:47Z", "updated_at": "2023-05-08T19:04:47Z", "author_association": "OWNER", "body": "This feels like a fundamental flaw in the way upserts are implemented by `sqlite-utils`.\r\n\r\nOne fix would be to switch to using the `UPSERT` feature in SQLite: https://www.sqlite.org/lang_UPSERT.html\r\n\r\nBut...\r\n\r\n> UPSERT syntax was added to SQLite with version 3.24.0 (2018-06-04).\r\n\r\nI still want to support SQLite versions earlier than that.", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 1695428235, "label": "`table.upsert_all` fails to write rows when `not_null` is present"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/sqlite-utils/issues/538#issuecomment-1538889482", "issue_url": "https://api.github.com/repos/simonw/sqlite-utils/issues/538", "id": 1538889482, "node_id": "IC_kwDOCGYnMM5buZcK", "user": {"value": 9599, "label": "simonw"}, "created_at": "2023-05-08T19:02:38Z", "updated_at": "2023-05-08T19:02:38Z", "author_association": "OWNER", "body": "Here's the code at fault:\r\nhttps://github.com/simonw/sqlite-utils/blob/80763edaa2bdaf1113717378b8d62075c4dcbcfb/sqlite_utils/db.py#L2774-L2788", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 1695428235, "label": "`table.upsert_all` fails to write rows when `not_null` is present"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/sqlite-utils/issues/538#issuecomment-1538887361", "issue_url": "https://api.github.com/repos/simonw/sqlite-utils/issues/538", "id": 1538887361, "node_id": "IC_kwDOCGYnMM5buY7B", "user": {"value": 9599, "label": "simonw"}, "created_at": "2023-05-08T19:01:20Z", "updated_at": "2023-05-08T19:01:20Z", "author_association": "OWNER", "body": "Here's the problem:\r\n```python\r\nimport sqlite3\r\ndb = sqlite3.connect(\":memory:\")\r\ndb.execute('create table foo (id integer primary key, name not null)')\r\ndb.execute('insert into foo (id) values (1)')\r\n```\r\nProduces:\r\n```\r\nIntegrityError: NOT NULL constraint failed: foo.name\r\n```\r\nBut this:\r\n```python\r\ndb.execute('insert or ignore into foo (id) values (1)')\r\n```\r\nCompletes without an exception.", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 1695428235, "label": "`table.upsert_all` fails to write rows when `not_null` is present"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/sqlite-utils/issues/538#issuecomment-1538801855", "issue_url": "https://api.github.com/repos/simonw/sqlite-utils/issues/538", "id": 1538801855, "node_id": "IC_kwDOCGYnMM5buEC_", "user": {"value": 9599, "label": "simonw"}, "created_at": "2023-05-08T18:00:17Z", "updated_at": "2023-05-08T18:00:17Z", "author_association": "OWNER", "body": "From time in the debugger, after creating the table it ends up doing this:\r\n```\r\n(Pdb) queries_and_params\r\n[\r\n ('INSERT OR IGNORE INTO [comments]([id]) VALUES(?);', [1]),\r\n ('UPDATE [comments] SET [name] = ? WHERE [id] = ?', ['Cleo', 1])\r\n]\r\n```", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 1695428235, "label": "`table.upsert_all` fails to write rows when `not_null` is present"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/sqlite-utils/issues/538#issuecomment-1538793817", "issue_url": "https://api.github.com/repos/simonw/sqlite-utils/issues/538", "id": 1538793817, "node_id": "IC_kwDOCGYnMM5buCFZ", "user": {"value": 9599, "label": "simonw"}, "created_at": "2023-05-08T17:55:10Z", "updated_at": "2023-05-08T17:55:10Z", "author_association": "OWNER", "body": "Confirmed - I added this test and it fails:\r\n```python\r\ndef test_upsert_all_not_null(fresh_db):\r\n # https://github.com/simonw/sqlite-utils/issues/538\r\n fresh_db[\"comments\"].upsert_all(\r\n [{\"id\": 1, \"name\": \"Cleo\"}],\r\n pk=\"id\",\r\n not_null=[\"name\"],\r\n )\r\n assert list(fresh_db[\"comments\"].rows) == [{\"id\": 1, \"name\": \"Cleo\"}]\r\n```", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 1695428235, "label": "`table.upsert_all` fails to write rows when `not_null` is present"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/sqlite-utils/issues/540#issuecomment-1537744000", "issue_url": "https://api.github.com/repos/simonw/sqlite-utils/issues/540", "id": 1537744000, "node_id": "IC_kwDOCGYnMM5bqByA", "user": {"value": 42327, "label": "pquentin"}, "created_at": "2023-05-08T04:56:12Z", "updated_at": "2023-05-08T04:56:12Z", "author_association": "NONE", "body": "Hey @simonw, urllib3 maintainer here :wave:\r\n\r\nSorry for breaking your CI. I understand you may prefer to pin the Python version, but note that specifying just `python: \"3\"` will get you the latest. We use that in urllib3: https://github.com/urllib3/urllib3/blob/main/.readthedocs.yml\r\n\r\nI can open PRs to sqlite-utils / datasette if you're interested", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 1699184583, "label": "sphinx.builders.linkcheck build error"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/sqlite-utils/issues/539#issuecomment-1537514610", "issue_url": "https://api.github.com/repos/simonw/sqlite-utils/issues/539", "id": 1537514610, "node_id": "IC_kwDOCGYnMM5bpJxy", "user": {"value": 9599, "label": "simonw"}, "created_at": "2023-05-07T18:43:24Z", "updated_at": "2023-05-07T18:43:24Z", "author_association": "OWNER", "body": "Documentation:\r\n- https://sqlite-utils.datasette.io/en/latest/cli.html#returning-raw-data-such-as-binary-content\r\n- https://sqlite-utils.datasette.io/en/latest/cli-reference.html#query\r\n- https://sqlite-utils.datasette.io/en/latest/cli-reference.html#memory", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 1699174055, "label": "`--raw-lines` option, like `--raw` for multiple lines"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/sqlite-utils/issues/540#issuecomment-1537514069", "issue_url": "https://api.github.com/repos/simonw/sqlite-utils/issues/540", "id": 1537514069, "node_id": "IC_kwDOCGYnMM5bpJpV", "user": {"value": 9599, "label": "simonw"}, "created_at": "2023-05-07T18:40:18Z", "updated_at": "2023-05-07T18:40:18Z", "author_association": "OWNER", "body": "https://docs.readthedocs.io/en/stable/config-file/v2.html suggests:\r\n\r\n```yaml\r\nbuild:\r\n os: ubuntu-22.04\r\n tools:\r\n python: \"3.11\"\r\n```", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 1699184583, "label": "sphinx.builders.linkcheck build error"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/sqlite-utils/issues/540#issuecomment-1537513912", "issue_url": "https://api.github.com/repos/simonw/sqlite-utils/issues/540", "id": 1537513912, "node_id": "IC_kwDOCGYnMM5bpJm4", "user": {"value": 9599, "label": "simonw"}, "created_at": "2023-05-07T18:39:29Z", "updated_at": "2023-05-07T18:39:29Z", "author_association": "OWNER", "body": "https://readthedocs.org/projects/sqlite-utils/builds/20513034/ said:\r\n\r\n> Problem in your project's configuration. Invalid \"python.version\": expected one of (2, 2.7, 3, 3.5, 3.6, 3.7, 3.8, pypy3.5), got 3.11", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 1699184583, "label": "sphinx.builders.linkcheck build error"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/sqlite-utils/issues/540#issuecomment-1537513653", "issue_url": "https://api.github.com/repos/simonw/sqlite-utils/issues/540", "id": 1537513653, "node_id": "IC_kwDOCGYnMM5bpJi1", "user": {"value": 9599, "label": "simonw"}, "created_at": "2023-05-07T18:37:59Z", "updated_at": "2023-05-07T18:38:19Z", "author_association": "OWNER", "body": "Useful comment here:\r\n\r\n- https://github.com/urllib3/urllib3/issues/2168#issuecomment-1537360928\r\n\r\n> I faced the same issue. I switched to a different Python kernel (3.11.2) and it worked.", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 1699184583, "label": "sphinx.builders.linkcheck build error"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/sqlite-utils/issues/539#issuecomment-1537507676", "issue_url": "https://api.github.com/repos/simonw/sqlite-utils/issues/539", "id": 1537507676, "node_id": "IC_kwDOCGYnMM5bpIFc", "user": {"value": 9599, "label": "simonw"}, "created_at": "2023-05-07T18:09:43Z", "updated_at": "2023-05-07T18:09:54Z", "author_association": "OWNER", "body": "This worked:\r\n\r\n```bash\r\nsqlite-utils memory /tmp/books3.json:nl \\\r\n 'select name from books3' --raw-lines > titles.txt\r\n```", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 1699174055, "label": "`--raw-lines` option, like `--raw` for multiple lines"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/sqlite-utils/issues/539#issuecomment-1537507525", "issue_url": "https://api.github.com/repos/simonw/sqlite-utils/issues/539", "id": 1537507525, "node_id": "IC_kwDOCGYnMM5bpIDF", "user": {"value": 9599, "label": "simonw"}, "created_at": "2023-05-07T18:09:09Z", "updated_at": "2023-05-07T18:09:09Z", "author_association": "OWNER", "body": "I'm tempted to upgrade `--raw` to do this instead, but that would be a breaking change.", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 1699174055, "label": "`--raw-lines` option, like `--raw` for multiple lines"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/sqlite-utils/issues/539#issuecomment-1537507394", "issue_url": "https://api.github.com/repos/simonw/sqlite-utils/issues/539", "id": 1537507394, "node_id": "IC_kwDOCGYnMM5bpIBC", "user": {"value": 9599, "label": "simonw"}, "created_at": "2023-05-07T18:08:44Z", "updated_at": "2023-05-07T18:08:44Z", "author_association": "OWNER", "body": "Prototype:\r\n```diff\r\ndiff --git a/docs/cli-reference.rst b/docs/cli-reference.rst\r\nindex 153e5f9..c830518 100644\r\n--- a/docs/cli-reference.rst\r\n+++ b/docs/cli-reference.rst\r\n@@ -124,6 +124,7 @@ See :ref:`cli_query`.\r\n --json-cols Detect JSON cols and output them as JSON, not\r\n escaped strings\r\n -r, --raw Raw output, first column of first row\r\n+ --raw-lines Raw output, first column of each row\r\n -p, --param ... Named :parameters for SQL query\r\n --functions TEXT Python code defining one or more custom SQL\r\n functions\r\n@@ -192,6 +193,7 @@ See :ref:`cli_memory`.\r\n --json-cols Detect JSON cols and output them as JSON, not\r\n escaped strings\r\n -r, --raw Raw output, first column of first row\r\n+ --raw-lines Raw output, first column of each row\r\n -p, --param ... Named :parameters for SQL query\r\n --encoding TEXT Character encoding for CSV input, defaults to\r\n utf-8\r\ndiff --git a/sqlite_utils/cli.py b/sqlite_utils/cli.py\r\nindex d25b1df..da0e4b6 100644\r\n--- a/sqlite_utils/cli.py\r\n+++ b/sqlite_utils/cli.py\r\n@@ -1653,6 +1653,7 @@ def drop_view(path, view, ignore, load_extension):\r\n )\r\n @output_options\r\n @click.option(\"-r\", \"--raw\", is_flag=True, help=\"Raw output, first column of first row\")\r\n+@click.option(\"--raw-lines\", is_flag=True, help=\"Raw output, first column of each row\")\r\n @click.option(\r\n \"-p\",\r\n \"--param\",\r\n@@ -1677,6 +1678,7 @@ def query(\r\n fmt,\r\n json_cols,\r\n raw,\r\n+ raw_lines,\r\n param,\r\n load_extension,\r\n functions,\r\n@@ -1700,7 +1702,19 @@ def query(\r\n _register_functions(db, functions)\r\n \r\n _execute_query(\r\n- db, sql, param, raw, table, csv, tsv, no_headers, fmt, nl, arrays, json_cols\r\n+ db,\r\n+ sql,\r\n+ param,\r\n+ raw,\r\n+ raw_lines,\r\n+ table,\r\n+ csv,\r\n+ tsv,\r\n+ no_headers,\r\n+ fmt,\r\n+ nl,\r\n+ arrays,\r\n+ json_cols,\r\n )\r\n \r\n \r\n@@ -1728,6 +1742,7 @@ def query(\r\n )\r\n @output_options\r\n @click.option(\"-r\", \"--raw\", is_flag=True, help=\"Raw output, first column of first row\")\r\n+@click.option(\"--raw-lines\", is_flag=True, help=\"Raw output, first column of each row\")\r\n @click.option(\r\n \"-p\",\r\n \"--param\",\r\n@@ -1773,6 +1788,7 @@ def memory(\r\n fmt,\r\n json_cols,\r\n raw,\r\n+ raw_lines,\r\n param,\r\n encoding,\r\n no_detect_types,\r\n@@ -1879,12 +1895,36 @@ def memory(\r\n _register_functions(db, functions)\r\n \r\n _execute_query(\r\n- db, sql, param, raw, table, csv, tsv, no_headers, fmt, nl, arrays, json_cols\r\n+ db,\r\n+ sql,\r\n+ param,\r\n+ raw,\r\n+ raw_lines,\r\n+ table,\r\n+ csv,\r\n+ tsv,\r\n+ no_headers,\r\n+ fmt,\r\n+ nl,\r\n+ arrays,\r\n+ json_cols,\r\n )\r\n \r\n \r\n def _execute_query(\r\n- db, sql, param, raw, table, csv, tsv, no_headers, fmt, nl, arrays, json_cols\r\n+ db,\r\n+ sql,\r\n+ param,\r\n+ raw,\r\n+ raw_lines,\r\n+ table,\r\n+ csv,\r\n+ tsv,\r\n+ no_headers,\r\n+ fmt,\r\n+ nl,\r\n+ arrays,\r\n+ json_cols,\r\n ):\r\n with db.conn:\r\n try:\r\n@@ -1903,6 +1943,13 @@ def _execute_query(\r\n sys.stdout.buffer.write(data)\r\n else:\r\n sys.stdout.write(str(data))\r\n+ elif raw_lines:\r\n+ for row in cursor:\r\n+ data = row[0]\r\n+ if isinstance(data, bytes):\r\n+ sys.stdout.buffer.write(data + b\"\\n\")\r\n+ else:\r\n+ sys.stdout.write(str(data) + \"\\n\")\r\n elif fmt or table:\r\n print(\r\n tabulate.tabulate(\r\n```\r\nNeeds tests and more documentation.", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 1699174055, "label": "`--raw-lines` option, like `--raw` for multiple lines"}, "performed_via_github_app": null}