home / github

Menu
  • Search all tables
  • GraphQL API

issue_comments

Table actions
  • GraphQL API for issue_comments

53 rows where "updated_at" is on date 2020-09-22 sorted by updated_at descending

✖
✖

✎ View and edit SQL

This data as json, CSV (advanced)

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

issue 12

  • table.extract(...) method and "sqlite-utils extract" command 17
  • table.transform() method 13
  • await datasette.client.get(path) mechanism for executing internal requests 8
  • sqlite-utils transform sub-command 4
  • table.transform() method for advanced alter table 2
  • A decorator for registering custom SQL functions 2
  • 'bool' object is not callable error 2
  • Mechanism for turning nested JSON into foreign keys / many-to-many 1
  • Add --tar option to "datasette publish heroku" 1
  • Idea: conversions= could take Python functions 1
  • Review the foreign key pragma stuff 1
  • Automate (as much as possible) updates published to Homebrew 1

user 1

  • simonw 53

author_association 1

  • OWNER 53
id html_url issue_url node_id user created_at updated_at ▲ author_association body reactions issue performed_via_github_app
697037974 https://github.com/simonw/sqlite-utils/issues/42#issuecomment-697037974 https://api.github.com/repos/simonw/sqlite-utils/issues/42 MDEyOklzc3VlQ29tbWVudDY5NzAzNzk3NA== simonw 9599 2020-09-22T23:39:31Z 2020-09-22T23:39:31Z OWNER

Documentation for sqlite-utils extract: https://sqlite-utils.readthedocs.io/en/latest/cli.html#extracting-columns-into-a-separate-table

{
    "total_count": 0,
    "+1": 0,
    "-1": 0,
    "laugh": 0,
    "hooray": 0,
    "confused": 0,
    "heart": 0,
    "rocket": 0,
    "eyes": 0
}
table.extract(...) method and "sqlite-utils extract" command 470345929  
697031174 https://github.com/simonw/sqlite-utils/issues/42#issuecomment-697031174 https://api.github.com/repos/simonw/sqlite-utils/issues/42 MDEyOklzc3VlQ29tbWVudDY5NzAzMTE3NA== simonw 9599 2020-09-22T23:16:00Z 2020-09-22T23:16:00Z OWNER

Trying this demo again: wget 'https://raw.githubusercontent.com/wri/global-power-plant-database/master/output_database/global_power_plant_database.csv' sqlite-utils insert global.db power_plants global_power_plant_database.csv --csv sqlite-utils extract global.db power_plants country country_long --table countries --rename country_long name It worked!

{
    "total_count": 0,
    "+1": 0,
    "-1": 0,
    "laugh": 0,
    "hooray": 0,
    "confused": 0,
    "heart": 0,
    "rocket": 0,
    "eyes": 0
}
table.extract(...) method and "sqlite-utils extract" command 470345929  
697025403 https://github.com/simonw/sqlite-utils/issues/42#issuecomment-697025403 https://api.github.com/repos/simonw/sqlite-utils/issues/42 MDEyOklzc3VlQ29tbWVudDY5NzAyNTQwMw== simonw 9599 2020-09-22T22:57:53Z 2020-09-22T22:57:53Z OWNER

The documentation for the .extract() method is here: https://sqlite-utils.readthedocs.io/en/latest/python-api.html#extracting-columns-into-a-separate-table

{
    "total_count": 0,
    "+1": 0,
    "-1": 0,
    "laugh": 0,
    "hooray": 0,
    "confused": 0,
    "heart": 0,
    "rocket": 0,
    "eyes": 0
}
table.extract(...) method and "sqlite-utils extract" command 470345929  
697019944 https://github.com/simonw/sqlite-utils/issues/42#issuecomment-697019944 https://api.github.com/repos/simonw/sqlite-utils/issues/42 MDEyOklzc3VlQ29tbWVudDY5NzAxOTk0NA== simonw 9599 2020-09-22T22:40:00Z 2020-09-22T22:40:00Z OWNER

I tried out the prototype of the CLI on the Global Power Plants data: wget 'https://raw.githubusercontent.com/wri/global-power-plant-database/master/output_database/global_power_plant_database.csv' sqlite-utils insert global.db power_plants global_power_plant_database.csv --csv sqlite-utils extract global.db power_plants country country_long This threw an error because rowid columns are not yet supported. I fixed that like so: sqlite-utils transform global.db power_plants --rename rowid id sqlite-utils extract global.db power_plants country country_long That worked! But it didn't play great with Datasette, because the resulting extracted table had columns country and country_long and neither of those are called name or value or title.

Based on this I need to add rowid table support AND I need to implement the proposed rename= argument for renaming columns on their way into the new table.

{
    "total_count": 0,
    "+1": 0,
    "-1": 0,
    "laugh": 0,
    "hooray": 0,
    "confused": 0,
    "heart": 0,
    "rocket": 0,
    "eyes": 0
}
table.extract(...) method and "sqlite-utils extract" command 470345929  
697013681 https://github.com/simonw/sqlite-utils/issues/42#issuecomment-697013681 https://api.github.com/repos/simonw/sqlite-utils/issues/42 MDEyOklzc3VlQ29tbWVudDY5NzAxMzY4MQ== simonw 9599 2020-09-22T22:22:49Z 2020-09-22T22:22:49Z OWNER

The command-line version of this needs to accept a table and one or more columns, then a --table and --fk-column option.

{
    "total_count": 0,
    "+1": 0,
    "-1": 0,
    "laugh": 0,
    "hooray": 0,
    "confused": 0,
    "heart": 0,
    "rocket": 0,
    "eyes": 0
}
table.extract(...) method and "sqlite-utils extract" command 470345929  
697012111 https://github.com/simonw/sqlite-utils/issues/42#issuecomment-697012111 https://api.github.com/repos/simonw/sqlite-utils/issues/42 MDEyOklzc3VlQ29tbWVudDY5NzAxMjExMQ== simonw 9599 2020-09-22T22:18:13Z 2020-09-22T22:18:13Z OWNER

Here's how I'm generating the examples for the documentation: ``` In [2]: import sqlite_utils

In [3]: db = sqlite_utils.Database(memory=True)

In [4]: db["Trees"].insert({"id": 1, "TreeAddress": "52 Vine St", "CommonName": ...: "Palm", "LatinName": "foo"}, pk="id") Out[4]: <Table Trees (id, TreeAddress, CommonName, LatinName)>

In [5]: db["Trees"].extract(["CommonName", "LatinName"], table="Species", fk_col ...: umn="species_id")

In [6]: print(db["Trees"].schema) CREATE TABLE "Trees" ( [id] INTEGER PRIMARY KEY, [TreeAddress] TEXT, [species_id] INTEGER, FOREIGN KEY(species_id) REFERENCES Species(id) )

In [7]: print(db["Species"].schema) CREATE TABLE [Species] ( [id] INTEGER PRIMARY KEY, [CommonName] TEXT, [LatinName] TEXT ) ```

{
    "total_count": 0,
    "+1": 0,
    "-1": 0,
    "laugh": 0,
    "hooray": 0,
    "confused": 0,
    "heart": 0,
    "rocket": 0,
    "eyes": 0
}
table.extract(...) method and "sqlite-utils extract" command 470345929  
696987925 https://github.com/simonw/sqlite-utils/issues/42#issuecomment-696987925 https://api.github.com/repos/simonw/sqlite-utils/issues/42 MDEyOklzc3VlQ29tbWVudDY5Njk4NzkyNQ== simonw 9599 2020-09-22T21:19:04Z 2020-09-22T21:19:04Z OWNER

Need to make sure this works correctly for rowid tables.

{
    "total_count": 0,
    "+1": 0,
    "-1": 0,
    "laugh": 0,
    "hooray": 0,
    "confused": 0,
    "heart": 0,
    "rocket": 0,
    "eyes": 0
}
table.extract(...) method and "sqlite-utils extract" command 470345929  
696987257 https://github.com/simonw/sqlite-utils/issues/42#issuecomment-696987257 https://api.github.com/repos/simonw/sqlite-utils/issues/42 MDEyOklzc3VlQ29tbWVudDY5Njk4NzI1Nw== simonw 9599 2020-09-22T21:17:34Z 2020-09-22T21:17:34Z OWNER

What to do if the table already exists? The .lookup() function already knows how to modify an existing table to create the correct constraints etc, so I'll rely on that mechanism.

{
    "total_count": 0,
    "+1": 0,
    "-1": 0,
    "laugh": 0,
    "hooray": 0,
    "confused": 0,
    "heart": 0,
    "rocket": 0,
    "eyes": 0
}
table.extract(...) method and "sqlite-utils extract" command 470345929  
696980709 https://github.com/simonw/sqlite-utils/issues/42#issuecomment-696980709 https://api.github.com/repos/simonw/sqlite-utils/issues/42 MDEyOklzc3VlQ29tbWVudDY5Njk4MDcwOQ== simonw 9599 2020-09-22T21:05:07Z 2020-09-22T21:05:07Z OWNER

So .extract() probably takes a batch_size= argument too, which defaults to maybe 1000.

{
    "total_count": 0,
    "+1": 0,
    "-1": 0,
    "laugh": 0,
    "hooray": 0,
    "confused": 0,
    "heart": 0,
    "rocket": 0,
    "eyes": 0
}
table.extract(...) method and "sqlite-utils extract" command 470345929  
696980503 https://github.com/simonw/sqlite-utils/issues/42#issuecomment-696980503 https://api.github.com/repos/simonw/sqlite-utils/issues/42 MDEyOklzc3VlQ29tbWVudDY5Njk4MDUwMw== simonw 9599 2020-09-22T21:04:45Z 2020-09-22T21:04:45Z OWNER

table.extract() can take an optional progress= argument which is a callback which will be used to report progress - called after each batch with (num_done, total). It will get called with (0, total) once at the start to allow progress bars to be initialized. The command-line progress bar will use this.

{
    "total_count": 0,
    "+1": 0,
    "-1": 0,
    "laugh": 0,
    "hooray": 0,
    "confused": 0,
    "heart": 0,
    "rocket": 0,
    "eyes": 0
}
table.extract(...) method and "sqlite-utils extract" command 470345929  
696979626 https://github.com/simonw/sqlite-utils/issues/42#issuecomment-696979626 https://api.github.com/repos/simonw/sqlite-utils/issues/42 MDEyOklzc3VlQ29tbWVudDY5Njk3OTYyNg== simonw 9599 2020-09-22T21:03:11Z 2020-09-22T21:03:11Z OWNER

And if you want to rename some of the columns in the new table: python db["trees"].extract(["common_name", "latin_name"], table="species", rename={"common_name": "name"})

{
    "total_count": 0,
    "+1": 0,
    "-1": 0,
    "laugh": 0,
    "hooray": 0,
    "confused": 0,
    "heart": 0,
    "rocket": 0,
    "eyes": 0
}
table.extract(...) method and "sqlite-utils extract" command 470345929  
696979168 https://github.com/simonw/sqlite-utils/issues/42#issuecomment-696979168 https://api.github.com/repos/simonw/sqlite-utils/issues/42 MDEyOklzc3VlQ29tbWVudDY5Njk3OTE2OA== simonw 9599 2020-09-22T21:02:24Z 2020-09-22T21:02:24Z OWNER

In Python it looks like this: ```python

Simple case - species column species_id pointing to species table

db["trees"].extract("species")

Setting a custom table

db["trees"].extract("species", table="Species")

Custom foreign key column on trees

db["trees"].extract("species", fk_column="species")

Extracting multiple columns

db["trees"].extract(["common_name", "latin_name"])

(this creates a lookup table called common_name_latin_name ref'd by common_name_latin_name_id)

Or with explicit table (fk_column here defaults to species_id because of the table name)

db["trees"].extract(["common_name", "latin_name"], table="species") ```

{
    "total_count": 0,
    "+1": 0,
    "-1": 0,
    "laugh": 0,
    "hooray": 0,
    "confused": 0,
    "heart": 0,
    "rocket": 0,
    "eyes": 0
}
table.extract(...) method and "sqlite-utils extract" command 470345929  
696976678 https://github.com/simonw/sqlite-utils/issues/42#issuecomment-696976678 https://api.github.com/repos/simonw/sqlite-utils/issues/42 MDEyOklzc3VlQ29tbWVudDY5Njk3NjY3OA== simonw 9599 2020-09-22T20:57:57Z 2020-09-22T20:57:57Z OWNER

I think I understand the shape of this feature now. It lets you specify one or more columns on the source table which will be extracted into another table. It uses the .lookup() mechanism to populate that other table, which means each unique column value / pair / triple will be assigned an integer ID.

That integer ID gets written back into the first of the columns that are being transformed. A .transform() call then converts that column to an integer (and drops the additional columns). Finally we set up the new foreign key relationship.

{
    "total_count": 0,
    "+1": 0,
    "-1": 0,
    "laugh": 0,
    "hooray": 0,
    "confused": 0,
    "heart": 0,
    "rocket": 0,
    "eyes": 0
}
table.extract(...) method and "sqlite-utils extract" command 470345929  
696893774 https://github.com/simonw/sqlite-utils/issues/42#issuecomment-696893774 https://api.github.com/repos/simonw/sqlite-utils/issues/42 MDEyOklzc3VlQ29tbWVudDY5Njg5Mzc3NA== simonw 9599 2020-09-22T18:15:33Z 2020-09-22T18:15:33Z OWNER

I think the new foreign key column is called company_name_id by default in this example but can be customized by passing --fk-column=xxx

{
    "total_count": 0,
    "+1": 0,
    "-1": 0,
    "laugh": 0,
    "hooray": 0,
    "confused": 0,
    "heart": 0,
    "rocket": 0,
    "eyes": 0
}
table.extract(...) method and "sqlite-utils extract" command 470345929  
696893244 https://github.com/simonw/sqlite-utils/issues/42#issuecomment-696893244 https://api.github.com/repos/simonw/sqlite-utils/issues/42 MDEyOklzc3VlQ29tbWVudDY5Njg5MzI0NA== simonw 9599 2020-09-22T18:14:33Z 2020-09-22T18:14:45Z OWNER

Thinking more about this one: $ sqlite-utils extract my.db \ dea_sales company_name company_address \ --table companies The goal here is to pull the company name and address pair out into a separate table.

Some questions: - should this first verify that every company_name has just one company_address? I like the idea of a unique constraint on the created table for this. - what should the foreign key column that gets added to the companies table be called?

{
    "total_count": 0,
    "+1": 0,
    "-1": 0,
    "laugh": 0,
    "hooray": 0,
    "confused": 0,
    "heart": 0,
    "rocket": 0,
    "eyes": 0
}
table.extract(...) method and "sqlite-utils extract" command 470345929  
513262013 https://github.com/simonw/sqlite-utils/issues/42#issuecomment-513262013 https://api.github.com/repos/simonw/sqlite-utils/issues/42 MDEyOklzc3VlQ29tbWVudDUxMzI2MjAxMw== simonw 9599 2019-07-19T14:58:23Z 2020-09-22T18:12:11Z OWNER

CLI design idea:

$ sqlite-utils extract my.db \
    dea_sales company_name

Here we just specify the original table and column - the new extracted table will automatically be called "company_name" and will have "id" and "value" columns, by default.

To set a custom extract table:

$ sqlite-utils extract my.db \
    dea_sales company_name \
    --table companies

And for extracting multiple columns and renaming them on the created table, maybe something like this:

$ sqlite-utils extract my.db \
    dea_sales company_name company_address \
    --table companies \
    --column company_name name \
    --column company_address address
{
    "total_count": 0,
    "+1": 0,
    "-1": 0,
    "laugh": 0,
    "hooray": 0,
    "confused": 0,
    "heart": 0,
    "rocket": 0,
    "eyes": 0
}
table.extract(...) method and "sqlite-utils extract" command 470345929  
696800410 https://github.com/simonw/datasette/issues/973#issuecomment-696800410 https://api.github.com/repos/simonw/datasette/issues/973 MDEyOklzc3VlQ29tbWVudDY5NjgwMDQxMA== simonw 9599 2020-09-22T15:35:28Z 2020-09-22T15:35:28Z OWNER

Confirmed in local dev: % datasette fixtures.db --inspect-file inspect.json Traceback (most recent call last): File "/Users/simon/.local/share/virtualenvs/datasette-AWNrQs95/bin/datasette", line 11, in <module> load_entry_point('datasette', 'console_scripts', 'datasette')() File "/Users/simon/.local/share/virtualenvs/datasette-AWNrQs95/lib/python3.8/site-packages/click/core.py", line 829, in __call__ return self.main(*args, **kwargs) File "/Users/simon/.local/share/virtualenvs/datasette-AWNrQs95/lib/python3.8/site-packages/click/core.py", line 782, in main rv = self.invoke(ctx) File "/Users/simon/.local/share/virtualenvs/datasette-AWNrQs95/lib/python3.8/site-packages/click/core.py", line 1259, in invoke return _process_result(sub_ctx.command.invoke(sub_ctx)) File "/Users/simon/.local/share/virtualenvs/datasette-AWNrQs95/lib/python3.8/site-packages/click/core.py", line 1066, in invoke return ctx.invoke(self.callback, **ctx.params) File "/Users/simon/.local/share/virtualenvs/datasette-AWNrQs95/lib/python3.8/site-packages/click/core.py", line 610, in invoke return callback(*args, **kwargs) File "/Users/simon/Dropbox/Development/datasette/datasette/cli.py", line 406, in serve inspect_data = json.load(open(inspect_file)) TypeError: 'bool' object is not callable

{
    "total_count": 0,
    "+1": 0,
    "-1": 0,
    "laugh": 0,
    "hooray": 0,
    "confused": 0,
    "heart": 0,
    "rocket": 0,
    "eyes": 0
}
'bool' object is not callable error 706486323  
696798114 https://github.com/simonw/datasette/issues/973#issuecomment-696798114 https://api.github.com/repos/simonw/datasette/issues/973 MDEyOklzc3VlQ29tbWVudDY5Njc5ODExNA== simonw 9599 2020-09-22T15:31:25Z 2020-09-22T15:31:25Z OWNER

D'oh because I have a new variable called open.

{
    "total_count": 0,
    "+1": 0,
    "-1": 0,
    "laugh": 0,
    "hooray": 0,
    "confused": 0,
    "heart": 0,
    "rocket": 0,
    "eyes": 0
}
'bool' object is not callable error 706486323  
696788109 https://github.com/simonw/datasette/issues/969#issuecomment-696788109 https://api.github.com/repos/simonw/datasette/issues/969 MDEyOklzc3VlQ29tbWVudDY5Njc4ODEwOQ== simonw 9599 2020-09-22T15:15:14Z 2020-09-22T15:15:14Z OWNER

I don't think a standard "pass these extra arguments to the publish tool" mechanism will work because there's no guarantee that a publisher uses a CLI tool - or if it does, it might make several calls to different CLI tools. The Cloud Run one runs a couple of commands, as illustrated by this test:

https://github.com/simonw/datasette/blob/a648bb82bac201c7658f6fdb499ff8ac17ebd2e8/tests/test_publish_cloudrun.py#L63-L73

Adding a --tar option for datasette publish heroku is a good fix for this though.

{
    "total_count": 0,
    "+1": 0,
    "-1": 0,
    "laugh": 0,
    "hooray": 0,
    "confused": 0,
    "heart": 0,
    "rocket": 0,
    "eyes": 0
}
Add --tar option to "datasette publish heroku" 705057955  
696778735 https://github.com/simonw/datasette/issues/943#issuecomment-696778735 https://api.github.com/repos/simonw/datasette/issues/943 MDEyOklzc3VlQ29tbWVudDY5Njc3ODczNQ== simonw 9599 2020-09-22T15:00:13Z 2020-09-22T15:00:39Z OWNER

Am I going to rewrite ALL of my tests to use this instead? It would clean up a lot of test code, at the cost of quite a bit of work.

It would make for much neater plugin tests too, and neater testing documentation: https://docs.datasette.io/en/stable/testing_plugins.html

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

```python class DatasetteClient: def init(self, ds): self._client = httpx.AsyncClient(app=ds.app())

def _fix(self, path):
    if path.startswith("/"):
        path = "http://localhost{}".format(path)
    return path

async def get(self, path, **kwargs):
    return await self._client.get(self._fix(path), **kwargs)

async def options(self, path, **kwargs):
    return await self._client.options(self._fix(path), **kwargs)

async def head(self, path, **kwargs):
    return await self._client.head(self._fix(path), **kwargs)

async def post(self, path, **kwargs):
    return await self._client.post(self._fix(path), **kwargs)

async def put(self, path, **kwargs):
    return await self._client.put(self._fix(path), **kwargs)

async def patch(self, path, **kwargs):
    return await self._client.patch(self._fix(path), **kwargs)

async def delete(self, path, **kwargs):
    return await self._client.delete(self._fix(path), **kwargs)

```

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

I may as well implement all of the HTTP methods supported by the httpx client:

  • get
  • options
  • head
  • post
  • put
  • patch
  • delete
{
    "total_count": 0,
    "+1": 0,
    "-1": 0,
    "laugh": 0,
    "hooray": 0,
    "confused": 0,
    "heart": 0,
    "rocket": 0,
    "eyes": 0
}
await datasette.client.get(path) mechanism for executing internal requests 681375466  
696775516 https://github.com/simonw/datasette/issues/943#issuecomment-696775516 https://api.github.com/repos/simonw/datasette/issues/943 MDEyOklzc3VlQ29tbWVudDY5Njc3NTUxNg== simonw 9599 2020-09-22T14:55:10Z 2020-09-22T14:55:10Z OWNER

Even smaller DatasetteClient implementation: ```python class DatasetteClient: def init(self, ds): self._client = httpx.AsyncClient(app=ds.app())

def _fix(self, path):
    if path.startswith("/"):
        path = "http://localhost{}".format(path)
    return path

async def get(self, path, **kwargs):
    return await self._client.get(self._fix(path), **kwargs)

async def post(self, path, **kwargs):
    return await self._client.post(self._fix(path), **kwargs)

async def options(self, path, **kwargs):
    return await self._client.options(self._fix(path), **kwargs)

```

{
    "total_count": 0,
    "+1": 0,
    "-1": 0,
    "laugh": 0,
    "hooray": 0,
    "confused": 0,
    "heart": 0,
    "rocket": 0,
    "eyes": 0
}
await datasette.client.get(path) mechanism for executing internal requests 681375466  
696774711 https://github.com/simonw/datasette/issues/943#issuecomment-696774711 https://api.github.com/repos/simonw/datasette/issues/943 MDEyOklzc3VlQ29tbWVudDY5Njc3NDcxMQ== simonw 9599 2020-09-22T14:53:56Z 2020-09-22T14:53:56Z OWNER

How important is it to use httpx.AsyncClient with a context manager?

https://www.python-httpx.org/async/#opening-and-closing-clients says:

Alternatively, use await client.aclose() if you want to close a client explicitly:

client = httpx.AsyncClient() ... await client.aclose() The .aclose() method has a comment saying "Close transport and proxies" - I'm not using proxies, so the relevant implementation seems to be a call to await self._transport.aclose() in https://github.com/encode/httpx/blob/f932af9172d15a803ad40061a4c2c0cd891645cf/httpx/_client.py#L1741-L1751

The transport I am using is a class called ASGITransport in https://github.com/encode/httpx/blob/master/httpx/_transports/asgi.py

The aclose() method on that class does nothing. So it looks like I can instantiate a client without bothering with the async with httpx.AsyncClient bit.

{
    "total_count": 0,
    "+1": 0,
    "-1": 0,
    "laugh": 0,
    "hooray": 0,
    "confused": 0,
    "heart": 0,
    "rocket": 0,
    "eyes": 0
}
await datasette.client.get(path) mechanism for executing internal requests 681375466  
696769853 https://github.com/simonw/datasette/issues/943#issuecomment-696769853 https://api.github.com/repos/simonw/datasette/issues/943 MDEyOklzc3VlQ29tbWVudDY5Njc2OTg1Mw== simonw 9599 2020-09-22T14:46:21Z 2020-09-22T14:46:21Z OWNER

This adds httpx as a dependency - I think I'm OK with that. I use it for testing in all of my plugins anyway.

{
    "total_count": 0,
    "+1": 0,
    "-1": 0,
    "laugh": 0,
    "hooray": 0,
    "confused": 0,
    "heart": 0,
    "rocket": 0,
    "eyes": 0
}
await datasette.client.get(path) mechanism for executing internal requests 681375466  
696769501 https://github.com/simonw/datasette/issues/943#issuecomment-696769501 https://api.github.com/repos/simonw/datasette/issues/943 MDEyOklzc3VlQ29tbWVudDY5Njc2OTUwMQ== simonw 9599 2020-09-22T14:45:49Z 2020-09-22T14:45:49Z OWNER

I put together a minimal prototype of this and it feels pretty good: ```diff diff --git a/datasette/app.py b/datasette/app.py index 20aae7d..fb3bdad 100644 --- a/datasette/app.py +++ b/datasette/app.py @@ -4,6 +4,7 @@ import collections import datetime import glob import hashlib +import httpx import inspect import itertools from itsdangerous import BadSignature @@ -312,6 +313,7 @@ class Datasette: self._register_renderers() self._permission_checks = collections.deque(maxlen=200) self._root_token = secrets.token_hex(32) + self.client = DatasetteClient(self)

 async def invoke_startup(self):
     for hook in pm.hook.startup(datasette=self):

@@ -1209,3 +1211,25 @@ def route_pattern_from_filepath(filepath):

class NotFoundExplicit(NotFound): pass + + +class DatasetteClient: + def init(self, ds): + self.app = ds.app() + + def _fix(self, path): + if path.startswith("/"): + path = "http://localhost{}".format(path) + return path + + async def get(self, path, kwargs): + async with httpx.AsyncClient(app=self.app) as client: + return await client.get(self._fix(path), kwargs) + + async def post(self, path, kwargs): + async with httpx.AsyncClient(app=self.app) as client: + return await client.post(self._fix(path), kwargs) + + async def options(self, path, kwargs): + async with httpx.AsyncClient(app=self.app) as client: + return await client.options(self._fix(path), kwargs) Used like this in `ipython`: In [1]: from datasette.app import Datasette

In [2]: ds = Datasette(["fixtures.db"])

In [3]: (await ds.client.get("/-/config.json")).json() Out[3]: {'default_page_size': 100, 'max_returned_rows': 1000, 'num_sql_threads': 3, 'sql_time_limit_ms': 1000, 'default_facet_size': 30, 'facet_time_limit_ms': 200, 'facet_suggest_time_limit_ms': 50, 'hash_urls': False, 'allow_facet': True, 'allow_download': True, 'suggest_facets': True, 'default_cache_ttl': 5, 'default_cache_ttl_hashed': 31536000, 'cache_size_kb': 0, 'allow_csv_stream': True, 'max_csv_mb': 100, 'truncate_cells_html': 2048, 'force_https_urls': False, 'template_debug': False, 'base_url': '/'}

In [4]: (await ds.client.get("/fixtures/facetable.json?_shape=array")).json() Out[4]: [{'pk': 1, 'created': '2019-01-14 08:00:00', 'planet_int': 1, 'on_earth': 1, 'state': 'CA', 'city_id': 1, 'neighborhood': 'Mission', 'tags': '["tag1", "tag2"]', 'complex_array': '[{"foo": "bar"}]', 'distinct_some_null': 'one'}, {'pk': 2, 'created': '2019-01-14 08:00:00', 'planet_int': 1, 'on_earth': 1, 'state': 'CA', 'city_id': 1, 'neighborhood': 'Dogpatch', 'tags': '["tag1", "tag3"]', 'complex_array': '[]', 'distinct_some_null': 'two'}, ```

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

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

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

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

{
    "total_count": 0,
    "+1": 0,
    "-1": 0,
    "laugh": 0,
    "hooray": 0,
    "confused": 0,
    "heart": 0,
    "rocket": 0,
    "eyes": 0
}
await datasette.client.get(path) mechanism for executing internal requests 681375466  
696573944 https://github.com/simonw/sqlite-utils/issues/168#issuecomment-696573944 https://api.github.com/repos/simonw/sqlite-utils/issues/168 MDEyOklzc3VlQ29tbWVudDY5NjU3Mzk0NA== simonw 9599 2020-09-22T08:11:30Z 2020-09-22T08:11:30Z OWNER

Huh... maybe I don't need to do anything here? It looks like it's been kept up to date: https://github.com/Homebrew/homebrew-core/commits/master/Formula/sqlite-utils.rb

{
    "total_count": 0,
    "+1": 0,
    "-1": 0,
    "laugh": 0,
    "hooray": 0,
    "confused": 0,
    "heart": 0,
    "rocket": 0,
    "eyes": 0
}
Automate (as much as possible) updates published to Homebrew 706167456  
696567988 https://github.com/simonw/sqlite-utils/issues/164#issuecomment-696567988 https://api.github.com/repos/simonw/sqlite-utils/issues/164 MDEyOklzc3VlQ29tbWVudDY5NjU2Nzk4OA== simonw 9599 2020-09-22T07:57:50Z 2020-09-22T07:57:50Z OWNER

Documentation: https://sqlite-utils.readthedocs.io/en/latest/cli.html#transforming-tables

{
    "total_count": 0,
    "+1": 0,
    "-1": 0,
    "laugh": 0,
    "hooray": 0,
    "confused": 0,
    "heart": 0,
    "rocket": 0,
    "eyes": 0
}
sqlite-utils transform sub-command 706017416  
696567460 https://github.com/simonw/sqlite-utils/issues/42#issuecomment-696567460 https://api.github.com/repos/simonw/sqlite-utils/issues/42 MDEyOklzc3VlQ29tbWVudDY5NjU2NzQ2MA== simonw 9599 2020-09-22T07:56:42Z 2020-09-22T07:56:42Z OWNER

.transform() has landed now which should make this a lot easier to solve.

{
    "total_count": 0,
    "+1": 0,
    "-1": 0,
    "laugh": 0,
    "hooray": 0,
    "confused": 0,
    "heart": 0,
    "rocket": 0,
    "eyes": 0
}
table.extract(...) method and "sqlite-utils extract" command 470345929  
696566750 https://github.com/simonw/sqlite-utils/issues/26#issuecomment-696566750 https://api.github.com/repos/simonw/sqlite-utils/issues/26 MDEyOklzc3VlQ29tbWVudDY5NjU2Njc1MA== simonw 9599 2020-09-22T07:55:00Z 2020-09-22T07:55:00Z OWNER

Problem: extract means something else now, see #47 and the upcoming work in #42.

{
    "total_count": 0,
    "+1": 0,
    "-1": 0,
    "laugh": 0,
    "hooray": 0,
    "confused": 0,
    "heart": 0,
    "rocket": 0,
    "eyes": 0
}
Mechanism for turning nested JSON into foreign keys / many-to-many 455486286  
696565981 https://github.com/simonw/sqlite-utils/issues/167#issuecomment-696565981 https://api.github.com/repos/simonw/sqlite-utils/issues/167 MDEyOklzc3VlQ29tbWVudDY5NjU2NTk4MQ== simonw 9599 2020-09-22T07:53:13Z 2020-09-22T07:53:13Z OWNER

Confirmed this is a bug, https://www.sqlite.org/lang_altertable.html#making_other_kinds_of_table_schema_changes explicitly says you should do the PRAGMA foreign_keys bits before and after the transaction, not during.

Right now my code does this INSIDE the transaction: https://github.com/simonw/sqlite-utils/blob/f29f6821f2d08e91c5c6d65d885a1bbc0c743bdd/sqlite_utils/db.py#L790-L793

{
    "total_count": 0,
    "+1": 0,
    "-1": 0,
    "laugh": 0,
    "hooray": 0,
    "confused": 0,
    "heart": 0,
    "rocket": 0,
    "eyes": 0
}
Review the foreign key pragma stuff 706098005  
696520928 https://github.com/simonw/sqlite-utils/issues/164#issuecomment-696520928 https://api.github.com/repos/simonw/sqlite-utils/issues/164 MDEyOklzc3VlQ29tbWVudDY5NjUyMDkyOA== simonw 9599 2020-09-22T05:50:17Z 2020-09-22T05:50:17Z OWNER

Idea for CLI options: --type age integer --drop colname --rename oldname newname --not-null col --not-null-false col --pk new_id --pk-none --default col value --default-none column --drop-foreign-key col other_table other_column

{
    "total_count": 0,
    "+1": 0,
    "-1": 0,
    "laugh": 0,
    "hooray": 0,
    "confused": 0,
    "heart": 0,
    "rocket": 0,
    "eyes": 0
}
sqlite-utils transform sub-command 706017416  
696500922 https://github.com/simonw/sqlite-utils/issues/164#issuecomment-696500922 https://api.github.com/repos/simonw/sqlite-utils/issues/164 MDEyOklzc3VlQ29tbWVudDY5NjUwMDkyMg== simonw 9599 2020-09-22T04:22:40Z 2020-09-22T04:22:40Z OWNER

Documentation for the .transform() method #114 (now landed) is here: https://sqlite-utils.readthedocs.io/en/latest/python-api.html#transforming-a-table

{
    "total_count": 0,
    "+1": 0,
    "-1": 0,
    "laugh": 0,
    "hooray": 0,
    "confused": 0,
    "heart": 0,
    "rocket": 0,
    "eyes": 0
}
sqlite-utils transform sub-command 706017416  
696500767 https://github.com/simonw/sqlite-utils/issues/114#issuecomment-696500767 https://api.github.com/repos/simonw/sqlite-utils/issues/114 MDEyOklzc3VlQ29tbWVudDY5NjUwMDc2Nw== simonw 9599 2020-09-22T04:21:45Z 2020-09-22T04:21:45Z OWNER

Documentation: https://sqlite-utils.readthedocs.io/en/latest/python-api.html#transforming-a-table

{
    "total_count": 0,
    "+1": 0,
    "-1": 0,
    "laugh": 0,
    "hooray": 0,
    "confused": 0,
    "heart": 0,
    "rocket": 0,
    "eyes": 0
}
table.transform() method for advanced alter table 621989740  
696494070 https://github.com/simonw/sqlite-utils/pull/161#issuecomment-696494070 https://api.github.com/repos/simonw/sqlite-utils/issues/161 MDEyOklzc3VlQ29tbWVudDY5NjQ5NDA3MA== simonw 9599 2020-09-22T03:48:58Z 2020-09-22T03:48:58Z OWNER

One last thing. https://www.sqlite.org/lang_altertable.html#making_other_kinds_of_table_schema_change says that the first step should be:

If foreign key constraints are enabled, disable them using PRAGMA foreign_keys=OFF.

And the last steps should be:

If foreign key constraints were originally enabled then run PRAGMA foreign_key_check to verify that the schema change did not break any foreign key constraints.

Commit the transaction started in step 2.

If foreign keys constraints were originally enabled, reenable them now.

I need to implement that.

{
    "total_count": 0,
    "+1": 0,
    "-1": 0,
    "laugh": 0,
    "hooray": 0,
    "confused": 0,
    "heart": 0,
    "rocket": 0,
    "eyes": 0
}
table.transform() method 705975133  
696490851 https://github.com/simonw/sqlite-utils/pull/161#issuecomment-696490851 https://api.github.com/repos/simonw/sqlite-utils/issues/161 MDEyOklzc3VlQ29tbWVudDY5NjQ5MDg1MQ== simonw 9599 2020-09-22T03:33:54Z 2020-09-22T03:33:54Z OWNER

It would be neat if .transform(pk=None) converted a primary key table to a rowid table.

{
    "total_count": 0,
    "+1": 0,
    "-1": 0,
    "laugh": 0,
    "hooray": 0,
    "confused": 0,
    "heart": 0,
    "rocket": 0,
    "eyes": 0
}
table.transform() method 705975133  
696488201 https://github.com/simonw/sqlite-utils/pull/161#issuecomment-696488201 https://api.github.com/repos/simonw/sqlite-utils/issues/161 MDEyOklzc3VlQ29tbWVudDY5NjQ4ODIwMQ== simonw 9599 2020-09-22T03:21:16Z 2020-09-22T03:21:16Z OWNER

Just needs documentation now.

{
    "total_count": 0,
    "+1": 0,
    "-1": 0,
    "laugh": 0,
    "hooray": 0,
    "confused": 0,
    "heart": 0,
    "rocket": 0,
    "eyes": 0
}
table.transform() method 705975133  
696485791 https://github.com/simonw/sqlite-utils/pull/161#issuecomment-696485791 https://api.github.com/repos/simonw/sqlite-utils/issues/161 MDEyOklzc3VlQ29tbWVudDY5NjQ4NTc5MQ== simonw 9599 2020-09-22T03:10:15Z 2020-09-22T03:10:15Z OWNER

Design decision needed on foreign keys: what does the syntax look like for removing an existing foreign key?

Since I already have a good implementation of add_foreign_key() I'm tempted to only support dropping them. Maybe like this:

python table.transform(drop_foreign_keys=[("author_id", "author", "id")]) It's a bit crufty but it's such a rare use-case that I think this will be good enough.

{
    "total_count": 0,
    "+1": 0,
    "-1": 0,
    "laugh": 0,
    "hooray": 0,
    "confused": 0,
    "heart": 0,
    "rocket": 0,
    "eyes": 0
}
table.transform() method 705975133  
696480925 https://github.com/simonw/sqlite-utils/pull/161#issuecomment-696480925 https://api.github.com/repos/simonw/sqlite-utils/issues/161 MDEyOklzc3VlQ29tbWVudDY5NjQ4MDkyNQ== simonw 9599 2020-09-22T02:45:47Z 2020-09-22T02:45:47Z OWNER

I'm not going to do conversions= because it would be inconsistent with how they work elsewhere. The SQL generated by this function looks like this:

INSERT INTO dogs_new_tmp VALUES (a, b) SELECT a, b from dogs;

So passing conversions={"name": "upper(?)"}) wouldn't make sense, since we're not using arguments hence there is no-where for that ? to go.

{
    "total_count": 0,
    "+1": 0,
    "-1": 0,
    "laugh": 0,
    "hooray": 0,
    "confused": 0,
    "heart": 0,
    "rocket": 0,
    "eyes": 0
}
table.transform() method 705975133  
696473559 https://github.com/simonw/sqlite-utils/issues/164#issuecomment-696473559 https://api.github.com/repos/simonw/sqlite-utils/issues/164 MDEyOklzc3VlQ29tbWVudDY5NjQ3MzU1OQ== simonw 9599 2020-09-22T02:10:37Z 2020-09-22T02:10:37Z OWNER

Maybe something like this:

sqlite-utils transform mydb.db mytable -c age integer --rename age dog_age
{
    "total_count": 0,
    "+1": 0,
    "-1": 0,
    "laugh": 0,
    "hooray": 0,
    "confused": 0,
    "heart": 0,
    "rocket": 0,
    "eyes": 0
}
sqlite-utils transform sub-command 706017416  
696465788 https://github.com/simonw/sqlite-utils/issues/163#issuecomment-696465788 https://api.github.com/repos/simonw/sqlite-utils/issues/163 MDEyOklzc3VlQ29tbWVudDY5NjQ2NTc4OA== simonw 9599 2020-09-22T01:33:04Z 2020-09-22T01:33:04Z OWNER

This would apply to .transform() in #114 too.

{
    "total_count": 0,
    "+1": 0,
    "-1": 0,
    "laugh": 0,
    "hooray": 0,
    "confused": 0,
    "heart": 0,
    "rocket": 0,
    "eyes": 0
}
Idea: conversions= could take Python functions 706001517  
696454485 https://github.com/simonw/sqlite-utils/issues/114#issuecomment-696454485 https://api.github.com/repos/simonw/sqlite-utils/issues/114 MDEyOklzc3VlQ29tbWVudDY5NjQ1NDQ4NQ== simonw 9599 2020-09-22T00:42:35Z 2020-09-22T00:42:35Z OWNER

The reason I'm working on this now is that I'd like to support many more options for data cleanup in the Datasette ecosystem - so being able to do things like convert the type of existing columns becomes increasingly important.

{
    "total_count": 0,
    "+1": 0,
    "-1": 0,
    "laugh": 0,
    "hooray": 0,
    "confused": 0,
    "heart": 0,
    "rocket": 0,
    "eyes": 0
}
table.transform() method for advanced alter table 621989740  
696454084 https://github.com/simonw/sqlite-utils/issues/162#issuecomment-696454084 https://api.github.com/repos/simonw/sqlite-utils/issues/162 MDEyOklzc3VlQ29tbWVudDY5NjQ1NDA4NA== simonw 9599 2020-09-22T00:40:44Z 2020-09-22T00:40:44Z OWNER

Documentation: https://sqlite-utils.readthedocs.io/en/latest/python-api.html#registering-custom-sql-functions

{
    "total_count": 0,
    "+1": 0,
    "-1": 0,
    "laugh": 0,
    "hooray": 0,
    "confused": 0,
    "heart": 0,
    "rocket": 0,
    "eyes": 0
}
A decorator for registering custom SQL functions 705995722  
696449345 https://github.com/simonw/sqlite-utils/issues/162#issuecomment-696449345 https://api.github.com/repos/simonw/sqlite-utils/issues/162 MDEyOklzc3VlQ29tbWVudDY5NjQ0OTM0NQ== simonw 9599 2020-09-22T00:22:46Z 2020-09-22T00:22:46Z OWNER

Inspired by the idea of adding conversions= to #114 - since this would make it easy to register custom Python functions that can be used to convert the values in a table.

{
    "total_count": 0,
    "+1": 0,
    "-1": 0,
    "laugh": 0,
    "hooray": 0,
    "confused": 0,
    "heart": 0,
    "rocket": 0,
    "eyes": 0
}
A decorator for registering custom SQL functions 705995722  
696446658 https://github.com/simonw/sqlite-utils/pull/161#issuecomment-696446658 https://api.github.com/repos/simonw/sqlite-utils/issues/161 MDEyOklzc3VlQ29tbWVudDY5NjQ0NjY1OA== simonw 9599 2020-09-22T00:13:55Z 2020-09-22T00:14:21Z OWNER

Idea: allow a conversions= parameter, as seen on .insert_all() and friends, which lets you apply a SQL transformation function as part of the operation. E.g.:

python table.transform({"age": int}, conversions={"name": "upper(?)"})

https://sqlite-utils.readthedocs.io/en/stable/python-api.html#converting-column-values-using-sql-functions

{
    "total_count": 0,
    "+1": 0,
    "-1": 0,
    "laugh": 0,
    "hooray": 0,
    "confused": 0,
    "heart": 0,
    "rocket": 0,
    "eyes": 0
}
table.transform() method 705975133  
696445766 https://github.com/simonw/sqlite-utils/pull/161#issuecomment-696445766 https://api.github.com/repos/simonw/sqlite-utils/issues/161 MDEyOklzc3VlQ29tbWVudDY5NjQ0NTc2Ng== simonw 9599 2020-09-22T00:10:50Z 2020-09-22T00:11:12Z OWNER

A less horrible interface might be the following: ```python

Ensure the 'age' column is not null:

table.transform(not_null={"age"})

The 'age' column is not null but I don't want it to be:

table.transform(not_null={"age": False}) ``` So if the argument is a set it means "make sure these are all not null" - if the argument is a dictionary it means "set these to be null or not null depending on if their dictionary value is true or false".

{
    "total_count": 0,
    "+1": 0,
    "-1": 0,
    "laugh": 0,
    "hooray": 0,
    "confused": 0,
    "heart": 0,
    "rocket": 0,
    "eyes": 0
}
table.transform() method 705975133  
696444842 https://github.com/simonw/sqlite-utils/pull/161#issuecomment-696444842 https://api.github.com/repos/simonw/sqlite-utils/issues/161 MDEyOklzc3VlQ29tbWVudDY5NjQ0NDg0Mg== simonw 9599 2020-09-22T00:07:43Z 2020-09-22T00:09:05Z OWNER

Syntax challenge: I could use .transform(defaults={"age": None}) to indicate that the age column should have its default removed, but how would I tell .transform() that the age column, currently not null, should have the not null removed from it?

I could do this: .transform(not_not_null={"age"}) - it's a bit gross but it's also kind of funny. I actually like it!

{
    "total_count": 0,
    "+1": 0,
    "-1": 0,
    "laugh": 0,
    "hooray": 0,
    "confused": 0,
    "heart": 0,
    "rocket": 0,
    "eyes": 0
}
table.transform() method 705975133  
696444353 https://github.com/simonw/sqlite-utils/pull/161#issuecomment-696444353 https://api.github.com/repos/simonw/sqlite-utils/issues/161 MDEyOklzc3VlQ29tbWVudDY5NjQ0NDM1Mw== simonw 9599 2020-09-22T00:06:12Z 2020-09-22T00:06:12Z OWNER

I should support not_null= and default= arguments to the .transform() method because it looks like you can't use ALTER TABLE to change those.

{
    "total_count": 0,
    "+1": 0,
    "-1": 0,
    "laugh": 0,
    "hooray": 0,
    "confused": 0,
    "heart": 0,
    "rocket": 0,
    "eyes": 0
}
table.transform() method 705975133  
696443845 https://github.com/simonw/sqlite-utils/pull/161#issuecomment-696443845 https://api.github.com/repos/simonw/sqlite-utils/issues/161 MDEyOklzc3VlQ29tbWVudDY5NjQ0Mzg0NQ== simonw 9599 2020-09-22T00:04:31Z 2020-09-22T00:04:44Z OWNER

Good news: the .columns introspection does tell me those things: ```

import sqlite_utils db = sqlite_utils.Database(memory=True) db.create_table("foo", {"id": int, "name": str, "age": int}, defaults={"age": 1}, not_null={"name", "age"})

<Table foo (id, name, age)> >>> db["foo"] <Table foo (id, name, age)> >>> print(db["foo"].schema) CREATE TABLE [foo] ( [id] INTEGER, [name] TEXT NOT NULL, [age] INTEGER NOT NULL DEFAULT 1 ) >>> db["foo"].columns [Column(cid=0, name='id', type='INTEGER', notnull=0, default_value=None, is_pk=0), Column(cid=1, name='name', type='TEXT', notnull=1, default_value=None, is_pk=0), Column(cid=2, name='age', type='INTEGER', notnull=1, default_value='1', is_pk=0)] ```
{
    "total_count": 0,
    "+1": 0,
    "-1": 0,
    "laugh": 0,
    "hooray": 0,
    "confused": 0,
    "heart": 0,
    "rocket": 0,
    "eyes": 0
}
table.transform() method 705975133  
696443190 https://github.com/simonw/sqlite-utils/pull/161#issuecomment-696443190 https://api.github.com/repos/simonw/sqlite-utils/issues/161 MDEyOklzc3VlQ29tbWVudDY5NjQ0MzE5MA== simonw 9599 2020-09-22T00:02:22Z 2020-09-22T00:02:22Z OWNER

How would I detect which columns are not_null and what their defaults are? I don`t think my introspection logic handles that yet.

{
    "total_count": 0,
    "+1": 0,
    "-1": 0,
    "laugh": 0,
    "hooray": 0,
    "confused": 0,
    "heart": 0,
    "rocket": 0,
    "eyes": 0
}
table.transform() method 705975133  
696443042 https://github.com/simonw/sqlite-utils/pull/161#issuecomment-696443042 https://api.github.com/repos/simonw/sqlite-utils/issues/161 MDEyOklzc3VlQ29tbWVudDY5NjQ0MzA0Mg== simonw 9599 2020-09-22T00:01:50Z 2020-09-22T00:01:50Z OWNER

When you transform a table, it should keep its primary key, foreign keys, not_null and defaults. I don't think it needs to care about hash_id or extracts= since those don't affect the structure of the table as it is being created - well, hash_id does but if we are transforming an existing table we will get the hash_id column for free.

{
    "total_count": 0,
    "+1": 0,
    "-1": 0,
    "laugh": 0,
    "hooray": 0,
    "confused": 0,
    "heart": 0,
    "rocket": 0,
    "eyes": 0
}
table.transform() method 705975133  
696442621 https://github.com/simonw/sqlite-utils/pull/161#issuecomment-696442621 https://api.github.com/repos/simonw/sqlite-utils/issues/161 MDEyOklzc3VlQ29tbWVudDY5NjQ0MjYyMQ== simonw 9599 2020-09-22T00:00:23Z 2020-09-22T00:00:23Z OWNER

I still need to figure out what to do about these various other table properties: https://github.com/simonw/sqlite-utils/blob/b34c9b40c206d7a9d7ee57a8c1f198ff1f522735/sqlite_utils/db.py#L775-L787

{
    "total_count": 0,
    "+1": 0,
    "-1": 0,
    "laugh": 0,
    "hooray": 0,
    "confused": 0,
    "heart": 0,
    "rocket": 0,
    "eyes": 0
}
table.transform() method 705975133  

Advanced export

JSON shape: default, array, newline-delimited, object

CSV options:

CREATE TABLE [issue_comments] (
   [html_url] TEXT,
   [issue_url] TEXT,
   [id] INTEGER PRIMARY KEY,
   [node_id] TEXT,
   [user] INTEGER REFERENCES [users]([id]),
   [created_at] TEXT,
   [updated_at] TEXT,
   [author_association] TEXT,
   [body] TEXT,
   [reactions] TEXT,
   [issue] INTEGER REFERENCES [issues]([id])
, [performed_via_github_app] TEXT);
CREATE INDEX [idx_issue_comments_issue]
                ON [issue_comments] ([issue]);
CREATE INDEX [idx_issue_comments_user]
                ON [issue_comments] ([user]);
Powered by Datasette · Queries took 1778.451ms · About: github-to-sqlite
  • Sort ascending
  • Sort descending
  • Facet by this
  • Hide this column
  • Show all columns
  • Show not-blank rows