{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "# fastlite\n", "\n", "> A bit of extra usability for sqlite\n", "\n", "- image: \"images/diagram.png\"" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "`fastlite` provides some little quality-of-life improvements for interactive use of the wonderful [sqlite-utils](https://sqlite-utils.datasette.io/) library. It's likely to be particularly of interest to folks using Jupyter." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Install" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "```\n", "pip install fastlite\n", "```" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Overview" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "#| hide\n", "from nbdev.showdoc import show_doc" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "from fastlite import *\n", "from fastcore.utils import *\n", "from fastcore.net import urlsave" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "We demonstrate `fastlite`'s features here using the 'chinook' sample database." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "#| hide\n", "Path('chinook.sqlite').unlink(missing_ok=True)\n", "Path('chinook.sqlite-shm').unlink(missing_ok=True)\n", "Path('chinook.sqlite-wal').unlink(missing_ok=True)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "url = 'https://github.com/lerocha/chinook-database/raw/master/ChinookDatabase/DataSources/Chinook_Sqlite.sqlite'\n", "path = Path('chinook.sqlite')\n", "if not path.exists(): urlsave(url, path)\n", "\n", "db = database(\"chinook.sqlite\")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Databases have a `t` property that lists all tables:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "Album, Artist, Customer, Employee, Genre, Invoice, InvoiceLine, MediaType, Playlist, PlaylistTrack, Track" ] }, "execution_count": null, "metadata": {}, "output_type": "execute_result" } ], "source": [ "dt = db.t\n", "dt" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "You can use this to grab a single table...:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "" ] }, "execution_count": null, "metadata": {}, "output_type": "execute_result" } ], "source": [ "artist = dt.artists\n", "artist" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "
" ] }, "execution_count": null, "metadata": {}, "output_type": "execute_result" } ], "source": [ "artist = dt.Artist\n", "artist" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "...or multiple tables at once:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "[
,\n", "
,\n", "
,\n", "
,\n", "
]" ] }, "execution_count": null, "metadata": {}, "output_type": "execute_result" } ], "source": [ "dt['Artist','Album','Track','Genre','MediaType']" ] }, { "attachments": { "image.png": { "image/png": "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" } }, "cell_type": "markdown", "metadata": {}, "source": [ "It also provides auto-complete in Jupyter, IPython, and nearly any other interactive Python environment:\n", "\n", "![](attachment:image.png){width=180}" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "You can check if a table is in the database already:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "True" ] }, "execution_count": null, "metadata": {}, "output_type": "execute_result" } ], "source": [ "'Artist' in dt" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Column work in a similar way to tables, using the `c` property:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "ArtistId, Name" ] }, "execution_count": null, "metadata": {}, "output_type": "execute_result" } ], "source": [ "ac = artist.c\n", "ac" ] }, { "attachments": { "image.png": { "image/png": "iVBORw0KGgoAAAANSUhEUgAAASQAAADQCAYAAACujRxXAAAMPWlDQ1BJQ0MgUHJvZmlsZQAASImVVwdYU8kWnluSkEBoAQSkhN4EESkBpITQQu9NVEISIJQYA0HFji4quHaxgA1dFVGw0iwoYmdR7H2xoKCsiwW78iYFdN1Xvjf5ZubPP2f+c+bcuWUAUDvBEYlyUXUA8oQF4pggP3pScgqd1AMwgAIq/DlyuPkiZlRUGIBlqP97eXcDINL+qr1U65/j/7Vo8Pj5XACQKIjTefncPIgPAYBXckXiAgCIUt5saoFIimEFWmIYIMSLpDhTjiulOF2O98ls4mJYELcBoKTC4YgzAVC9DHl6ITcTaqj2Q+wo5AmEAKjRIfbOy5vMgzgNYmtoI4JYqs9I/0En82+a6cOaHE7mMJavRVaU/AX5olzO9P8zHf+75OVKhnxYwqqSJQ6Oka4Z5u1WzuRQKVaBuE+YHhEJsSbEHwQ8mT3EKCVLEhwvt0cNuPksmDOgA7Ejj+MfCrEBxIHC3IgwBZ+eIQhkQwx3CDpNUMCOg1gX4kX8/IBYhc0W8eQYhS+0PkPMYir4cxyxzK/U1wNJTjxTof86i89W6GOqRVlxiRBTIDYvFCREQKwKsUN+TmyowmZcURYrYshGLImRxm8OcQxfGOQn18cKM8SBMQr70rz8ofViW7IE7AgFPlCQFRcszw/WxuXI4odrwS7zhcz4IR1+flLY0Fp4fP8A+dqxHr4wPlah80FU4Bcjn4tTRLlRCnvclJ8bJOVNIXbOL4xVzMUTCuCGlOvjGaKCqDh5nHhRNickSh4PvhyEARbwB3QggTUdTAbZQNDR19AH/8lHAgEHiEEm4AN7BTM0I1E2IoRtLCgCf0LEB/nD8/xko3xQCPmvw6y8tQcZstFC2Ywc8BTiPBAKcuF/iWyWcNhbAngCGcE/vHNg5cJ4c2GVjv97foj9zjAhE6ZgJEMe6WpDlsQAoj8xmBhItMH1cW/cEw+DrS+sTjgDdx9ax3d7wlNCJ+ER4Tqhi3B7kqBY/FOU4aAL6gcqcpH+Yy5wS6jpgvvhXlAdKuM6uD6wx52hHybuAz27QJaliFuaFfpP2n9bwQ9XQ2FHdiSj5BFkX7L1zzNVbVVdhlWkuf4xP/JY04fzzRoe+dk/64fs82Af+rMltgg7iJ3FTmLnsaNYA6BjLVgj1o4dk+Lh3fVEtruGvMXI4smBOoJ/+Bu6stJM5jvWOPY6fpGPFfCnSZ/RgDVZNF0syMwqoDPhG4FPZwu5DqPoTo5OzgBI3y/yx9ebaNl7A9Fp/87N/wMAr5bBwcEj37mQFgD2u8Hbv+k7Z82Arw5lAM41cSXiQjmHSxsCfEqowTtNDxgBM2AN1+MEXIEn8AUBIAREgjiQDCbC6LPgPheDqWAmmAdKQBlYDtaADWAz2AZ2gb3gAGgAR8FJcAZcBJfBdXAX7p5u8AL0g3fgM4IgJISK0BA9xBixQOwQJ4SBeCMBSBgSgyQjaUgmIkQkyExkPlKGrEQ2IFuRamQ/0oScRM4jncht5CHSi7xGPqEYqoJqoYaoJToaZaBMNBSNQyegmegUtAhdgC5F16FV6B60Hj2JXkSvo13oC3QAA5gypoOZYPYYA2NhkVgKloGJsdlYKVaOVWG1WDO8zlexLqwP+4gTcRpOx+3hDg7G43EuPgWfjS/BN+C78Hq8Db+KP8T78W8EKsGAYEfwILAJSYRMwlRCCaGcsINwmHAa3kvdhHdEIlGHaEV0g/diMjGbOIO4hLiRWEc8QewkPiYOkEgkPZIdyYsUSeKQCkglpPWkPaQW0hVSN+mDkrKSsZKTUqBSipJQqVipXGm30nGlK0rPlD6T1ckWZA9yJJlHnk5eRt5ObiZfIneTP1M0KFYUL0ocJZsyj7KOUks5TblHeaOsrGyq7K4crSxQnqu8Tnmf8jnlh8ofVTRVbFVYKqkqEpWlKjtVTqjcVnlDpVItqb7UFGoBdSm1mnqK+oD6QZWm6qDKVuWpzlGtUK1XvaL6Uo2sZqHGVJuoVqRWrnZQ7ZJanzpZ3VKdpc5Rn61eod6kflN9QIOmMUYjUiNPY4nGbo3zGj2aJE1LzQBNnuYCzW2apzQf0zCaGY1F49Lm07bTTtO6tYhaVlpsrWytMq29Wh1a/dqa2s7aCdrTtCu0j2l36WA6ljpsnVydZToHdG7ofBphOII5gj9i8YjaEVdGvNcdqeury9ct1a3Tva77SY+uF6CXo7dCr0Hvvj6ub6sfrT9Vf5P+af2+kVojPUdyR5aOPDDyjgFqYGsQYzDDYJtBu8GAoZFhkKHIcL3hKcM+Ix0jX6Nso9VGx416jWnG3sYC49XGLcbP6dp0Jj2Xvo7eRu83MTAJNpGYbDXpMPlsamUab1psWmd634xixjDLMFtt1mrWb25sHm4+07zG/I4F2YJhkWWx1uKsxXtLK8tEy4WWDZY9VrpWbKsiqxqre9ZUax/rKdZV1tdsiDYMmxybjTaXbVFbF9ss2wrbS3aonaudwG6jXecowij3UcJRVaNu2qvYM+0L7WvsHzroOIQ5FDs0OLwcbT46ZfSK0WdHf3N0ccx13O54d4zmmJAxxWOax7x2snXiOlU4XRtLHRs4ds7YxrGvnO2c+c6bnG+50FzCXRa6tLp8dXVzFbvWuva6mbuluVW63WRoMaIYSxjn3Anufu5z3I+6f/Rw9SjwOODxl6e9Z47nbs+ecVbj+OO2j3vsZerF8drq1eVN907z3uLd5WPiw/Gp8nnka+bL893h+4xpw8xm7mG+9HP0E/sd9nvP8mDNYp3wx/yD/Ev9OwI0A+IDNgQ8CDQNzAysCewPcgmaEXQimBAcGrwi+CbbkM1lV7P7Q9xCZoW0haqExoZuCH0UZhsmDmsOR8NDwleF34uwiBBGNESCSHbkqsj7UVZRU6KORBOjo6Irop/GjImZGXM2lhY7KXZ37Ls4v7hlcXfjreMl8a0JagmpCdUJ7xP9E1cmdiWNTpqVdDFZP1mQ3JhCSklI2ZEyMD5g/Jrx3akuqSWpNyZYTZg24fxE/Ym5E49NUpvEmXQwjZCWmLY77QsnklPFGUhnp1em93NZ3LXcFzxf3mpeL9+Lv5L/LMMrY2VGT6ZX5qrM3iyfrPKsPgFLsEHwKjs4e3P2+5zInJ05g7mJuXV5SnlpeU1CTWGOsG2y0eRpkztFdqISUdcUjylrpvSLQ8U78pH8CfmNBVrwQ75dYi35RfKw0LuwovDD1ISpB6dpTBNOa59uO33x9GdFgUW/zcBncGe0zjSZOW/mw1nMWVtnI7PTZ7fOMZuzYE733KC5u+ZR5uXM+73YsXhl8dv5ifObFxgumLvg8S9Bv9SUqJaIS24u9Fy4eRG+SLCoY/HYxesXfyvllV4ocywrL/uyhLvkwq9jfl336+DSjKUdy1yXbVpOXC5cfmOFz4pdKzVWFq18vCp8Vf1q+urS1W/XTFpzvty5fPNaylrJ2q51Yesa15uvX77+y4asDdcr/CrqKg0qF1e+38jbeGWT76bazYabyzZ/2iLYcmtr0Nb6Ksuq8m3EbYXbnm5P2H72N8Zv1Tv0d5Tt+LpTuLNrV8yutmq36urdBruX1aA1kprePal7Lu/139tYa1+7tU6nrmwf2CfZ93x/2v4bB0IPtB5kHKw9ZHGo8jDtcGk9Uj+9vr8hq6GrMbmxsymkqbXZs/nwEYcjO4+aHK04pn1s2XHK8QXHB1uKWgZOiE70ncw8+bh1UuvdU0mnrrVFt3WcDj197kzgmVNnmWdbznmdO3re43zTBcaFhouuF+vbXdoP/+7y++EO1476S26XGi+7X27uHNd5/IrPlZNX/a+euca+dvF6xPXOG/E3bt1Mvdl1i3er53bu7Vd3Cu98vjv3HuFe6X31++UPDB5U/WHzR12Xa9exh/4P2x/FPrr7mPv4xZP8J1+6FzylPi1/Zvysusep52hvYO/l5+Ofd78QvfjcV/Knxp+VL61fHvrL96/2/qT+7lfiV4Ovl7zRe7PzrfPb1oGogQfv8t59fl/6Qe/Dro+Mj2c/JX569nnqF9KXdV9tvjZ/C/12bzBvcFDEEXNknwIYrGhGBgCvdwJATQaABs9nlPHy85+sIPIzqwyB/4TlZ0RZcQWgFn6/R/fBr5ubAOzbDo9fUF8tFYAoKgBx7gAdO3a4Dp3VZOdKaSHCc8CW+K/peeng3xT5mfOHuH/ugVTVGfzc/wtgaXxgPjyFpAAAAIplWElmTU0AKgAAAAgABAEaAAUAAAABAAAAPgEbAAUAAAABAAAARgEoAAMAAAABAAIAAIdpAAQAAAABAAAATgAAAAAAAACQAAAAAQAAAJAAAAABAAOShgAHAAAAEgAAAHigAgAEAAAAAQAAASSgAwAEAAAAAQAAANAAAAAAQVNDSUkAAABTY3JlZW5zaG90UFx6DwAAAAlwSFlzAAAWJQAAFiUBSVIk8AAAAdZpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IlhNUCBDb3JlIDYuMC4wIj4KICAgPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4KICAgICAgPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIKICAgICAgICAgICAgeG1sbnM6ZXhpZj0iaHR0cDovL25zLmFkb2JlLmNvbS9leGlmLzEuMC8iPgogICAgICAgICA8ZXhpZjpQaXhlbFlEaW1lbnNpb24+MjA4PC9leGlmOlBpeGVsWURpbWVuc2lvbj4KICAgICAgICAgPGV4aWY6UGl4ZWxYRGltZW5zaW9uPjI5MjwvZXhpZjpQaXhlbFhEaW1lbnNpb24+CiAgICAgICAgIDxleGlmOlVzZXJDb21tZW50PlNjcmVlbnNob3Q8L2V4aWY6VXNlckNvbW1lbnQ+CiAgICAgIDwvcmRmOkRlc2NyaXB0aW9uPgogICA8L3JkZjpSREY+CjwveDp4bXBtZXRhPgrJWtfDAAAAHGlET1QAAAACAAAAAAAAAGgAAAAoAAAAaAAAAGgAABB883Cf8wAAEEhJREFUeAHsnXnMXcMfxr8tbak2KYmlKKlqQ9A2DYml1tBSSlSqlpDYCf7QCKG2JlQsVUsRWhUllraaoqW1U6JUqaUIJVTbN9qglFqK+5tn4ju/ueee5Z773vs6d+4zyX1nnzPzmXOed86cmXM6lYwRGhIgARIoAIFOFKQC9AKrQAIkYAlQkHgikAAJFIYABakwXcGKkAAJUJB4DpAACRSGAAWpMF3BipAACVCQeA6QAAkUhgAFqTBdwYqQAAlQkHgOkAAJFIYABakwXcGKkAAJUJB4DpAACRSGAAWpMF3BipAACXQat2As97LxPCABEigEAQpSIbqBlSABEgAB3rLxPCABEigMAQpSYbqCFSEBEqAg8RwgARIoDAEKUmG6ghUhARKgIPEcIAESKAwBClJhuoIVIQESoCDxHCABEigMAQpSYbqCFSEBEqAg8RwgARIoDAEKUmG6ghUhARKgIPEcIAESKAyBqgTpn3/+kba2Nlm3bp1s3LixMJVnRUiABMIikClIEKPly5fLhg0bwmo5W0MCJFA4ApmCtGrVKlm7dq10795ddtxxR2sXrhWsEAmQQBAEMgVp2bJl9jZtwIABFKMgupyNIIHiEsgUpKVLl9raDx48uLitYM1IgASCIEBBCqIb2QgSCIMABSmMfmQrSCAIAhSkILqRjSCBMAhQkMLoR7aCBIIgQEEKohvZCBIIgwAFKYx+ZCtIIAgCFKQgupGNIIEwCFCQwuhHtoIEgiBAQQqiG9kIEgiDAAUpjH5kK0ggCAIUpCC6kY0ggTAIUJDC6Ee2ggSCIEBBCqIb2QgSCIMABSmMfmQrSCAIAhSkILqRjSCBMAhQkMLoR7aCBIIgQEEKohvZCBIIgwAFKYx+ZCtIIAgCFKQgupGNIIEwCFCQwuhHtoIEgiBAQQqiG9kIEgiDAAUpjH5kK0ggCAIUpCC6kY0ggTAIdKgg4bPc33//vWy22WbSs2fPuhNE+WvWrJFevXrZY9T9ACyQBEigoQQaKkjLly+XBQsWyLx58+Sjjz6SlStXusbgs9z77LOPDBw4UEaNGmVtF1ml46+//pLp06fL22+/LR9++KEsWrTI5UT5gwYNkjPPPFNGjhwpXbp0cXF0kAAJFJNAwwTp1FNPlUcffbTqVk+dOlXOOuusqtPjE99nnHGGLF68ODPPNttsI++++6706dMnMy0TkAAJ/HcEGiZIw4YNkxdeeCFXyzCamTJlinTu3Dk1H9Kce+65qWmikR9//LHsscce0WD6SYAECkSg4YKEuaJzzjlHDjroINlll12kb9++UiqV7O3bjBkz5LrrrivDgVs8iFmSwW1g//79y6JxezZhwgR727frrrtKW1ubfPrppzJ37ly5//77bVoKUhkyekigmASMOKSa999/v4RfXnP55ZeXjBiU1q9fn5r1pZdeKhky7nf00Uenpke8n3706NGlH3/8MTHPwoULSwMGDCgZQUpMwwgSIIFiEGjYCCmP/I4YMUKee+45lwVPyzp16uT86pg/f74cddRR6hWMjDBi6tatmwuLc2DyG2bTTTeNi2YYCZBAQQgUQpCuvPJKufHGGx0SLA3YaqutnF8dl156qUycOFG9Mm3aNDux7QLoIAESaGoCHSJI5pZKPv/8c1m1apX9Ya3Q33//7cDhCZg/AY7lATvssIOLV8cxxxxjlxCo/48//pCuXbuqlzYJkECTE2ioIH399dcyadIkufPOO3NhWr16tfTu3bsiT79+/eSrr76y4Zgg//LLLyvSMIAESKB5CTRMkGbNmiVmwrkmMpgXgvj45vfff5fNN9/cBQ0fPlwwp0RDAiQQDoGGCBJuwbAK2zd4/H/aaaeJeeIl2223nfTo0cNFP/HEE/Lwww87f5wg4bbPn1c67rjjZM6cOS4PHSRAAgEQyHrYV8tjfyM8ZY/mzTqk0i+//JJ4qKuuuqosvRGk2LRG1Fw6s+UkNg0DSYAEmpdA3UdImGjG5lk1Q4YMsXvN0h65jxkzRrBIUk3cCAlxQ4cOlTfffFOT2QWWzlMHBxZT4hc1mDjHhDoNCZBAgwlkaWneEdKKFSvcKMZUvXT11VenHsI8bSuZCeqyPEkjJIy0UKb+3nnnndSy80Zef/31rmw9BmyMzGhIgAQaTwCjjFSTV5DMk7Wyi3r8+PGp5ZvJ77L0EIAkQZo8eXJZWrNIMrXsvJEUpLzEmJ4E6kug7oKEEY8/uth3331LCIsz2PKBbR1++jRBwjyU2blflv7xxx+PK9qFbdy4sXTXXXeVvvvuOxeW5KAgJZFhOAl0DIG6CxKqDRHyRQb72n777beyFn3wwQcVt2qaJ2mEhAJmzpxZVjbyXHLJJaUNGzaUlW+2n5SWLFlSMnNYNn01e9koSGUI6SGBDidQ90ltIxB2NXV0Ehj7zrDjH68WMXM/duU20saZpEltpDWE7H42vBUgavCytz333FO+/fZbWbp0qZiNvS5JNbv9b7jhBjFP/FwedWDJws8//6xe2iRAAo0ikCWBeeeQtDzzvqKKkYxpQ0UYHt/fdNNNZeFpIySUbxZJlq655pqyPHFl+2EcIWnP0CaB4hJoyC0bmotbJrPY0T6h8oXBd+NW69dff7Xp/PAsQVKcZgFmxe2hXw7ceF2JeZNAyez412yJdlQYzRsHSvhtu+22iXniItB2/sggpHMg7jxvRFhDbtmMEDhj5nbks88+c5trsdoaG2cPOOAA2WKLLVy69jjMxLXd14bjYK8bjoG9bnhZ2/bbb9+eonPnNZ2UOw8zkEAzEIh7JVC9691wQap3hYtcHsWoyL3DutWDQKNFiYJUZS9RbKoExWQtT6A9okVBquL0oRhVAYlJSMAjUKsoUZA8iHHO9ohRe/LG1YVhJNDRBGoVFtSzlrwUpEgPt0dE2pM3Ug16SaCQBGoRGW1INXkpSErL2LUKSkfn86pMJwm0i0A1IhF3gEbloyD9S7taUakmXTVp4jqZYSRQVALVCFA1adC+tHQUJAOoGgFJS5MWpydYNWk0LW0S+C8JpAmG1istTVpcVv6WF6QsoUiLT4pLCtfOyIrXdLRJoKMIZIlIUnxSOOqdFpcU39KClCUMcfHRsKjfP4HS4vx0ce725I0rj2GtSyBLGNLIpOWNxkX9KDcuzD9eNL5lBSnrgo/G5/X70KN5/bg87nqVk+eYTNtcBKIXeK21TysnGpfXH62Tn78lBSnrwo7G+3648ZFLvHsbH74073mK8qWfBFqCAD5Lhn2pu+++u2yyySZloyFfZAAj6o8C0viWEyRfXKJQonG+X90Qo4ULF8q6deui2ekngZYk0KtXLznwwAOtKAGAikvUHedHmBrkaylBUlFRAGpHw31/1I0XveGLuXijwODBgwVvM4A44Y0DNCTQCgS6dOkiEKHu3bvbFyH+8MMP9sOueDlikhj54WAU9Su3lhEkX1i08Wr7cVnu559/3t6mHXbYYbJ27VorSFoObRJoJQIQpK233lpefvll+1XpYcOG2eb7YlON22fWEoLki4zfeLj9uDh3NOzpp5+2RZj3hltBipZHPwm0EgEI0qJFi2yTjz322LKRj4qR2kiU5FZmwQuSLyjaaN/WeLURp261/TAVpN122423aT5IuluSAG7f8GJEGAgSTJzoZIXZjMhrLrrUVxziZfkwmC9pRpPWPI1TG+1Td5L9zDPPWAz9+vVrRhysMwnUnQDmVGFGjhxpbRWfJBuJonE2I8LNhdeSguQ3W91ptsbNnTvXsqMg6SlEu9UJqCDpl4YgNlHBSfKDncZZt7nQWk6Q/CarO81GnP6effZZe/5RkCyGuv6ZOHGiXduFQnv06CHjxo2Tbt261fUYLCybwM033yzmu4ku4a233iq9e/d2/qhDBWnEiBFWXFSQVGiybJTn0pgLLVhBimtaNAx+DYuzNV5t8wUT2x95BQnrl3DBRZcHnHzyyfaDBNFObkX/eeedJ1988YVr+pNPPilbbrml84fqwBwM2lqrwXzmCSecUGv2inzmw66yePFiF/7QQw9Jnz59nD/qUEEyn7avECQnNGbUBOP71a3lwZ95y7Zs2TJ7EZlPXtt1B5q56LaKS7Sefri6VWyQNhqmcWrPnz/fFplXkD755BO56KKLotWRU045Rc4+++yK8KIE4KOb5mvBrjr777+/4AljI0wzCdJbb70l+KkZM2aMXbWs/jw2Rt0YhdRqDj74YLn22mtrzV6Rr1ZBOvLII2MFSYXHt323VqAqQcL2CKy3wZoDfH0WdtGNikq0nn64uuNshCX99Iu5eQVp2rRp8sgjj0SrZJlOnz69IrwoAW+88YaYj3K66px44oly/vnnO389Hc0kSPfcc4/MmjXLNR9fPd5vv/2cP48jFEEaPnx4oiBZsYkZJYGTipN1mwsv9ZbNfOxO8GlrrEhuFuM3yW+sH65t8cPUrTbSqFvtFStW2Kx5Ben000+XlStX6mHL7KwhcVniDvZ0pCBhBImRpJoi37LVU5AwDXDLLbdos3Pbo0ePlgsuuCB3vqQMtY6QdtppJ1ukXnNqI1DdavthNtO/fzJv2ZAOotTW1tY0WyRUPPzGaxjaAzfi/DCEw2iYb6sb8bUIEoQIgpRkLrzwwrrOASQdp5bwjhQk83Vh+fPPP101sXnT70MXUQBHPQUpqTmPPfaYTJkyxUXvtddecscddzh/oxztFSTUC/2mfRe1/XojDteXS2M8qSMkP3OzuOOa5IepG3bUrWFqQ4zVDfvFF1+0GPKMkPCf/u6773b4sO0Ey+3VdNSJpsfLY3ekIOWp13+dloL0/x7QSe3DDz/cCREEpnPnzmV+hOkPuaNuG2YuspYSJG1unI0w/KIipH7YKiR5BGns2LF2E6J24X333Sf4L+S/MWD27Nl2w6KmqaeNJ3s//fST9OzZM/dj9FAECVMO+NXCIK4vQhAknH94bQiY+KbWERL+0aoIqa2iE/XjeIiL2lXdstlcTfRHxcavsob5dtQNv/5UhOBXN+xXXnnFFlutIEEIjj/+eFcVPBTAau/bbrtN5s2b58KvuOIKOeKII5y/Gsf69evlgQcecEnx+BdPOmBwiw2Re/3118v23GGXNp6SYd6hb9++Li8ct99+e5kfHjzUWLJkiQtH/fGfMMkMHTpU9t5776RoG44L4amnnrJc0xKedNJJdtNmWpqkOLQfk854OwPa4M+Bog3YgzVkyBA7ET1w4EDp2rVrbFGrV6+WGTNmVMRhnc4333zjwnfeeWcZNGiQ80cdeAqXtpYnmh7+et+y4XYYE+hg8t5775X9Q8S5hwcWOK9rFaRDDz00VpCiYqQihTZWuM0F13IjJG2yb8OtwgO379dw2K+++io42o6zjow/uMWbMGGCSwXBuOyyy+S1116T8ePHu/BaHt2uWbNGcNGq6d+/v2D0BaHDmqcsM2nSpLKLCP/h2mvwBA4ndprBUpKLL744LYmNq2VSG/NQkydPFt1zmHkQkwBi8uCDD8YmTVquEZs4JRAjKvzDyGPqKUj4J4CnpRCjNHPvvfcKngjXsg7pkEMOqRCkqBj5ftTDHyXB/T8AAAD//4lh1DMAACIlSURBVO2dCdxtU/nH1+UqkU+G5GMI6d8kQzdDGlBEH0XIkDKFFIWkaJJKlFQfJUMSGRLSIJEkPgoproQMRddMpTJGcnP+67vu/e377Oesfc4+9773fd/zvut5P+t9hrX22ms9Z6/fedbaa+8zpRMpTDDyXZKe49h8euaZZyobsnT4r371q+StF7/4xa28duihh4bLLrusKnvwwQeHjTbaKDzyyCNh6623ruwIF154YXj2s59ds/VS/v73v4cddtihKvKSl7wkbL755uGoo46qbL2EI444Iqy77rpVEdo1r7TXXnuF7bffvmc1N910U9h33317liHzhz/8YVhiiSX6lrMFvvKVr4Sf/exn1tRXXnbZZcMZZ5yRLXfzzTeHffbZJ5s3iPG4444LL3/5ywc5JJx55pnhxBNPrI5ZffXVw9e//vVKbyvcc8894aMf/Wh48MEH+x6yyCKLhCeeeKJW7tRTTw0vfOELazar/OUvf0nqhhtuGBZYYIEwZcqUxCVLh+cSB2NPPA7GSQlI6rYFIwEPNslwJey//vWvk+PaANJTTz0VNttss1Re/370ox+FxRdfPKl77713+NOf/qSs4AGiymgQPCDlLqall146vOxlLwtLLrlkuO+++8Itt9xSXXD+fKeddlrXme68884aoK666qo1EPMHvPnNbw7LLbecN9f0Bx54IIFNzRiVn//851XbyBsUkK6//vrw4Q9/uFYt/d9iiy3CyiuvHBZddNHwj3/8I3B+/H7VVVelsr0ACR/TLk8caz87wHzFFVf0xZK+0EILhXe84x0Dfdlw4EgB0kc+8pFw3XXX1doGuL3mNa8J+Oe2225LX7RNgNUWkDbYYIMaGAmQxHPARKMEUkmOg2xCA5LtnmS4lwVA2CULiMTJu/zyy9MH2waQpk+fHg466KBUnn9EMCeccEKln3766eE73/lOpW+55ZbhQx/6UKX3Ezwg2fKrrLJKOOSQQ7oGyf/+9780wI4//viUbyMke7zkK664IpWTTvRDFDQ/6P3vf38aHKp7UEA65phjAoAvmjZtWgJ5ACFHf/vb38J5550X7r///vCZz3wmV6TRRsTzgx/8oMo//PDDw2tf+9pKHwlhJACJL6APfvCDteZsu+22gS9DRSVkPvzwwymKmjFjRq0sSltAWn/99bsASWAk7kGJ+gsgzQYkAZPlTWAkOwMUagNIRx99dDj33HNTef7tuuuuKcngpwNETgxCe6GobI43AdImm2wS+FZ81rOe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hzTeKQJJx6cIKh3SzJkzU4q/ntL573//23nqqac68du48+STT3bi7811YmTRefTRRzsxuug89NBDnTjYO3GwdGJk0IkDrxMHe+fOO+/sxPe+dM4555yUvD/iviF+vaVKu+++eyf+Rnqlkxd/S94fVun77rtvrWy87V/leWGLLbaolbXnzclxgHfOP/98X01Nj789X6szAlpNV7206+677+5EwGjMv+OOO2p155QLL7ywQ12qtxc//vjjc1UU2zjxgMYE44Nxwnhh3DB+GEeMJ8YV44txxnhj3DH+GIeMR8Yl43MKfYoXQyOV14+0e/2I34cUASmwWZLfTbcUB2s2WhkkQmJKpqjE1t1PPvjgg9NGzdzU6pRTTgm77bZbvypa5b/vfe8LJ5xwQrZsvPjCJz7xiRRJZQs0GLfffvsUweXa3nBIMY+SB0YyQiqAZKZpYPNITdkApJNOOikw9TnyyCOrSyNGQuHoo4+udAnzAkhMD9nZzY9Xsv7CD3uySfPYY4+tTeM416mnnhp22WUXnbbiOUDaZ599wkYbbRR+/OMfh9NPP70qK4Hz7rjjjuGGG25I0zfZmXLFb0KpNc40j93rligfo8ew5pprhkUXXTTceuutIX7rVlM4lY2RUthrr72kFj5OPDCSgFSmbPNxykb0ee+993ZNS+IiMFk1GmTKxlSHKeI3v/nNFALXKjLKww8/3ImgUTt/XI9JU1tTLIl+yrbJJptURZj2+ika5yfMFu2888618xC2e7rrrrtqZeJ46sQtESnE92UJ5/fff/9aeaaeEWx90aKPsQdGcspWAGk+AxLXyt57710bWHHK0nUJDQJIMSLJgkpXpdEQHz/pMJAZ/ErXX399V1EPSEcccUStzCc/+cnqeOqhT5ZOPvnkWv5VV11ls5O8ww471MoAchbUug6IBr9eBkgVGl8eKICU+Tz8gjb6WC9qq5m33XZbbSAyoOPtbWUnPggg1Q5socT1l9r544O8XUd5QIp372pliMYEaPDjjjuuln/RRRfV8s8999xa/o033ljLpw780o9YILfnXWWVVfodUvJH2QMFkDIOH8+ARHP9lObwww+v9WJ+AlJcs6kN6rjgXDs3igekSy+9tFYm3p6v1RHXlGr5REQWOL73ve/V8v3xb3zjG2v5vRR/x4+paKHx44GRBKSyqG0WsuNHPOKL2nGQJooR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" } }, "cell_type": "markdown", "metadata": {}, "source": [ "Auto-complete works for columns too:\n", "\n", "![](attachment:image.png){width=140}" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Columns, tables, and view stringify in a format suitable for including in SQL statements. That means you can use auto-complete in f-strings." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "select * from \"Artist\" where \"Artist\".\"Name\" like 'AC/%'\n" ] } ], "source": [ "qry = f\"select * from {artist} where {ac.Name} like 'AC/%'\"\n", "print(qry)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "You can view the results of a select query using `q`:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "[{'ArtistId': 1, 'Name': 'AC/DC'}]" ] }, "execution_count": null, "metadata": {}, "output_type": "execute_result" } ], "source": [ "db.q(qry)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Views can be accessed through the `v` property:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "[{'AlbumId': 1,\n", " 'Title': 'For Those About To Rock We Salute You',\n", " 'ArtistId': 1},\n", " {'AlbumId': 4, 'Title': 'Let There Be Rock', 'ArtistId': 1}]" ] }, "execution_count": null, "metadata": {}, "output_type": "execute_result" } ], "source": [ "album = dt.Album\n", "\n", "acca_sql = f\"\"\"select {album}.*\n", "from {album} join {artist} using (ArtistId)\n", "where {ac.Name} like 'AC/%'\"\"\"\n", "\n", "db.create_view(\"AccaDaccaAlbums\", acca_sql, replace=True)\n", "acca_dacca = db.q(f\"select * from {db.v.AccaDaccaAlbums}\")\n", "acca_dacca" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Dataclass support" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "A `dataclass` type with the names, types, and defaults of the tables is created using `dataclass()`:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "album_dc = album.dataclass()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Let's try it:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "Album(AlbumId=1, Title='For Those About To Rock We Salute You', ArtistId=1)" ] }, "execution_count": null, "metadata": {}, "output_type": "execute_result" } ], "source": [ "album_obj = album_dc(**acca_dacca[0])\n", "album_obj" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "You can get the definition of the dataclass using fastcore's `dataclass_src` -- everything is treated as nullable, in order to handle auto-generated database values:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [ { "data": { "text/markdown": [ "```python\n", "@dataclass\n", "class Album:\n", " AlbumId: int | None = None\n", " Title: str | None = None\n", " ArtistId: int | None = None\n", "\n", "```" ], "text/plain": [ "" ] }, "execution_count": null, "metadata": {}, "output_type": "execute_result" } ], "source": [ "src = dataclass_src(album_dc)\n", "hl_md(src, 'python')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Because `dataclass()` is dynamic, you won't get auto-complete in editors like vscode -- it'll only work in dynamic environments like Jupyter and IPython. For editor support, you can export the full set of dataclasses to a module, which you can then import from:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "create_mod(db, 'db_dc')" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "Track(TrackId=None, Name=None, AlbumId=None, MediaTypeId=None, GenreId=None, Composer=None, Milliseconds=None, Bytes=None, UnitPrice=None)" ] }, "execution_count": null, "metadata": {}, "output_type": "execute_result" } ], "source": [ "#| eval: false\n", "from db_dc import Track\n", "Track()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Indexing into a table does a query on primary key:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "Track(TrackId=1, Name='For Those About To Rock (We Salute You)', AlbumId=1, MediaTypeId=1, GenreId=1, Composer='Angus Young, Malcolm Young, Brian Johnson', Milliseconds=343719, Bytes=11170334, UnitPrice=0.99)" ] }, "execution_count": null, "metadata": {}, "output_type": "execute_result" } ], "source": [ "dt.Track[1]" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "There's a shortcut to select from a table -- just call it as a function. If you've previously called `dataclass()`, returned iterms will be constructed using that class by default. There's lots of params you can check out, such as `limit`:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "[Album(AlbumId=1, Title='For Those About To Rock We Salute You', ArtistId=1),\n", " Album(AlbumId=2, Title='Balls to the Wall', ArtistId=2)]" ] }, "execution_count": null, "metadata": {}, "output_type": "execute_result" } ], "source": [ "album(limit=2)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Pass a truthy value as `with_pk` and you'll get tuples of primary keys and records:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "[(1,\n", " Album(AlbumId=1, Title='For Those About To Rock We Salute You', ArtistId=1)),\n", " (2, Album(AlbumId=2, Title='Balls to the Wall', ArtistId=2))]" ] }, "execution_count": null, "metadata": {}, "output_type": "execute_result" } ], "source": [ "album(with_pk=1, limit=2)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Indexing also uses the dataclass by default:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "Album(AlbumId=5, Title='Big Ones', ArtistId=3)" ] }, "execution_count": null, "metadata": {}, "output_type": "execute_result" } ], "source": [ "album[5]" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "If you set `xtra` fields, then indexing is also filtered by those. As a result, for instance in this case, nothing is returned since album 5 is not created by artist 1:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Not found\n" ] } ], "source": [ "album.xtra(ArtistId=1)\n", "\n", "try: album[5]\n", "except NotFoundError: print(\"Not found\")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The same filtering is done when using the table as a callable:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "[Album(AlbumId=1, Title='For Those About To Rock We Salute You', ArtistId=1),\n", " Album(AlbumId=4, Title='Let There Be Rock', ArtistId=1)]" ] }, "execution_count": null, "metadata": {}, "output_type": "execute_result" } ], "source": [ "album()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Core design" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The following methods accept `**kwargs`, passing them along to the first `dict` param:\n", "\n", "- `create`\n", "- `transform`\n", "- `transform_sql`\n", "- `update`\n", "- `insert`\n", "- `upsert`\n", "- `lookup`" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "We can access a table that doesn't actually exist yet:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "
" ] }, "execution_count": null, "metadata": {}, "output_type": "execute_result" } ], "source": [ "cats = dt.cats\n", "cats" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "We can use keyword arguments to now create that table:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [ { "data": { "text/markdown": [ "```sql\n", "CREATE TABLE [cats] (\n", " [id] INTEGER PRIMARY KEY,\n", " [name] TEXT,\n", " [weight] FLOAT,\n", " [uid] INTEGER\n", ")\n", "```" ], "text/plain": [ "" ] }, "execution_count": null, "metadata": {}, "output_type": "execute_result" } ], "source": [ "cats.create(id=int, name=str, weight=float, uid=int, pk='id')\n", "hl_md(cats.schema, 'sql')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "It we set `xtra` then the additional fields are used for `insert`, `update`, and `delete`:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "cats.xtra(uid=2)\n", "cat = cats.insert(name='meow', weight=6)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The inserted row is returned, including the xtra 'uid' field." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "{'id': 1, 'name': 'meow', 'weight': 6.0, 'uid': 2}" ] }, "execution_count": null, "metadata": {}, "output_type": "execute_result" } ], "source": [ "cat" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Using `**` in `update` here doesn't actually achieve anything, since we can just pass a `dict` directly -- it's just to show that it works:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "[{'id': 1, 'name': 'moo', 'weight': 6.0, 'uid': 2}]" ] }, "execution_count": null, "metadata": {}, "output_type": "execute_result" } ], "source": [ "cat['name'] = \"moo\"\n", "cat['uid'] = 1\n", "cats.update(**cat)\n", "cats()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Attempts to update or insert with xtra fields are ignored.\n", "\n", "An error is raised if there's an attempt to update a record not matching `xtra` fields:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Not found\n" ] } ], "source": [ "cats.xtra(uid=1)\n", "try: cats.update(**cat)\n", "except NotFoundError: print(\"Not found\")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "This all also works with dataclasses:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "Cats(id=1, name='moo', weight=6.0, uid=2)" ] }, "execution_count": null, "metadata": {}, "output_type": "execute_result" } ], "source": [ "cats.xtra(uid=2)\n", "cats.dataclass()\n", "cat = cats[1]\n", "cat" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "
" ] }, "execution_count": null, "metadata": {}, "output_type": "execute_result" } ], "source": [ "cats.drop()\n", "cats" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Alternatively, you can create a table from a class. If it's not already a dataclass, it will be converted into one. In either case, the dataclass will be created (or modified) so that `None` can be passed to any field (this is needed to support fields such as automatic row ids)." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "class Cat: id:int; name:str; weight:float; uid:int" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "cats = db.create(Cat)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [ { "data": { "text/markdown": [ "```sql\n", "CREATE TABLE [cat] (\n", " [id] INTEGER PRIMARY KEY,\n", " [name] TEXT,\n", " [weight] FLOAT,\n", " [uid] INTEGER\n", ")\n", "```" ], "text/plain": [ "" ] }, "execution_count": null, "metadata": {}, "output_type": "execute_result" } ], "source": [ "hl_md(cats.schema, 'sql')" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "Cat(id=1, name='咪咪', weight=9.0, uid=None)" ] }, "execution_count": null, "metadata": {}, "output_type": "execute_result" } ], "source": [ "cat = Cat(name='咪咪', weight=9)\n", "cats.insert(cat)\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "cats.drop()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Manipulating data" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "We try to make the following methods as flexible as possible. Wherever possible, they support Python dictionaries, dataclasses, and classes. " ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### .insert()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Creates a record. Returns an instance of the updated record.\n", "\n", "Insert using a dictionary." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "Cat(id=1, name='Rex', weight=12.2, uid=UNSET)" ] }, "execution_count": null, "metadata": {}, "output_type": "execute_result" } ], "source": [ "cats.insert({'name': 'Rex', 'weight': 12.2})" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Insert using a dataclass." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "Cat(id=2, name='Tom', weight=10.2)" ] }, "execution_count": null, "metadata": {}, "output_type": "execute_result" } ], "source": [ "CatDC = cats.dataclass()\n", "cats.insert(CatDC(name='Tom', weight=10.2))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Insert using a standard Python class" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "cat = cats.insert(Cat(name='Jerry', weight=5.2))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### .update()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Updates a record using a Python dict, dataclass, or object, and returns an instance of the updated record.\n", "\n", "Updating from a Python dict:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "Cat(id=3, name='Jerry', weight=6.2)" ] }, "execution_count": null, "metadata": {}, "output_type": "execute_result" } ], "source": [ "cats.update(dict(id=cat.id, name='Jerry', weight=6.2))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Updating from a dataclass:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "Cat(id=3, name='Jerry', weight=6.3)" ] }, "execution_count": null, "metadata": {}, "output_type": "execute_result" } ], "source": [ "cats.update(CatDC(id=cat.id, name='Jerry', weight=6.3))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Updating using a class:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "Cat(id=3, name='Jerry', weight=5.7)" ] }, "execution_count": null, "metadata": {}, "output_type": "execute_result" } ], "source": [ "cats.update(Cat(id=cat.id, name='Jerry', weight=5.7))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### .delete()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Removing data is done by providing the primary key value of the record." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "
" ] }, "execution_count": null, "metadata": {}, "output_type": "execute_result" } ], "source": [ "# Farewell Jerry!\n", "cats.delete(cat.id)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Multi-field primary keys" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Pass a collection of strings to create a multi-field pk:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "CREATE TABLE [pet_food] (\n", " [catid] INTEGER,\n", " [food] TEXT,\n", " [qty] INTEGER,\n", " PRIMARY KEY ([catid], [food])\n", ")\n" ] } ], "source": [ "class PetFood: catid:int; food:str; qty:int\n", "petfoods = db.create(PetFood, pk=['catid','food'])\n", "print(petfoods.schema)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "You can index into these using multiple values:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "PetFood(catid=1, food='tuna', qty=2)" ] }, "execution_count": null, "metadata": {}, "output_type": "execute_result" } ], "source": [ "pf = petfoods.insert(PetFood(1, 'tuna', 2))\n", "petfoods[1,'tuna']" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Updates work in the usual way:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "PetFood(catid=1, food='tuna', qty=3)" ] }, "execution_count": null, "metadata": {}, "output_type": "execute_result" } ], "source": [ "pf.qty=3\n", "petfoods.update(pf)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "You can also use `upsert` to update if the key exists, or insert otherwise:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "[PetFood(catid=1, food='tuna', qty=1)]" ] }, "execution_count": null, "metadata": {}, "output_type": "execute_result" } ], "source": [ "pf.qty=1\n", "petfoods.upsert(pf)\n", "petfoods()" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "[PetFood(catid=1, food='tuna', qty=1), PetFood(catid=1, food='salmon', qty=1)]" ] }, "execution_count": null, "metadata": {}, "output_type": "execute_result" } ], "source": [ "pf.food='salmon'\n", "petfoods.upsert(pf)\n", "petfoods()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "`delete` takes a tuple of keys:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "[PetFood(catid=1, food='salmon', qty=1)]" ] }, "execution_count": null, "metadata": {}, "output_type": "execute_result" } ], "source": [ "petfoods.delete((1, 'tuna'))\n", "petfoods()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Diagrams" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "If you have [graphviz](https://pypi.org/project/graphviz/) installed, you can create database diagrams. Pass a subset of tables to just diagram those. You can also adjust the size and aspect ratio." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [ { "data": { "image/svg+xml": [ "\n", "\n", "\n", "\n", "\n", "\n", "G\n", "\n", "\n", "\n", "Artist\n", "\n", "\n", "Artist\n", "\n", "\n", "ArtistId 🔑\n", "\n", "Name\n", "\n", "\n", "\n", "\n", "Album\n", "\n", "\n", "Album\n", "\n", "\n", "AlbumId 🔑\n", "\n", "Title\n", "\n", "ArtistId\n", "\n", "\n", "\n", "\n", "Album:ArtistId->Artist:ArtistId\n", "\n", "\n", "\n", "\n", "\n", "Track\n", "\n", "\n", "Track\n", "\n", "\n", "TrackId 🔑\n", "\n", "Name\n", "\n", "AlbumId\n", "\n", "MediaTypeId\n", "\n", "GenreId\n", "\n", "Composer\n", "\n", "Milliseconds\n", "\n", "Bytes\n", "\n", "UnitPrice\n", "\n", "\n", "\n", "\n", "Track:AlbumId->Album:AlbumId\n", "\n", "\n", "\n", "\n", "\n", "Genre\n", "\n", "\n", "Genre\n", "\n", "\n", "GenreId 🔑\n", "\n", "Name\n", "\n", "\n", "\n", "\n", "Track:GenreId->Genre:GenreId\n", "\n", "\n", "\n", "\n", "\n", "MediaType\n", "\n", "\n", "MediaType\n", "\n", "\n", "MediaTypeId 🔑\n", "\n", "Name\n", "\n", "\n", "\n", "\n", "Track:MediaTypeId->MediaType:MediaTypeId\n", "\n", "\n", "\n", "\n", "\n" ], "text/plain": [ "" ] }, "execution_count": null, "metadata": {}, "output_type": "execute_result" } ], "source": [ "diagram(db.t['Artist','Album','Track','Genre','MediaType'], size=8, ratio=0.4)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Importing CSV/TSV/etc" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [ { "data": { "text/markdown": [ "---\n", "\n", "[source](https://github.com/AnswerDotAI/fastlite/blob/main/fastlite/core.py#LNone){target=\"_blank\" style=\"float:right; font-size:smaller\"}\n", "\n", "### Database.import_file\n", "\n", "> Database.import_file (table_name, file, format=None, pk=None,\n", "> alter=False)\n", "\n", "*Import path or handle `file` to new table `table_name`*" ], "text/plain": [ "---\n", "\n", "[source](https://github.com/AnswerDotAI/fastlite/blob/main/fastlite/core.py#LNone){target=\"_blank\" style=\"float:right; font-size:smaller\"}\n", "\n", "### Database.import_file\n", "\n", "> Database.import_file (table_name, file, format=None, pk=None,\n", "> alter=False)\n", "\n", "*Import path or handle `file` to new table `table_name`*" ] }, "execution_count": null, "metadata": {}, "output_type": "execute_result" } ], "source": [ "show_doc(Database.import_file)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "You can pass a file name, string, bytes, or open file handle to `import_file` to import a CSV:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "[{'id': 1, 'name': 'Alice', 'age': 30},\n", " {'id': 2, 'name': 'Bob', 'age': 25},\n", " {'id': 3, 'name': 'Charlie', 'age': 35}]" ] }, "execution_count": null, "metadata": {}, "output_type": "execute_result" } ], "source": [ "db = Database(\":memory:\")\n", "csv_data = \"\"\"id,name,age\n", "1,Alice,30\n", "2,Bob,25\n", "3,Charlie,35\"\"\"\n", "\n", "table = db.import_file(\"people\", csv_data)\n", "table()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## fin -" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [] } ], "metadata": { "kernelspec": { "display_name": "python3", "language": "python", "name": "python3" } }, "nbformat": 4, "nbformat_minor": 4 }