+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/Pipfile b/Pipfile
new file mode 100644
index 0000000..92b4967
--- /dev/null
+++ b/Pipfile
@@ -0,0 +1,22 @@
+[[source]]
+name = "pypi"
+url = "https://pypi.org/simple"
+verify_ssl = true
+
+[dev-packages]
+
+[packages]
+sqlalchemy = "*"
+jupyterlab = "*"
+ipykernel = "*"
+pandas = "*"
+ipywidgets = "*"
+pymssql = "*"
+cython = "*"
+jupyterlab-git = "*"
+six = ">=1.9.0"
+psycopg2 = "*"
+sqlcell = "*"
+
+[requires]
+python_version = "3.7"
diff --git a/Pipfile.lock b/Pipfile.lock
new file mode 100644
index 0000000..6ceffda
--- /dev/null
+++ b/Pipfile.lock
@@ -0,0 +1,576 @@
+{
+ "_meta": {
+ "hash": {
+ "sha256": "f98ed980a3e02b953c863beb3a867b8632348abe6942c58121a466678809b77f"
+ },
+ "pipfile-spec": 6,
+ "requires": {
+ "python_version": "3.7"
+ },
+ "sources": [
+ {
+ "name": "pypi",
+ "url": "https://pypi.org/simple",
+ "verify_ssl": true
+ }
+ ]
+ },
+ "default": {
+ "attrs": {
+ "hashes": [
+ "sha256:08a96c641c3a74e44eb59afb61a24f2cb9f4d7188748e76ba4bb5edfa3cb7d1c",
+ "sha256:f7b7ce16570fe9965acd6d30101a28f62fb4a7f9e926b3bbc9b61f8b04247e72"
+ ],
+ "version": "==19.3.0"
+ },
+ "backcall": {
+ "hashes": [
+ "sha256:38ecd85be2c1e78f77fd91700c76e14667dc21e2713b63876c0eb901196e01e4",
+ "sha256:bbbf4b1e5cd2bdb08f915895b51081c041bac22394fdfcfdfbe9f14b77c08bf2"
+ ],
+ "version": "==0.1.0"
+ },
+ "bleach": {
+ "hashes": [
+ "sha256:cc8da25076a1fe56c3ac63671e2194458e0c4d9c7becfd52ca251650d517903c",
+ "sha256:e78e426105ac07026ba098f04de8abe9b6e3e98b5befbf89b51a5ef0a4292b03"
+ ],
+ "index": "pypi",
+ "version": "==3.1.4"
+ },
+ "cython": {
+ "hashes": [
+ "sha256:0ce8f6c789c907472c9084a44b625eba76a85d0189513de1497ab102a9d39ef8",
+ "sha256:0d67964b747ac09758ba31fe25da2f66f575437df5f121ff481889a7a4485f56",
+ "sha256:1630823619a87a814e5c1fa9f96544272ce4f94a037a34093fbec74989342328",
+ "sha256:1a4c634bb049c8482b7a4f3121330de1f1c1f66eac3570e1e885b0c392b6a451",
+ "sha256:1ec91cc09e9f9a2c3173606232adccc68f3d14be1a15a8c5dc6ab97b47b31528",
+ "sha256:237a8fdd8333f7248718875d930d1e963ffa519fefeb0756d01d91cbfadab0bc",
+ "sha256:28a308cbfdf9b7bb44def918ad4a26b2d25a0095fa2f123addda33a32f308d00",
+ "sha256:2fe3dde34fa125abf29996580d0182c18b8a240d7fa46d10984cc28d27808731",
+ "sha256:30bda294346afa78c49a343e26f3ab2ad701e09f6a6373f579593f0cfcb1235a",
+ "sha256:33d27ea23e12bf0d420e40c20308c03ef192d312e187c1f72f385edd9bd6d570",
+ "sha256:34d24d9370a6089cdd5afe56aa3c4af456e6400f8b4abb030491710ee765bafc",
+ "sha256:4e4877c2b96fae90f26ee528a87b9347872472b71c6913715ca15c8fe86a68c9",
+ "sha256:50d6f1f26702e5f2a19890c7bc3de00f9b8a0ec131b52edccd56a60d02519649",
+ "sha256:55d081162191b7c11c7bfcb7c68e913827dfd5de6ecdbab1b99dab190586c1e8",
+ "sha256:59d339c7f99920ff7e1d9d162ea309b35775172e4bab9553f1b968cd43b21d6d",
+ "sha256:6cf4d10df9edc040c955fca708bbd65234920e44c30fccd057ecf3128efb31ad",
+ "sha256:6ec362539e2a6cf2329cd9820dec64868d8f0babe0d8dc5deff6c87a84d13f68",
+ "sha256:7edc61a17c14b6e54d5317b0300d2da23d94a719c466f93cafa3b666b058c43b",
+ "sha256:8e37fc4db3f2c4e7e1ed98fe4fe313f1b7202df985de4ee1451d2e331332afae",
+ "sha256:b8c996bde5852545507bff45af44328fa48a7b22b5bec2f43083f0b8d1024fd9",
+ "sha256:bf9c16f3d46af82f89fdefc0d64b2fb02f899c20da64548a8ea336beefcf8d23",
+ "sha256:c1038aba898bed34ab1b5ddb0d3f9c9ae33b0649387ab9ffe6d0af677f66bfc1",
+ "sha256:d405649c1bfc42e20d86178257658a859a3217b6e6d950ee8cb76353fcea9c39",
+ "sha256:db6eeb20a3bd60e1cdcf6ce9a784bc82aec6ab891c800dc5d7824d5cfbfe77f2",
+ "sha256:e382f8cb40dca45c3b439359028a4b60e74e22d391dc2deb360c0b8239d6ddc0",
+ "sha256:f3f6c09e2c76f2537d61f907702dd921b04d1c3972f01d5530ef1f748f22bd89",
+ "sha256:f749287087f67957c020e1de26906e88b8b0c4ea588facb7349c115a63346f67",
+ "sha256:f86b96e014732c0d1ded2c1f51444c80176a98c21856d0da533db4e4aef54070"
+ ],
+ "index": "pypi",
+ "version": "==0.29.7"
+ },
+ "decorator": {
+ "hashes": [
+ "sha256:41fa54c2a0cc4ba648be4fd43cff00aedf5b9465c9bf18d64325bc225f08f760",
+ "sha256:e3a62f0520172440ca0dcc823749319382e377f37f140a0b99ef45fecb84bfe7"
+ ],
+ "version": "==4.4.2"
+ },
+ "defusedxml": {
+ "hashes": [
+ "sha256:6687150770438374ab581bb7a1b327a847dd9c5749e396102de3fad4e8a3ef93",
+ "sha256:f684034d135af4c6cbb949b8a4d2ed61634515257a67299e5f940fbaa34377f5"
+ ],
+ "version": "==0.6.0"
+ },
+ "entrypoints": {
+ "hashes": [
+ "sha256:589f874b313739ad35be6e0cd7efde2a4e9b6fea91edcc34e58ecbb8dbe56d19",
+ "sha256:c70dd71abe5a8c85e55e12c19bd91ccfeec11a6e99044204511f9ed547d48451"
+ ],
+ "version": "==0.3"
+ },
+ "importlib-metadata": {
+ "hashes": [
+ "sha256:2a688cbaa90e0cc587f1df48bdc97a6eadccdcd9c35fb3f976a09e3b5016d90f",
+ "sha256:34513a8a0c4962bc66d35b359558fd8a5e10cd472d37aec5f66858addef32c1e"
+ ],
+ "markers": "python_version < '3.8'",
+ "version": "==1.6.0"
+ },
+ "ipykernel": {
+ "hashes": [
+ "sha256:346189536b88859937b5f4848a6fd85d1ad0729f01724a411de5cae9b618819c",
+ "sha256:f0e962052718068ad3b1d8bcc703794660858f58803c3798628817f492a8769c"
+ ],
+ "index": "pypi",
+ "version": "==5.1.1"
+ },
+ "ipython": {
+ "hashes": [
+ "sha256:ca478e52ae1f88da0102360e57e528b92f3ae4316aabac80a2cd7f7ab2efb48a",
+ "sha256:eb8d075de37f678424527b5ef6ea23f7b80240ca031c2dd6de5879d687a65333"
+ ],
+ "markers": "python_version >= '3.3'",
+ "version": "==7.13.0"
+ },
+ "ipython-genutils": {
+ "hashes": [
+ "sha256:72dd37233799e619666c9f639a9da83c34013a73e8bbc79a7a6348d93c61fab8",
+ "sha256:eb2e116e75ecef9d4d228fdc66af54269afa26ab4463042e33785b887c628ba8"
+ ],
+ "version": "==0.2.0"
+ },
+ "ipywidgets": {
+ "hashes": [
+ "sha256:0f2b5cde9f272cb49d52f3f0889fdd1a7ae1e74f37b48dac35a83152780d2b7b",
+ "sha256:a3e224f430163f767047ab9a042fc55adbcab0c24bbe6cf9f306c4f89fdf0ba3"
+ ],
+ "index": "pypi",
+ "version": "==7.4.2"
+ },
+ "jedi": {
+ "hashes": [
+ "sha256:b4f4052551025c6b0b0b193b29a6ff7bdb74c52450631206c262aef9f7159ad2",
+ "sha256:d5c871cb9360b414f981e7072c52c33258d598305280fef91c6cae34739d65d5"
+ ],
+ "version": "==0.16.0"
+ },
+ "jinja2": {
+ "hashes": [
+ "sha256:93187ffbc7808079673ef52771baa950426fd664d3aad1d0fa3e95644360e250",
+ "sha256:b0eaf100007721b5c16c1fc1eecb87409464edc10469ddc9a22a27a99123be49"
+ ],
+ "version": "==2.11.1"
+ },
+ "jsonschema": {
+ "hashes": [
+ "sha256:4e5b3cf8216f577bee9ce139cbe72eca3ea4f292ec60928ff24758ce626cd163",
+ "sha256:c8a85b28d377cc7737e46e2d9f2b4f44ee3c0e1deac6bf46ddefc7187d30797a"
+ ],
+ "version": "==3.2.0"
+ },
+ "jupyter-client": {
+ "hashes": [
+ "sha256:5724827aedb1948ed6ed15131372bc304a8d3ad9ac67ac19da7c95120d6b17e0",
+ "sha256:81c1c712de383bf6bf3dab6b407392b0d84d814c7bd0ce2c7035ead8b2ffea97"
+ ],
+ "version": "==6.1.2"
+ },
+ "jupyter-core": {
+ "hashes": [
+ "sha256:394fd5dd787e7c8861741880bdf8a00ce39f95de5d18e579c74b882522219e7e",
+ "sha256:a4ee613c060fe5697d913416fc9d553599c05e4492d58fac1192c9a6844abb21"
+ ],
+ "version": "==4.6.3"
+ },
+ "jupyterlab": {
+ "hashes": [
+ "sha256:0e47743c117609052ea75d211a85b2cad72b208f5fef664fab0c11afa1f5f0f7",
+ "sha256:2ec845845d51221e39d0d753884a19342c953f39febf3148a68631bf57ecb774"
+ ],
+ "index": "pypi",
+ "version": "==0.35.6"
+ },
+ "jupyterlab-git": {
+ "hashes": [
+ "sha256:592b1b6418b1c46856accc82062a39c8d3cb498e4e6e9b75c894e003798aebff",
+ "sha256:d5c84d5d15383910e1e8c010259383fd68ff8020b81c4718c8bf13ca518ecbb7"
+ ],
+ "index": "pypi",
+ "version": "==0.5.0"
+ },
+ "jupyterlab-server": {
+ "hashes": [
+ "sha256:65eaf85b27a37380329fbdd8ebd095a0bd65fe9261d73ef6a1abee1dbaeaac1f",
+ "sha256:72d916a73957a880cdb885def6d8664a6d1b2760ef5dca5ad665aa1e8d1bb783"
+ ],
+ "version": "==0.2.0"
+ },
+ "markupsafe": {
+ "hashes": [
+ "sha256:00bc623926325b26bb9605ae9eae8a215691f33cae5df11ca5424f06f2d1f473",
+ "sha256:09027a7803a62ca78792ad89403b1b7a73a01c8cb65909cd876f7fcebd79b161",
+ "sha256:09c4b7f37d6c648cb13f9230d847adf22f8171b1ccc4d5682398e77f40309235",
+ "sha256:1027c282dad077d0bae18be6794e6b6b8c91d58ed8a8d89a89d59693b9131db5",
+ "sha256:13d3144e1e340870b25e7b10b98d779608c02016d5184cfb9927a9f10c689f42",
+ "sha256:24982cc2533820871eba85ba648cd53d8623687ff11cbb805be4ff7b4c971aff",
+ "sha256:29872e92839765e546828bb7754a68c418d927cd064fd4708fab9fe9c8bb116b",
+ "sha256:43a55c2930bbc139570ac2452adf3d70cdbb3cfe5912c71cdce1c2c6bbd9c5d1",
+ "sha256:46c99d2de99945ec5cb54f23c8cd5689f6d7177305ebff350a58ce5f8de1669e",
+ "sha256:500d4957e52ddc3351cabf489e79c91c17f6e0899158447047588650b5e69183",
+ "sha256:535f6fc4d397c1563d08b88e485c3496cf5784e927af890fb3c3aac7f933ec66",
+ "sha256:596510de112c685489095da617b5bcbbac7dd6384aeebeda4df6025d0256a81b",
+ "sha256:62fe6c95e3ec8a7fad637b7f3d372c15ec1caa01ab47926cfdf7a75b40e0eac1",
+ "sha256:6788b695d50a51edb699cb55e35487e430fa21f1ed838122d722e0ff0ac5ba15",
+ "sha256:6dd73240d2af64df90aa7c4e7481e23825ea70af4b4922f8ede5b9e35f78a3b1",
+ "sha256:717ba8fe3ae9cc0006d7c451f0bb265ee07739daf76355d06366154ee68d221e",
+ "sha256:79855e1c5b8da654cf486b830bd42c06e8780cea587384cf6545b7d9ac013a0b",
+ "sha256:7c1699dfe0cf8ff607dbdcc1e9b9af1755371f92a68f706051cc8c37d447c905",
+ "sha256:88e5fcfb52ee7b911e8bb6d6aa2fd21fbecc674eadd44118a9cc3863f938e735",
+ "sha256:8defac2f2ccd6805ebf65f5eeb132adcf2ab57aa11fdf4c0dd5169a004710e7d",
+ "sha256:98c7086708b163d425c67c7a91bad6e466bb99d797aa64f965e9d25c12111a5e",
+ "sha256:9add70b36c5666a2ed02b43b335fe19002ee5235efd4b8a89bfcf9005bebac0d",
+ "sha256:9bf40443012702a1d2070043cb6291650a0841ece432556f784f004937f0f32c",
+ "sha256:ade5e387d2ad0d7ebf59146cc00c8044acbd863725f887353a10df825fc8ae21",
+ "sha256:b00c1de48212e4cc9603895652c5c410df699856a2853135b3967591e4beebc2",
+ "sha256:b1282f8c00509d99fef04d8ba936b156d419be841854fe901d8ae224c59f0be5",
+ "sha256:b2051432115498d3562c084a49bba65d97cf251f5a331c64a12ee7e04dacc51b",
+ "sha256:ba59edeaa2fc6114428f1637ffff42da1e311e29382d81b339c1817d37ec93c6",
+ "sha256:c8716a48d94b06bb3b2524c2b77e055fb313aeb4ea620c8dd03a105574ba704f",
+ "sha256:cd5df75523866410809ca100dc9681e301e3c27567cf498077e8551b6d20e42f",
+ "sha256:cdb132fc825c38e1aeec2c8aa9338310d29d337bebbd7baa06889d09a60a1fa2",
+ "sha256:e249096428b3ae81b08327a63a485ad0878de3fb939049038579ac0ef61e17e7",
+ "sha256:e8313f01ba26fbbe36c7be1966a7b7424942f670f38e666995b88d012765b9be"
+ ],
+ "version": "==1.1.1"
+ },
+ "mistune": {
+ "hashes": [
+ "sha256:59a3429db53c50b5c6bcc8a07f8848cb00d7dc8bdb431a4ab41920d201d4756e",
+ "sha256:88a1051873018da288eee8538d476dffe1262495144b33ecb586c4ab266bb8d4"
+ ],
+ "version": "==0.8.4"
+ },
+ "nbconvert": {
+ "hashes": [
+ "sha256:21fb48e700b43e82ba0e3142421a659d7739b65568cc832a13976a77be16b523",
+ "sha256:f0d6ec03875f96df45aa13e21fd9b8450c42d7e1830418cccc008c0df725fcee"
+ ],
+ "version": "==5.6.1"
+ },
+ "nbformat": {
+ "hashes": [
+ "sha256:562de41fc7f4f481b79ab5d683279bf3a168858268d4387b489b7b02be0b324a",
+ "sha256:f4bbbd8089bd346488f00af4ce2efb7f8310a74b2058040d075895429924678c"
+ ],
+ "version": "==5.0.4"
+ },
+ "notebook": {
+ "hashes": [
+ "sha256:3edc616c684214292994a3af05eaea4cc043f6b4247d830f3a2f209fa7639a80",
+ "sha256:47a9092975c9e7965ada00b9a20f0cf637d001db60d241d479f53c0be117ad48"
+ ],
+ "version": "==6.0.3"
+ },
+ "numpy": {
+ "hashes": [
+ "sha256:1598a6de323508cfeed6b7cd6c4efb43324f4692e20d1f76e1feec7f59013448",
+ "sha256:1b0ece94018ae21163d1f651b527156e1f03943b986188dd81bc7e066eae9d1c",
+ "sha256:2e40be731ad618cb4974d5ba60d373cdf4f1b8dcbf1dcf4d9dff5e212baf69c5",
+ "sha256:4ba59db1fcc27ea31368af524dcf874d9277f21fd2e1f7f1e2e0c75ee61419ed",
+ "sha256:59ca9c6592da581a03d42cc4e270732552243dc45e87248aa8d636d53812f6a5",
+ "sha256:5e0feb76849ca3e83dd396254e47c7dba65b3fa9ed3df67c2556293ae3e16de3",
+ "sha256:6d205249a0293e62bbb3898c4c2e1ff8a22f98375a34775a259a0523111a8f6c",
+ "sha256:6fcc5a3990e269f86d388f165a089259893851437b904f422d301cdce4ff25c8",
+ "sha256:82847f2765835c8e5308f136bc34018d09b49037ec23ecc42b246424c767056b",
+ "sha256:87902e5c03355335fc5992a74ba0247a70d937f326d852fc613b7f53516c0963",
+ "sha256:9ab21d1cb156a620d3999dd92f7d1c86824c622873841d6b080ca5495fa10fef",
+ "sha256:a1baa1dc8ecd88fb2d2a651671a84b9938461e8a8eed13e2f0a812a94084d1fa",
+ "sha256:a244f7af80dacf21054386539699ce29bcc64796ed9850c99a34b41305630286",
+ "sha256:a35af656a7ba1d3decdd4fae5322b87277de8ac98b7d9da657d9e212ece76a61",
+ "sha256:b1fe1a6f3a6f355f6c29789b5927f8bd4f134a4bd9a781099a7c4f66af8850f5",
+ "sha256:b5ad0adb51b2dee7d0ee75a69e9871e2ddfb061c73ea8bc439376298141f77f5",
+ "sha256:ba3c7a2814ec8a176bb71f91478293d633c08582119e713a0c5351c0f77698da",
+ "sha256:cd77d58fb2acf57c1d1ee2835567cd70e6f1835e32090538f17f8a3a99e5e34b",
+ "sha256:cdb3a70285e8220875e4d2bc394e49b4988bdb1298ffa4e0bd81b2f613be397c",
+ "sha256:deb529c40c3f1e38d53d5ae6cd077c21f1d49e13afc7936f7f868455e16b64a0",
+ "sha256:e7894793e6e8540dbeac77c87b489e331947813511108ae097f1715c018b8f3d"
+ ],
+ "version": "==1.18.2"
+ },
+ "pandas": {
+ "hashes": [
+ "sha256:071e42b89b57baa17031af8c6b6bbd2e9a5c68c595bc6bf9adabd7a9ed125d3b",
+ "sha256:17450e25ae69e2e6b303817bdf26b2cd57f69595d8550a77c308be0cd0fd58fa",
+ "sha256:17916d818592c9ec891cbef2e90f98cc85e0f1e89ed0924c9b5220dc3209c846",
+ "sha256:2538f099ab0e9f9c9d09bbcd94b47fd889bad06dc7ae96b1ed583f1dc1a7a822",
+ "sha256:366f30710172cb45a6b4f43b66c220653b1ea50303fbbd94e50571637ffb9167",
+ "sha256:42e5ad741a0d09232efbc7fc648226ed93306551772fc8aecc6dce9f0e676794",
+ "sha256:4e718e7f395ba5bfe8b6f6aaf2ff1c65a09bb77a36af6394621434e7cc813204",
+ "sha256:4f919f409c433577a501e023943e582c57355d50a724c589e78bc1d551a535a2",
+ "sha256:4fe0d7e6438212e839fc5010c78b822664f1a824c0d263fd858f44131d9166e2",
+ "sha256:5149a6db3e74f23dc3f5a216c2c9ae2e12920aa2d4a5b77e44e5b804a5f93248",
+ "sha256:627594338d6dd995cfc0bacd8e654cd9e1252d2a7c959449228df6740d737eb8",
+ "sha256:83c702615052f2a0a7fb1dd289726e29ec87a27272d775cb77affe749cca28f8",
+ "sha256:8c872f7fdf3018b7891e1e3e86c55b190e6c5cee70cab771e8f246c855001296",
+ "sha256:90f116086063934afd51e61a802a943826d2aac572b2f7d55caaac51c13db5b5",
+ "sha256:a3352bacac12e1fc646213b998bce586f965c9d431773d9e91db27c7c48a1f7d",
+ "sha256:bcdd06007cca02d51350f96debe51331dec429ac8f93930a43eb8fb5639e3eb5",
+ "sha256:c1bd07ebc15285535f61ddd8c0c75d0d6293e80e1ee6d9a8d73f3f36954342d0",
+ "sha256:c9a4b7c55115eb278c19aa14b34fcf5920c8fe7797a09b7b053ddd6195ea89b3",
+ "sha256:cc8fc0c7a8d5951dc738f1c1447f71c43734244453616f32b8aa0ef6013a5dfb",
+ "sha256:d7b460bc316064540ce0c41c1438c416a40746fd8a4fb2999668bf18f3c4acf1"
+ ],
+ "index": "pypi",
+ "version": "==0.24.2"
+ },
+ "pandocfilters": {
+ "hashes": [
+ "sha256:b3dd70e169bb5449e6bc6ff96aea89c5eea8c5f6ab5e207fc2f521a2cf4a0da9"
+ ],
+ "version": "==1.4.2"
+ },
+ "parso": {
+ "hashes": [
+ "sha256:0c5659e0c6eba20636f99a04f469798dca8da279645ce5c387315b2c23912157",
+ "sha256:8515fc12cfca6ee3aa59138741fc5624d62340c97e401c74875769948d4f2995"
+ ],
+ "version": "==0.6.2"
+ },
+ "pexpect": {
+ "hashes": [
+ "sha256:0b48a55dcb3c05f3329815901ea4fc1537514d6ba867a152b581d69ae3710937",
+ "sha256:fc65a43959d153d0114afe13997d439c22823a27cefceb5ff35c2178c6784c0c"
+ ],
+ "markers": "sys_platform != 'win32'",
+ "version": "==4.8.0"
+ },
+ "pickleshare": {
+ "hashes": [
+ "sha256:87683d47965c1da65cdacaf31c8441d12b8044cdec9aca500cd78fc2c683afca",
+ "sha256:9649af414d74d4df115d5d718f82acb59c9d418196b7b4290ed47a12ce62df56"
+ ],
+ "version": "==0.7.5"
+ },
+ "prometheus-client": {
+ "hashes": [
+ "sha256:71cd24a2b3eb335cb800c7159f423df1bd4dcd5171b234be15e3f31ec9f622da"
+ ],
+ "version": "==0.7.1"
+ },
+ "prompt-toolkit": {
+ "hashes": [
+ "sha256:563d1a4140b63ff9dd587bda9557cffb2fe73650205ab6f4383092fb882e7dc8",
+ "sha256:df7e9e63aea609b1da3a65641ceaf5bc7d05e0a04de5bd45d05dbeffbabf9e04"
+ ],
+ "version": "==3.0.5"
+ },
+ "psutil": {
+ "hashes": [
+ "sha256:1413f4158eb50e110777c4f15d7c759521703bd6beb58926f1d562da40180058",
+ "sha256:298af2f14b635c3c7118fd9183843f4e73e681bb6f01e12284d4d70d48a60953",
+ "sha256:60b86f327c198561f101a92be1995f9ae0399736b6eced8f24af41ec64fb88d4",
+ "sha256:685ec16ca14d079455892f25bd124df26ff9137664af445563c1bd36629b5e0e",
+ "sha256:73f35ab66c6c7a9ce82ba44b1e9b1050be2a80cd4dcc3352cc108656b115c74f",
+ "sha256:75e22717d4dbc7ca529ec5063000b2b294fc9a367f9c9ede1f65846c7955fd38",
+ "sha256:a02f4ac50d4a23253b68233b07e7cdb567bd025b982d5cf0ee78296990c22d9e",
+ "sha256:d008ddc00c6906ec80040d26dc2d3e3962109e40ad07fd8a12d0284ce5e0e4f8",
+ "sha256:d84029b190c8a66a946e28b4d3934d2ca1528ec94764b180f7d6ea57b0e75e26",
+ "sha256:e2d0c5b07c6fe5a87fa27b7855017edb0d52ee73b71e6ee368fae268605cc3f5",
+ "sha256:f344ca230dd8e8d5eee16827596f1c22ec0876127c28e800d7ae20ed44c4b310"
+ ],
+ "version": "==5.7.0"
+ },
+ "psycopg2": {
+ "hashes": [
+ "sha256:00cfecb3f3db6eb76dcc763e71777da56d12b6d61db6a2c6ccbbb0bff5421f8f",
+ "sha256:076501fc24ae13b2609ba2303d88d4db79072562f0b8cc87ec1667dedff99dc1",
+ "sha256:4e2b34e4c0ddfeddf770d7df93e269700b080a4d2ec514fec668d71895f56782",
+ "sha256:5cacf21b6f813c239f100ef78a4132056f93a5940219ec25d2ef833cbeb05588",
+ "sha256:61f58e9ecb9e4dc7e30be56b562f8fc10ae3addcfcef51b588eed10a5a66100d",
+ "sha256:8954ff6e47247bdd134db602fcadfc21662835bd92ce0760f3842eacfeb6e0f3",
+ "sha256:b6e8c854cdc623028e558a409b06ea2f16d13438335941c7765d0a42b5bedd33",
+ "sha256:baca21c0f7344576346e260454d0007313ccca8c170684707a63946b27a56c8f",
+ "sha256:bb1735378770fb95dbe392d29e71405d45c8bdcfa064f916504833a92ab03c55",
+ "sha256:de3d3c46c1ee18f996db42d1eb44cf1565cc9e38fb1dbd9b773ff6b3fa8035d7",
+ "sha256:dee885602bb200bdcb1d30f6da6c7bb207360bc786d0a364fe1540dd14af0bab"
+ ],
+ "index": "pypi",
+ "version": "==2.8.2"
+ },
+ "ptyprocess": {
+ "hashes": [
+ "sha256:923f299cc5ad920c68f2bc0bc98b75b9f838b93b599941a6b63ddbc2476394c0",
+ "sha256:d7cc528d76e76342423ca640335bd3633420dc1366f258cb31d05e865ef5ca1f"
+ ],
+ "markers": "os_name != 'nt'",
+ "version": "==0.6.0"
+ },
+ "pygments": {
+ "hashes": [
+ "sha256:647344a061c249a3b74e230c739f434d7ea4d8b1d5f3721bc0f3558049b38f44",
+ "sha256:ff7a40b4860b727ab48fad6360eb351cc1b33cbf9b15a0f689ca5353e9463324"
+ ],
+ "version": "==2.6.1"
+ },
+ "pymssql": {
+ "hashes": [
+ "sha256:0479294789d7f230016d87e115a23e51617909611de7b1ced1263c1e365f6c91",
+ "sha256:056dacf9120ae3a01955c52ce425b7ee7da499b27bd80f3bfcfb4013ae7adff8",
+ "sha256:084deb1462fb52c0d802b006c673f3a02ce562b7b3a2f65f6a6bd2162a0850a2",
+ "sha256:254e3552b6a52b40cb437f4b75683e003398f8ce12da0e40908a07703882802d",
+ "sha256:3201eb1b1263ad55b555d727ed8bed0b12b7b9de3ce5e529206e36d4be1a7afb",
+ "sha256:46dcd9c2f371d7d5744c22d6a3dc426b10debd12e0cab5b69c92f29b8eb90e21",
+ "sha256:47a567ed94618a89c83b8ce24c886ff65f0c063ef02ed7240b98ab953ca95b5d",
+ "sha256:4fd384371ddc260c12d79dc8bcc239c722d571d1701cba1ec7788617db603a06",
+ "sha256:6da06dd6a37b3472b034a82fd156c63c6cb288b996a6ef252e874e343b37a254",
+ "sha256:7544b8b1b38d101f0b7bd794d2da53dd19fee38e3dfe7312cd9bc87252347260",
+ "sha256:7a3869e6f0ea3a49f4faf8489638341578b4e638e86dc4067a69e8fa5d0469a2",
+ "sha256:bc2f3f93711cb6262cee83f63e9e38021a79f0d1b4c0deec7f68de97b49a8938",
+ "sha256:c32df8c15dda0a23551348a51b7b41f0bfe25be8925c7afebcef977f89cceb62",
+ "sha256:c830088a8c1571e4606edd461d5f2291c8dacea2cd615466fb4764773d968949",
+ "sha256:cfbce903ed29d2979168ac5d253e8241cb73901ec79c1e6692eb912ebc990be9",
+ "sha256:d30001a9e00dbbb7ca04922eeb4df89e946bb083bbf7114667b8cbb7e08d9a0c",
+ "sha256:d3bf234eeb24ba6048ab1bb069395e3acd212376e91d8d7fdfdcfef60178cef4",
+ "sha256:e077af5c80e768976b47789644bd7ea211131b5d8ed174e44995b3ef34009aad",
+ "sha256:e730e1fbbde44227ad34c024e71c1764503cf97c3812c520766867d1ee916e68",
+ "sha256:ebecdd63e9a6c871dcd25c877895f9ed2ba96549743cb4d45d201fc478f1bf28",
+ "sha256:f58810f6917916af539948b3ed53881aca4aa7db80b662dcabcbf93a3cdea938",
+ "sha256:fb340fbf5525ea51c5d4b704340a6fe8ec97e5380b5cd17107ea48179d1c1605",
+ "sha256:fe7e4bf3c79b3f0425588bd99188e431727f4d6b19249dcb81b94e10d261d86f"
+ ],
+ "index": "pypi",
+ "version": "==2.1.4"
+ },
+ "pyrsistent": {
+ "hashes": [
+ "sha256:28669905fe725965daa16184933676547c5bb40a5153055a8dee2a4bd7933ad3"
+ ],
+ "version": "==0.16.0"
+ },
+ "python-dateutil": {
+ "hashes": [
+ "sha256:73ebfe9dbf22e832286dafa60473e4cd239f8592f699aa5adaf10050e6e1823c",
+ "sha256:75bb3f31ea686f1197762692a9ee6a7550b59fc6ca3a1f4b5d7e32fb98e2da2a"
+ ],
+ "version": "==2.8.1"
+ },
+ "pytz": {
+ "hashes": [
+ "sha256:1c557d7d0e871de1f5ccd5833f60fb2550652da6be2693c1e02300743d21500d",
+ "sha256:b02c06db6cf09c12dd25137e563b31700d3b80fcc4ad23abb7a315f2789819be"
+ ],
+ "version": "==2019.3"
+ },
+ "pyzmq": {
+ "hashes": [
+ "sha256:0bbc1728fe4314b4ca46249c33873a390559edac7c217ec7001b5e0c34a8fb7f",
+ "sha256:1e076ad5bd3638a18c376544d32e0af986ca10d43d4ce5a5d889a8649f0d0a3d",
+ "sha256:242d949eb6b10197cda1d1cec377deab1d5324983d77e0d0bf9dc5eb6d71a6b4",
+ "sha256:26f4ae420977d2a8792d7c2d7bda43128b037b5eeb21c81951a94054ad8b8843",
+ "sha256:32234c21c5e0a767c754181c8112092b3ddd2e2a36c3f76fc231ced817aeee47",
+ "sha256:3f12ce1e9cc9c31497bd82b207e8e86ccda9eebd8c9f95053aae46d15ccd2196",
+ "sha256:4557d5e036e6d85715b4b9fdb482081398da1d43dc580d03db642b91605b409f",
+ "sha256:4f562dab21c03c7aa061f63b147a595dbe1006bf4f03213272fc9f7d5baec791",
+ "sha256:5e071b834051e9ecb224915398f474bfad802c2fff883f118ff5363ca4ae3edf",
+ "sha256:5e1f65e576ab07aed83f444e201d86deb01cd27dcf3f37c727bc8729246a60a8",
+ "sha256:5f10a31f288bf055be76c57710807a8f0efdb2b82be6c2a2b8f9a61f33a40cea",
+ "sha256:6aaaf90b420dc40d9a0e1996b82c6a0ff91d9680bebe2135e67c9e6d197c0a53",
+ "sha256:75238d3c16cab96947705d5709187a49ebb844f54354cdf0814d195dd4c045de",
+ "sha256:7f7e7b24b1d392bb5947ba91c981e7d1a43293113642e0d8870706c8e70cdc71",
+ "sha256:84b91153102c4bcf5d0f57d1a66a0f03c31e9e6525a5f656f52fc615a675c748",
+ "sha256:944f6bb5c63140d76494467444fd92bebd8674236837480a3c75b01fe17df1ab",
+ "sha256:a1f957c20c9f51d43903881399b078cddcf710d34a2950e88bce4e494dcaa4d1",
+ "sha256:a49fd42a29c1cc1aa9f461c5f2f5e0303adba7c945138b35ee7f4ab675b9f754",
+ "sha256:a99ae601b4f6917985e9bb071549e30b6f93c72f5060853e197bdc4b7d357e5f",
+ "sha256:ad48865a29efa8a0cecf266432ea7bc34e319954e55cf104be0319c177e6c8f5",
+ "sha256:b08e425cf93b4e018ab21dc8fdbc25d7d0502a23cc4fea2380010cf8cf11e462",
+ "sha256:bb10361293d96aa92be6261fa4d15476bca56203b3a11c62c61bd14df0ef89ba",
+ "sha256:bd1a769d65257a7a12e2613070ca8155ee348aa9183f2aadf1c8b8552a5510f5",
+ "sha256:cb3b7156ef6b1a119e68fbe3a54e0a0c40ecacc6b7838d57dd708c90b62a06dc",
+ "sha256:e8e4efb52ec2df8d046395ca4c84ae0056cf507b2f713ec803c65a8102d010de",
+ "sha256:f37c29da2a5b0c5e31e6f8aab885625ea76c807082f70b2d334d3fd573c3100a",
+ "sha256:f4d558bc5668d2345773a9ff8c39e2462dafcb1f6772a2e582fbced389ce527f",
+ "sha256:f5b6d015587a1d6f582ba03b226a9ddb1dfb09878b3be04ef48b01b7d4eb6b2a"
+ ],
+ "version": "==19.0.0"
+ },
+ "send2trash": {
+ "hashes": [
+ "sha256:60001cc07d707fe247c94f74ca6ac0d3255aabcb930529690897ca2a39db28b2",
+ "sha256:f1691922577b6fa12821234aeb57599d887c4900b9ca537948d2dac34aea888b"
+ ],
+ "version": "==1.5.0"
+ },
+ "six": {
+ "hashes": [
+ "sha256:3350809f0555b11f552448330d0b52d5f24c91a322ea4a15ef22629740f3761c",
+ "sha256:d16a0141ec1a18405cd4ce8b4613101da75da0e9a7aec5bdd4fa804d0e0eba73"
+ ],
+ "index": "pypi",
+ "version": "==1.12.0"
+ },
+ "sqlalchemy": {
+ "hashes": [
+ "sha256:91c54ca8345008fceaec987e10924bf07dcab36c442925357e5a467b36a38319"
+ ],
+ "index": "pypi",
+ "version": "==1.3.3"
+ },
+ "sqlcell": {
+ "hashes": [
+ "sha256:0f67c228cb594e7ef2848305fff4ad69b431356a3405bbf73fb2c89fb63af23c"
+ ],
+ "index": "pypi",
+ "version": "==0.2.0.11"
+ },
+ "terminado": {
+ "hashes": [
+ "sha256:4804a774f802306a7d9af7322193c5390f1da0abb429e082a10ef1d46e6fb2c2",
+ "sha256:a43dcb3e353bc680dd0783b1d9c3fc28d529f190bc54ba9a229f72fe6e7a54d7"
+ ],
+ "version": "==0.8.3"
+ },
+ "testpath": {
+ "hashes": [
+ "sha256:60e0a3261c149755f4399a1fff7d37523179a70fdc3abdf78de9fc2604aeec7e",
+ "sha256:bfcf9411ef4bf3db7579063e0546938b1edda3d69f4e1fb8756991f5951f85d4"
+ ],
+ "version": "==0.4.4"
+ },
+ "tornado": {
+ "hashes": [
+ "sha256:0fe2d45ba43b00a41cd73f8be321a44936dc1aba233dee979f17a042b83eb6dc",
+ "sha256:22aed82c2ea340c3771e3babc5ef220272f6fd06b5108a53b4976d0d722bcd52",
+ "sha256:2c027eb2a393d964b22b5c154d1a23a5f8727db6fda837118a776b29e2b8ebc6",
+ "sha256:5217e601700f24e966ddab689f90b7ea4bd91ff3357c3600fa1045e26d68e55d",
+ "sha256:5618f72e947533832cbc3dec54e1dffc1747a5cb17d1fd91577ed14fa0dc081b",
+ "sha256:5f6a07e62e799be5d2330e68d808c8ac41d4a259b9cea61da4101b83cb5dc673",
+ "sha256:c58d56003daf1b616336781b26d184023ea4af13ae143d9dda65e31e534940b9",
+ "sha256:c952975c8ba74f546ae6de2e226ab3cc3cc11ae47baf607459a6728585bb542a",
+ "sha256:c98232a3ac391f5faea6821b53db8db461157baa788f5d6222a193e9456e1740"
+ ],
+ "version": "==6.0.4"
+ },
+ "traitlets": {
+ "hashes": [
+ "sha256:70b4c6a1d9019d7b4f6846832288f86998aa3b9207c6821f3578a6a6a467fe44",
+ "sha256:d023ee369ddd2763310e4c3eae1ff649689440d4ae59d7485eb4cfbbe3e359f7"
+ ],
+ "version": "==4.3.3"
+ },
+ "wcwidth": {
+ "hashes": [
+ "sha256:cafe2186b3c009a04067022ce1dcd79cb38d8d65ee4f4791b8888d6599d1bbe1",
+ "sha256:ee73862862a156bf77ff92b09034fc4825dd3af9cf81bc5b360668d425f3c5f1"
+ ],
+ "version": "==0.1.9"
+ },
+ "webencodings": {
+ "hashes": [
+ "sha256:a0af1213f3c2226497a97e2b3aa01a7e4bee4f403f95be16fc9acd2947514a78",
+ "sha256:b36a1c245f2d304965eb4e0a82848379241dc04b865afcc4aab16748587e1923"
+ ],
+ "version": "==0.5.1"
+ },
+ "widgetsnbextension": {
+ "hashes": [
+ "sha256:14b2c65f9940c9a7d3b70adbe713dbd38b5ec69724eebaba034d1036cf3d4740",
+ "sha256:fa618be8435447a017fd1bf2c7ae922d0428056cfc7449f7a8641edf76b48265"
+ ],
+ "version": "==3.4.2"
+ },
+ "zipp": {
+ "hashes": [
+ "sha256:aa36550ff0c0b7ef7fa639055d797116ee891440eac1a56f378e2d3179e0320b",
+ "sha256:c599e4d75c98f6798c509911d08a22e6c021d074469042177c8c86fb92eefd96"
+ ],
+ "version": "==3.1.0"
+ }
+ },
+ "develop": {}
+}
diff --git a/README.md b/README.md
index 27345d4..31f0567 100644
--- a/README.md
+++ b/README.md
@@ -1,99 +1,10 @@
-*** UPDATE *** - 11/017/2016
-
-All SQL queries are now executed on separate threads so you can run multiple queries and Python code concurrently.
-
-*** UPDATE *** - 11/08/2016
-
-Introducing inline editing
-
-
-
-*** UPDATE ***
-
-Introducing buttons!
-
-
-
-Buttons include • Running Explain Analyze on your query • executing query • executing query and returning SQLAlchemy results in a variable • saving to a TSV • stopping query • swithcing between user-defined engines
-
# SQLCell
-Uses Jupyter magic function to run SQL queries in Jupyter Notebook. Blog post here.
-
-In less than one hundred lines of Python, you can get rid of your favorite SQL interface and
-use a Jupyter notebook to run queries with as little as
-
- %%sql
- your query
-
-Just clone the repo and `cp` the app to Jupyter's startup directory:
-
- $ cd .ipython/profile_default/startup # or wherever your startup directory is
- $ git clone https://github.com/tmthyjames/SQLCell.git
- $ cp SQLCell/app.py app.py # place app.py in the startup folder so it will get executed
-
-Then in the Jupyter notebook, define your connection variables. You can also input these directly
-in the engine_config.py (recommended) script so you don't have to add them everytime. Just find the line that says
-`# default connection string info here` and enter the connection details there. If you don't add them to engine_config.py,
-then you have to define them everytime and invoke them with `DB=your_database` the first time you use `%%sql`.
-
- driver = 'postgresql'
- username = 'username'
- password = 'password'
- host = 'host'
- port = '5432'
-
-Now you are ready to ditch pgAdmin or whatever SQL interface you use. Continue reading to see all the available
-options, like writing results to a CSV, using SQLAlchemy named parameters and more.
-
-After adding your connection details to sqlcell.py, run your first query with the DB argument:
-
-
-
-For the rest of the session, you won't have to use the DB argument unless you want to change
-databases.
-
-
-
-To switch databases, just invoke the DB argument again with a different database:
-
-
-
-To write the data to a CSV, use the PATH argument:
-
-
-
-To use SQLAlchemy's named parameters (it's nice to just copy and paste if you use Jupyter for
-development/editing and don't want to delete all SQLALchemy paramters just to run a query), use
-the PARAMS argument.
-
-
-
-And my favorite. You can assign the dataframe to a variable like this useing the MAKE_GLOBAL argument:
-
-
-
-With all arguments specified:
-
-
-
-And for those few people who have Jupyter but not pandas, I thought about you. If you have pandas, a dataframe
-will be returned. If not, you'll get a simple HTML table to view the data, without any additional configuration.
-
-
-
-NEW FEATURES as of Aug 16, 2016:
-
-You can now use python variables without the `PARAMS` argument
-
-
+Bringing together the power of SQL, Python, and Javascript. Run raw, multi-threaded SQL in an IPython Notebook while concurrently executing python cells, and a ton of other features.
-You can also return the raw RowProxy from SQLAlchemy by setting the RAW argument to `True` and using the `MAKE_GLOBAL`
-argument.
+# *** New Version 2.0 ***
-
-
-And that's it.
+Find a tutorial here.
-Enjoy and contribute.
+## To Install:
-See more here.
+`pip install sqlcell`
diff --git a/README1.0.md b/README1.0.md
new file mode 100644
index 0000000..7c3ac3c
--- /dev/null
+++ b/README1.0.md
@@ -0,0 +1,358 @@
+# SQLCell
+Bringing together the power of SQL, Python, and Javascript. Run raw, multi-threaded SQL in an IPython Notebook while concurrently executing python cells, and a ton of other features.
+
+See the tutorials here:
+
+[Part 1: The Basics](https://tmthyjames.github.io/projects/SQLCell-Part-I/)
+
+[Part 2: Beyond The Basics](https://tmthyjames.github.io/tools/projects/SQLCell-Part-II/)
+
+
+
+
+## Contents
+ • [Most recent feature](#most-recent-feature)
+ • [Installation and Configuration](#installation-and-configuration)
+ • [Features](#features)
+ i. [Parameters](#parameters)
+ ii. [Flags](#flags)
+ iii. [Pass python variables to SQL](#pass-python-variables-to-sql)
+ iv. [`psql` metacommands](#psql-metacommands)
+ v. [Multi-threading](#multi-threading)
+ vi. [Buttons](#buttons)
+ vii. [Inline editing](#inline-editing)
+ viii. [Easy-to-read Query Plan Table](#easy-to-read-query-plan-table)
+ ix. [Easy-to-read Query Plan Graph](#easy-to-read-query-plan-graph)
+ x. [Alter column name and type via the UI](#alter-column-name-and-type-inline)
+ xi. [Notifications](#notifications)
+ xii. [`pg_dump` support](#pg_dump-support)
+ xiii. [Switch Engines](#switch-engines) (To be documented...)
+ • In development
+ i. Built-in PostGIS preview (inspired by [postgis-preview](https://github.com/NYCPlanning/postgis-preview)
+ ii. MSSQL support.
+ • To dos
+ i. Add UI elements to perform basic, common database tasks, such as adding columns, creating tables, etc.
+ ii. Need to confirm install process is smooth on non-Mac computers.
+ iii. Add support for MySQL commands.
+ iv. Add modifiers and constraints via the UI
+
+Open issues can be found here.
+
+
+## Most recent feature
+
+added 12/04/2016
+
+SQLCell now offers the option to view the Query Plan as a sankey graph built with D3.js
+
+
+
+
+## Installation and configuration
+
+Just clone the repo and `cp` the `sqlcell_app.py` file to Jupyter's startup directory (on my computer, the directory is `~/.ipython/profile_default/startup`, but may be different depending on your OS and version of IPython/Jupyter):
+
+ $ cd .ipython/profile_default/startup # or wherever your startup directory is
+ $ git clone https://github.com/tmthyjames/SQLCell.git
+ $ cp SQLCell/sqlcell_app.py sqlcell_app.py # place sqlcell_app.py in the startup folder so it will get executed
+
+Then in the engine_config.py file, define your connection variables. If you don't add them to engine_config.py,
+then you'll have to pass a connection string to the ENGINE parameter everytime you use `%%sql`, like so:
+
+ In [2]: %%sql ENGINE='postgresql://username:password@host:port/database'
+ SELECT * FROM table;
+
+To save the engines:
+
+ %%sql --declare_engines new
+ LOCAL=postgresql://username:password@localhost:5432/
+ DEV=postgresql://username:password@random.domain.com/
+
+See more about this option in the [Declare Engines](#declare-engines) section
+
+Now you are ready to ditch pgAdmin or whatever SQL interface you use. Continue reading to see all the available
+options, like writing results to a CSV, using SQLAlchemy named parameters and more.
+
+## Features
+
+### Parameters
+
+Available parameters:
+ • [`DB`](#db-parameter): Determines what database to query. On the first run, this parameter is required. After that, it will remember what database was chosen. To change databases, use this parameter again. Default is the last-specificed database.
+ • [`PATH`](#path-parameter): Writes results of the query to a CSV (can also be done through the UI). No default.
+ • [`MAKE_GLOBAL`](#make_global-parameter): Passes results of the query to the variable you pass to it. If this parameter is specified but the `RAW` parameter is not, then the results will be a Pandas DataFrame. If `RAW` is set to `True`, then the results will be the raw RowProxy returned from the database. No Default.
+ • [`RAW`](#raw-parameter): Determines whether the data will be of type DataFrame or RowProxy. Default: `False`.
+ • [`DISPLAY`](#display-parameter): Determines whether or not to render the results as a table. This is best used in conjunction with the `MAKE_GLOBAL` parameter because displaying a table in a busy workflow can be cumbersome and annoying sometimes.
+ • [`ENGINE`](#engine-parameter): Speicifies what host, database to connect to. Default is the connection that is specified in the engine_config.py file. If the engine_config.py file is not configured, then the `ENGINE` parameter is required.
+ • [`TRANSACTION_BLOCK`](#transaction_block-parameter)': Determines whether the query will be executed inside a transaction block or not. This is useful when creating a database, dropping a database, `VACUUM ANALYZE`ing a database, or any other query statements that cannot be run inside a transaction block. Default: True
+ • [`EDIT`](#edit-parameter): Enables inline editing. To use this, you must specify only one table in your query, and that table must have a primary key. Default: False.
+ • [`NOTIFY`](#notify-parameter): Disables notifications for finished queries. Default: True.
+
+
+
+Examples of how to use these are below.
+
+#### `DB` Parameter
+After adding your connection details to engines.py, run your first query with the DB argument:
+
+ In [3]: %%sql DB=bls
+ SELECT *
+ FROM la_unemployment
+ LIMIT 3
+
series_id
year
period
value
footnote_codes
1
LASST470000000000003
1976
M01
6.2
None
2
LASST470000000000003
1976
M02
6.1
None
3
LASST470000000000003
1976
M03
6.0
None
+
+
+For the rest of the session, you won't have to use the DB argument unless you want to change
+databases. And the last-used DB will be persisted even after you shut down Jupyter and start it back up next time.
+
+ In [4]: %%sql
+ SELECT *
+ FROM avg_price LIMIT 3
+
series_id
year
period
value
1
APU0000701111
1980
M01
0.203
2
APU0000701111
1980
M02
0.205
3
APU0000701111
1980
M03
0.211
+
+To switch databases, just invoke the DB argument again with a different database:
+
+ In [5]: %%sql DB=sports
+ SELECT *
+ FROM nba LIMIT 3
+
dateof
team
opp
pts
fg
fg_att
ft
ft_att
fg3
fg3_att
off_rebounds
def_rebounds
asst
blks
fouls
stls
turnovers
1
2015-10-27
DET
ATL
106
37
96
20
26
12
29
23
36
23
3
15
5
15
2
2015-10-27
ATL
DET
94
37
82
12
15
8
27
7
33
22
4
25
9
15
3
2015-10-27
CLE
CHI
95
38
94
10
17
9
29
11
39
26
7
21
5
11
+
+#### `PATH` Parameter
+
+To write the data to a CSV, use the PATH argument:
+
+ In [6]: %%sql PATH='///.csv'
+ SELECT *
+ FROM nba LIMIT 3
+
dateof
team
opp
pts
fg
fg_att
ft
ft_att
fg3
fg3_att
off_rebounds
def_rebounds
asst
blks
fouls
stls
turnovers
1
2015-10-27
DET
ATL
106
37
96
20
26
12
29
23
36
23
3
15
5
15
2
2015-10-27
ATL
DET
94
37
82
12
15
8
27
7
33
22
4
25
9
15
3
2015-10-27
CLE
CHI
95
38
94
10
17
9
29
11
39
26
7
21
5
11
+
+
+#### `MAKE_GLOBAL` parameter
+And my favorite. You can assign the dataframe to a variable like this useing the MAKE_GLOBAL argument:
+
+ In [9]: %%sql MAKE_GLOBAL=WHATEVER_NAME_YOU_WANT DB=bls
+ SELECT *
+ FROM la_unemployment
+ WHERE year = 1976
+ AND period = 'M01'
+ LIMIT 3
+
series_id
year
period
value
footnote_codes
1
LASST470000000000003
1976
M01
6.2
None
2
LASST470000000000004
1976
M01
111152.0
None
3
LASST470000000000005
1976
M01
1691780.0
None
+
+And call the variable:
+
+ In [10]: WHATEVER_NAME_YOU_WANT
+
series_id
year
period
value
footnote_codes
1
LASST470000000000003
1976
M01
6.2
None
2
LASST470000000000004
1976
M01
111152.0
None
3
LASST470000000000005
1976
M01
1691780.0
None
+
+#### `RAW` Parameter
+You can also return the raw RowProxy from SQLAlchemy by setting the RAW argument to `True` and using the `MAKE_GLOBAL`
+argument.
+
+ In [10]: %%sql MAKE_GLOBAL=data RAW=True
+ SELECT *
+ FROM la_unemployment
+ LIMIT 3
+
+ In [11]: data
+ [(u'LASST470000000000003', 1976, u'M01', 6.2, None),
+ (u'LASST470000000000003', 1976, u'M02', 6.1, None),
+ (u'LASST470000000000003', 1976, u'M03', 6.0, None)]
+
+#### `DISPLAY` Parameter
+Query the data without rendering the table (useful if the result set is prohibitively large and displaying the table breaks things) by setting the `DISPLAY` parameter to `False`. It makes sense to use this parameter in conjunction with the `MAKE_GLOBAL` parameter so the data is passed to the variable but the table isn't rendered:
+
+ In [10]: %%sql MAKE_GLOBAL=data DISPLAY=False
+ SELECT *
+ FROM la_unemployment
+
+#### `ENGINE` Parameter
+The `ENGINE` parameter accepts any connection string and creates a connection based on that.
+
+ In [10]: %%sql ENGINE='postgresql://username:password@host:port/DB'
+ SELECT *
+ FROM la_unemployment
+ LIMIT 3
+
+#### `TRANSACTION_BLOCK` Parameter
+Some SQL statements (`VACUUM`, `CREATE `, `DROP `, etc.) must be executed outside of a transaction block by setting the isolation level to 0 (see this)
+
+ In [10]: %%sql TRANSACTION_BLOCK=False
+ VACUUM ANALYZE
+
+#### `EDIT` Parameter
+Enables inline editing.
+
+ In [10]: %%sql EDIT=True
+ SELECT *
+ FROM la_unemployment
+ LIMIT 3
+
+Will display a table where the cells can be clicked on and edited.
+
+#### `NOTIFY` Parameter
+
+ In [11]: %%sql NOTIFY=False
+ SELECT * FROM la_unemployment LIMIT 1
+
+Will disable notifications for the remainder of your Jupyter session. To re-enable notifications, just set `NOTIFY=True`.
+
+### Flags
+ • [`declare_engines`](#declare-engines): Makes adding engines to the engines.py file easy.
+ • [`pg_dump`](#pg_dump-support): Run `--pg_dump` commands from your Jupyter Notebook.
+
+#### Declare Engines
+All engines should be in this format: `name=connection_string`. For example:
+
+ %%sql --declare_engines new
+ LOCAL=postgresql://username:password@localhost:5432/
+ DEV=postgresql://username:password@random.domain.com/
+
+Will create the ENGINES object with only LOCAL and DEV in it. "LOCAL" will be the text that goes on the button, like the following:
+
+
+
+To append new engines to an existing ENGINES object:
+
+ %%sql --declare_engines append
+ LOCAL_test=postgresql://username:password@localhost:5432/
+ DEV_test=postgresql://username:password@random.domain.com/
+
+
+
+Where I had the engines LOCAL, DEV, and PROD I now have LOCAL_test and DEV_test also.
+
+
+### `pg_dump` support
+
+ In[17]: %%sql --pg_dump
+ -t nba sports --schema-only
+
+Will output the following:
+
+ SET statement_timeout = 0;
+ SET lock_timeout = 0;
+ SET client_encoding = 'UTF8';
+ SET standard_conforming_strings = on;
+ SET check_function_bodies = false;
+ SET client_min_messages = warning;
+ SET row_security = off;
+ SET search_path = public, pg_catalog;
+ SET default_tablespace = '';
+ SET default_with_oids = false;
+ --
+ -- Name: nba; Type: TABLE; Schema: public; Owner: postgres
+ --
+ CREATE TABLE nba (
+ dateof date,
+ team character varying(5),
+ opp character varying(5),
+ pts bigint,
+ fg integer,
+ fg_att integer,
+ ft integer,
+ ft_att integer,
+ fg3 integer,
+ fg3_att integer,
+ off_rebounds integer,
+ def_rebounds integer,
+ asst integer,
+ blks integer,
+ fouls integer,
+ stls integer,
+ turnovers integer
+ );
+ ALTER TABLE nba OWNER TO postgres;
+
+
+### Pass Python variables to SQL
+
+To pass python variables to your queries, just do the following.
+
+ In[7]: # define your parameters in a python cell
+ name = '1976'
+ period = 'M01'
+ series_id = ('LASST470000000000005', 'LASST470000000000004', 'LASST470000000000003')
+
+Now in a `%%sql` cell:
+
+ In [8]: %%sql DB=bls
+ SELECT *
+ FROM la_unemployment
+ WHERE year = %(year)s
+ AND period = %(period)s AND series_id IN %(series_id)s
+ LIMIT 3
+
+You can also use a colon to indicate your variables:
+
+ In [8]: %%sql DB=bls
+ SELECT *
+ FROM la_unemployment
+ WHERE year = :year
+ AND period = :period AND series_id IN :series_id
+ LIMIT 3
+
+Both output the following table:
+
+
QUERY DEAD...')
- return True
-
-def new_queue():
- return Queue.Queue()
-
-def _SQL(path, cell, kernel_vars):
- """
- Create magic cell function to treat cell text as SQL
- to remove the need of third party SQL interfaces. The
- args are split on spaces so don't use spaces except to
- input a new argument.
- Args:
- PATH (str): path to write dataframe to in csv.
- MAKE_GLOBAL: make dataframe available globally.
- DB: name of database to connect to.
- RAW: when used with MAKE_GLOBAL, will return the
- raw RowProxy from sqlalchemy.
- Returns:
- DataFrame:
- """
- global driver, username, password, host, port, db, table, __EXPLAIN__, __GETDATA__, __SAVEDATA__, engine
- unique_id = str(uuid.uuid4())
-
- display(
- HTML(
- '''
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- '''
- )
- )
-
- if '__EXPLAIN__' in dir(__builtin__) and __builtin__.__EXPLAIN__:
- cell = 'EXPLAIN ANALYZE ' + cell
- __builtin__.__EXPLAIN__ = False
-
- elif '__GETDATA__' in dir(__builtin__) and __builtin__.__GETDATA__:
- if 'MAKE_GLOBAL' not in path:
- path = 'MAKE_GLOBAL=DATA RAW=True ' + path.strip()
- print 'data available as DATA'
- __builtin__.__GETDATA__ = False
-
- elif '__SAVEDATA__' in dir(__builtin__) and __builtin__.__SAVEDATA__:
- # path = 'PATH="'+PATH+'" '+path # not sure what this did
- __builtin__.__SAVEDATA__ = PATH = False
-
- elif 'ENGINE' in globals() and ENGINE:
- engine = create_engine(ENGINE)
-
- args = path.split(' ')
- for i in args:
- if i.startswith('MAKE_GLOBAL'):
- glovar = i.split('=')
- exec(glovar[0]+'='+glovar[1]+'=None')
- elif i.startswith('DB'):
- db = i.replace('DB=', '')
- __builtin__.DB = db
- exec("global engine\nengine=create_engine('"+driver+"://"+username+":"+password+"@"+host+":"+port+"/"+db+application_name+"')")
- exec('global DB\nDB=db')
-
- home = expanduser("~")
- filepath = home + '/.ipython/profile_default/startup/SQLCell/engines/engine_config.py'
-
- for line in fileinput.FileInput(filepath,inplace=1):
- line = re.sub("default_db = '.*'","default_db = '"+db+"'", line)
- print line,
-
- elif i.startswith('ENGINE'):
- exec("global ENGINE\nENGINE="+i.replace('ENGINE=', ""))
- if ENGINE != str(engine.url):
- exec("global engine\nengine=create_engine("+i.replace('ENGINE=', "")+application_name+")")
- conn_str = engine.url
- driver, username = conn_str.drivername, conn_str.username
- password, host = conn_str.password, conn_str.host
- port, db = conn_str.port, conn_str.database
-
- else:
- exec(i)
-
- psql_command = False
- if cell.startswith('\\'):
- psql_command = True
- db_name = db if isinstance(db, (str, unicode)) else __ENGINE__.url.database
-
- commands = ''
- for i in cell.strip().split(';'):
- if i:
- commands += ' -c ' + '"'+i+'" '
- commands = 'psql ' + db_name + commands + '-H'
-
- matches = re.findall(r'%\([a-zA-Z_][a-zA-Z0-9_]*\)s', cell)
-
- t0 = time.time()
- connection = engine.connect()
-
- try:
- if not psql_command:
- data = connection.execute(cell, reduce(build_dict, matches, {}))
- t1 = time.time() - t0
- columns = data.keys()
- table_data = [i for i in data] if 'pd' in globals() else [columns] + [i for i in data]
- if 'DISPLAY' in locals():
- if not DISPLAY:
- exec('__builtin__.' + glovar[1] + '=table_data')
- print 'To execute: ' + str(round(t1, 3)) + ' sec', '|',
- print 'Rows:', len(table_data), '|',
- print 'DB:', engine.url.database, '| Host:', engine.url.host
- print 'data not displayed but captured in variable: ' + glovar[1]
- return None
- df = to_table(table_data)
- else:
- output = subprocess.check_output(commands, shell=True)
- t1 = time.time() - t0
- if '
To execute: %s sec | '
- +'To render: %s sec | '
- +'Rows: %s | '
- +'DB: %s | Host: %s'
- )
- """ % (str(round(t1, 3)), str(round(t3, 3)), len(df.index), engine.url.database, engine.url.host)
- )
- )
-
- table_name = re.search('from\s*([a-z_][a-z\-_0-9]{,})', cell, re.IGNORECASE)
- table_name = None if not table_name else table_name.group(1).strip()
-
- if EDIT:
-
- primary_key_results = engine.execute("""
- SELECT
- %(table_name)s as table_name, pg_attribute.attname as column_name
- FROM pg_index, pg_class, pg_attribute, pg_namespace
- WHERE
- pg_class.oid = %(table_name)s::regclass AND
- indrelid = pg_class.oid AND
- nspname = 'public' AND
- pg_class.relnamespace = pg_namespace.oid AND
- pg_attribute.attrelid = pg_class.oid AND
- pg_attribute.attnum = any(pg_index.indkey)
- AND indisprimary
- """, {'table_name': table_name}).first()
-
- if primary_key_results:
- primary_key = primary_key_results.column_name
-
- if not re.search('join', cell, re.IGNORECASE):
-
- HTMLTable(table_data, unique_id).display(columns, msg=' | EDIT MODE')
-
- display(
- Javascript(
- """
- $('#table%s').editableTableWidget({preventColumns:[1]});
- $('#table%s').on('change', function(evt, newValue){
- var th = $('#table%s th').eq(evt.target.cellIndex);
- var columnName = th.text();
-
- var tableName = '%s';
- var primary_key = '%s';
-
- var pkId,
- pkValue;
- $('#table%s tr th').filter(function(i,v){
- if (v.innerHTML == primary_key){
- pkId = i;
- }
- });
-
- var row = $('#table%s > tbody > tr').eq(evt.target.parentNode.rowIndex-1);
- row.find('td').each(function(i,v){
- if (i == pkId){
- pkValue = v.innerHTML;
- }
- });
-
- var SQLText = "UPDATE " + tableName + " SET " + columnName + " = '" + newValue + "' WHERE " + primary_key + " = " + pkValue;
- console.log(SQLText, 't' + pkValue + 't');
-
- if (pkValue === ''){
- console.log('testingietren');
- } else {
- $('#error').remove();
- IPython.notebook.kernel.execute('update_table("'+SQLText+'")',
- {
- iopub: {
- output: function(response) {
- var $table = $('#table%s').parent();
- if (response.content.evalue){
- var error = response.content.evalue.replace(/\\n/g, "");
- $table.append('
'+error+'
');
- } else {
- $table.append('
Update successful
');
- }
- }
- }
- },
- {
- silent: false,
- store_history: false,
- stop_on_error: true
- }
- );
- }
- });
- """ % (unique_id, unique_id, unique_id, table_name, primary_key, unique_id, unique_id, unique_id)
- )
- )
-
- else:
- HTMLTable(table_data, unique_id).display(columns, msg=" | CAN\\'T EDIT MULTIPLE TABLES")
- return None
- # return df.replace(to_replace={'QUERY PLAN': {' ': '-'}}, regex=True)
- else:
- HTMLTable(table_data, unique_id).display(columns, msg=' | TABLE HAS NO PK')
- return None
- # return df.replace(to_replace={'QUERY PLAN': {' ': '-'}}, regex=True)
- else:
- HTMLTable(table_data, unique_id).display(columns, msg=' | READ MODE')
- return None
- # return df.replace(to_replace={'QUERY PLAN': {' ': '-'}}, regex=True)
-
-
-def sql(path, cell):
-
- t = threading.Thread(
- target=_SQL,
- args=(
- path, cell, {
- k:v
- for (k,v) in kernel_vars.g.iteritems()
- if k not in ('In', 'Out', 'v', 'k')
- and not k.startswith('_')
- and isinstance(v,
- (str, int, float, list, unicode, tuple)
- )
- }
- )
- )
- t.daemon = True
- t.start()
- return None
-
-
-__builtin__.update_table = update_table
-__builtin__.kill_last_pid_on_new_thread = kill_last_pid_on_new_thread
-__builtin__.jupyter_id = jupyter_id
-
-js = "IPython.CodeCell.config_defaults.highlight_modes['magic_sql'] = {'reg':[/^%%sql/]};"
-IPython.core.display.display_javascript(js, raw=True)
-
\ No newline at end of file
diff --git a/sqlcell/__init__.py b/sqlcell/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/sqlcell/_initdb.py b/sqlcell/_initdb.py
new file mode 100644
index 0000000..4d69511
--- /dev/null
+++ b/sqlcell/_initdb.py
@@ -0,0 +1,52 @@
+import sqlite3
+# sqlcell table
+
+db_file = 'sqlcell.db'
+
+create_table_sql = """CREATE TABLE IF NOT EXISTS sqlcell (
+ id integer PRIMARY KEY,
+ key text NOT NULL,
+ value BINARY,
+ dt datetime default current_timestamp
+);"""
+
+create_settings_sql = """CREATE TABLE IF NOT EXISTS settings (
+ id integer PRIMARY KEY,
+ key text NOT NULL,
+ value text NOT NULL,
+ dt datetime default current_timestamp
+);"""
+
+create_engines_sql = """CREATE TABLE IF NOT EXISTS engines (
+ id integer PRIMARY KEY,
+ alias text,
+ db text NOT NULL,
+ host text NOT NULL,
+ engine text NOT NULL,
+ engine_b blob,
+ dt datetime default current_timestamp
+);"""
+
+create_hooks_sql = """CREATE TABLE IF NOT EXISTS hooks (
+ id integer PRIMARY KEY,
+ key text NOT NULL UNIQUE,
+ cmd text NOT NULL,
+ engine text NOT NULL,
+ engine_b blob,
+ dt datetime default current_timestamp,
+ UNIQUE (key, engine) ON CONFLICT IGNORE
+);
+"""
+
+tables = [create_table_sql, create_settings_sql, create_engines_sql, create_hooks_sql]
+
+def run():
+ """
+ initialize sqlite3 database to record engines/hooks
+ """
+ conn = sqlite3.connect(db_file)
+ for table in tables:
+ conn.execute(table)
+
+if __name__ == '__main__':
+ run()
diff --git a/sqlcell/args.py b/sqlcell/args.py
new file mode 100644
index 0000000..97a4102
--- /dev/null
+++ b/sqlcell/args.py
@@ -0,0 +1,37 @@
+import argparse
+import shlex
+
+class ArgHandler(object):
+ def __init__(self, line):
+ self.parser = argparse.ArgumentParser(description='SQLCell arguments')
+ self.parser.add_argument(
+ "-e", "--engine",
+ help='Engine param, specify your connection string: --engine=postgresql://user:password@localhost:5432/mydatabase',
+ required=False
+ )
+ self.parser.add_argument(
+ "-es", "--engines",
+ help='add new engines to be aliased and stored for future use without having to specify entire connection string.',
+ required=False, default=False, action="store_true"
+ )
+ self.parser.add_argument(
+ "-v", "--var",
+ help='Variable name to write output to: --var=foo',
+ required=False
+ )
+ self.parser.add_argument(
+ "-bg", "--background",
+ help='whether to run query in background or not: --background runs in background',
+ required=False, default=False, action="store_true"
+ )
+ self.parser.add_argument(
+ "-k", "--hook",
+ help='define shortcuts with the --hook param',
+ required=False, default=False, action="store_true"
+ )
+ self.parser.add_argument(
+ "-r", "--refresh",
+ help='refresh engines/hooks by specifying --refresh flag',
+ required=False, default=False, action="store_true"
+ )
+ self.args = self.parser.parse_args(shlex.split(line))
diff --git a/sqlcell/db.py b/sqlcell/db.py
new file mode 100644
index 0000000..3e173f1
--- /dev/null
+++ b/sqlcell/db.py
@@ -0,0 +1,110 @@
+from sqlalchemy import create_engine
+from sqlalchemy.engine.url import make_url
+from sqlalchemy.ext.automap import automap_base
+from sqlalchemy.orm import Session, sessionmaker
+from sqlalchemy import create_engine
+from sqlalchemy import desc, asc
+from sqlalchemy.engine.base import Engine
+import pandas as pd
+import re
+
+class DBSessionHandler(object):
+ def __init__(self):
+ Base = automap_base()
+ engine = create_engine("sqlite:///sqlcell.db")
+ Base.prepare(engine, reflect=True)
+ self.classes = Base.classes
+ self.tables = Base.metadata.tables.keys()
+ self.Sqlcell = Base.classes.sqlcell
+ self.Engines = Base.classes.engines
+ self.Hooks = Base.classes.hooks
+ Session = sessionmaker(autoflush=False)
+ Session.configure(bind=engine)
+ self.session = Session()
+
+ dbs = self.session.query(self.Engines).all()
+ self.db_info = {}
+ for row in dbs:
+ engine = row.engine
+ if row.db:
+ self.db_info[row.db] = engine
+ if row.alias:
+ self.db_info[row.alias] = engine
+ self.db_info[engine] = engine
+ self.db_info[row.host] = engine
+
+ def recycle(self):
+ pass
+
+ def create(self):
+ pass
+
+ def dispose(self):
+ pass
+
+class EngineHandler(DBSessionHandler):
+ """remove all engines from sqlcell.db:
+ %%sql refresh
+ may have to use @cell_line_magic.
+ add multiple new engines:
+ %%sql add
+ foo=
+ bar=
+ baz="""
+ def __init__(self, *args, **kwargs):
+ super(EngineHandler, self).__init__()
+
+ def list(self):
+ "show all alias/engines"
+ engines = []
+ for row in self.session.query(self.Engines):
+ engine = {
+ 'Alias': row.alias,
+ 'Engine': row.engine
+ }
+ engines.append(engine)
+ return pd.DataFrame(engines)
+
+ @property
+ def latest_engine(self) -> Engine:
+ record = self.session.query(self.Engines).order_by(desc(self.Engines.dt)).limit(1).first()
+ if record:
+ engine = record.engine
+ return create_engine(engine)
+
+ def get_engine(self, engine_var: str, session_engine: bool or Engine=False, as_binary: bool=False):
+ if engine_var:
+ if engine_var not in self.db_info:
+ engine = create_engine(engine_var) #new engines
+ self.add_engine(engine)
+ else:
+ engine = create_engine(self.db_info[engine_var]) #engine lookup
+ else:
+ engine = session_engine or self.latest_engine
+ return engine
+
+ def add_engine(self, engine: Engine, alias: str=None) -> None:
+ if isinstance(engine, str):
+ engine = make_url(engine)
+ else:
+ engine = engine.url
+ host = engine.host
+ db = engine.database
+ engine_str = str(engine)
+ engine_exists_check = self.session.query(self.Engines).filter_by(db=db, host=host, engine=engine_str).first()
+ if engine_exists_check: return None
+ self.session.add(self.Engines(db=db, host=host, engine=engine_str, alias=alias))
+ self.session.commit()
+
+ def add_alias(self, cell):
+ for i in re.split('\n{1,}', cell):
+ row = i.replace(' ', '').split('=', 1)
+ if row[1:]:
+ alias, engine = row
+ self.add_engine(engine, alias=alias)
+ return ('Engines successfully registered')
+
+ def refresh(self, cell):
+ self.session.query(self.Engines).delete()
+ self.session.commit()
+
diff --git a/sqlcell/hooks.py b/sqlcell/hooks.py
new file mode 100644
index 0000000..e1966bc
--- /dev/null
+++ b/sqlcell/hooks.py
@@ -0,0 +1,58 @@
+from sqlalchemy import create_engine
+from sqlcell.db import DBSessionHandler, EngineHandler
+import pandas as pd
+
+class HookHandler(EngineHandler):
+ """input common queries to remember with a key/value pair. ie,
+ %%sql hook
+ \d="
+ \dt="""
+ def __init__(self, engine, *args, **kwargs):
+ super().__init__()
+ self.hook_engine = engine
+
+ def is_engine(self, engine: str):
+ try:
+ create_engine(engine)
+ return True
+ except:
+ return False
+
+ def add(self, line, cell):
+ "add hook to db"
+ cmds_to_add = []
+ hooks = cell.split('\n\n')
+ for hook in hooks:
+ hook = hook.strip()
+ if hook:
+ key, cmd = [i.strip() for i in hook.split('=', 1)]
+ cmds_to_add.append((key, cmd))
+
+ for key, cmd in cmds_to_add:
+ self.session.add(self.Hooks(key=key, engine='', cmd=cmd))
+ self.session.commit()
+ return self
+
+ def run(self, cell, engine_var):
+ cell = cell.replace('~', '').split(' ')
+ engine_alias, sql, cmd_args = cell[0], cell[1], cell[2:]
+ hook_query = self.session.query(self.Hooks).filter_by(key=sql).first()
+ hook_cmd = hook_query.cmd
+ hook_engine = self.get_engine(engine_alias)
+ self.hook_engine = hook_engine
+ return hook_engine, hook_cmd.format(*cmd_args)
+
+ def list(self, *srgs, **kwargs):
+ hooks = []
+ for row in self.session.query(self.Hooks).all():
+ hook = {
+ 'Alias': row.key,
+ 'Hook': row.cmd,
+ 'Engine': row.engine
+ }
+ hooks.append(hook)
+ return pd.DataFrame(hooks)
+
+ def refresh(self, cell):
+ self.session.query(self.Hooks).delete()
+ self.session.commit()
diff --git a/sqlcell/sqlcell.py b/sqlcell/sqlcell.py
new file mode 100644
index 0000000..8515bbc
--- /dev/null
+++ b/sqlcell/sqlcell.py
@@ -0,0 +1,147 @@
+from IPython.core.magic import (Magics, magics_class, line_magic,
+ cell_magic, line_cell_magic)
+from sqlalchemy import create_engine
+from sqlalchemy.ext.automap import automap_base
+from sqlalchemy.orm import Session, sessionmaker
+from sqlalchemy import create_engine
+from sqlalchemy import desc, asc
+from sqlalchemy.engine.base import Engine
+from sqlalchemy import sql
+import pandas as pd
+import pickle
+import threading
+################# SQLCell modules #################
+from sqlcell.db import EngineHandler, DBSessionHandler
+from sqlcell.args import ArgHandler
+from sqlcell.hooks import HookHandler
+from sqlcell._initdb import run
+
+
+@magics_class
+class SQLCell(Magics, EngineHandler):
+
+ current_engine = False
+ current_hook_engine = False
+ modes = ['query', 'hook', 'refresh']
+ # consider yaml file for these types of params:
+ hook_indicator = '~'
+
+ def __init__(self, shell, data):
+ # You must call the parent constructor
+ super(SQLCell, self).__init__(shell)
+ self.shell = shell
+ self.data = data
+ self.ipy = get_ipython()
+ self.refresh_options = ['hooks', 'engines']
+ self.line_args = None
+
+ def register_line_vars(self, line):
+ """options: engine, var, bg"""
+ mode = self.get_mode(line)
+ if line.strip() and mode == 'query':
+ line = line.split(' ')
+ line_vars = {}
+ for var in line:
+ key,value = var.split('=')
+ line_vars[key] = value
+ self.line_vars = line_vars
+ return line_vars
+ return {}
+
+ def push_var(self, obj):
+ if self.line_args.var:
+ self.ipy.push({self.line_args.var: obj})
+
+ def async_handler(self, obj):
+ self.push_var(obj)
+ return obj
+
+ def run_query(self, engine, query_params, var=None, callback=None, **kwargs):
+ results = pd.DataFrame([dict(row) for row in engine.execute(*query_params)])
+ return callback(results)
+
+ def query_router(self, *args):
+ if self.line_args.background:
+ processThread = threading.Thread(target=self.run_query, args=args)
+ processThread.start()
+ return None
+ return self.run_query(*args)
+
+ def get_mode(self, line):
+ line = [l.split('=') for l in line.split('=')]
+ if len(line) == 0:
+ if line in SQLCell.modes: return line
+ else: raise Exception('Invalid mode, please review docs')
+ return 'query'
+
+ def get_bind_params(self, params, ipython):
+ return {key:getattr(ipython.user_module, key) for key in params.keys()}
+
+ def get_sql_statement(self, cell):
+ text = sql.text(cell)
+ params = text.compile().params
+ bind_params = self.get_bind_params(params, self.ipy)
+ return (text, bind_params)
+
+ @line_cell_magic
+ def sql(self, line: str="", cell: str="") -> None:
+
+ line = line.strip()
+ cell = cell.strip()
+
+ line_args = ArgHandler(line).args
+ container_var = line_args.var
+ engine_var = line_args.engine
+ background = line_args.background
+ hook = line_args.hook
+ refresh = line_args.refresh
+ add_engines = line_args.engines
+ # refer to all args as self.line_args. to get rid of entire block ^?
+ self.line_args = line_args
+
+ ############################ Refresh logic ##########################
+ if refresh and cell in self.refresh_options:
+ if cell in self.tables:
+ self.session.query(getattr(self.classes, cell)).delete()
+ self.session.commit()
+ return ('Removed all records from ' + cell)
+ ############################ End Refresh logic ######################
+ ############################ Engine Aliases Logic ###################
+ if self.line_args.engines:
+ if cell == 'list':
+ return self.list()
+ else:
+ self.add_alias(cell)
+ # need to reinit db_info to update new engines added
+ self.db_info = SQLCell(self.shell, self.data).db_info
+ return ('Engines successfully registered')
+ ############################ End Engine Aliases #####################
+ # need engine below but not in refresh or alias logic
+ engine = self.get_engine(engine_var, session_engine=SQLCell.current_engine)
+ ########################## HookHandler logic ########################
+ hook_handler = HookHandler(engine)
+ if hook:
+ if cell == 'list':
+ return hook_handler.list()
+ hook_handler.add(line, cell)
+ return ('Hook successfully registered')
+
+ if cell.startswith(self.hook_indicator):
+ # run returns `engine, cmd`, consider renaming
+ engine, cell = hook_handler.run(cell, engine_var)
+ SQLCell.current_hook_engine = hook_handler.hook_engine
+ ########################## End HookHandler logic ####################
+ sql_statemnent_params = self.get_sql_statement(cell)
+ results = self.query_router(engine, sql_statemnent_params, self.line_args.var, self.async_handler)
+ # self.push_var(results)
+ engine.pool.dispose()
+
+ # reinitialize to update db_info, find better way
+ self.db_info = SQLCell(self.shell, self.data).db_info
+ SQLCell.current_engine = engine
+ return results
+
+def load_ipython_extension(ipython):
+ run()
+ magics = SQLCell(ipython, [])
+ ipython.register_magics(magics)
diff --git a/sqlcell_app.py b/sqlcell_app.py
deleted file mode 100644
index d136cf2..0000000
--- a/sqlcell_app.py
+++ /dev/null
@@ -1,7 +0,0 @@
-# should be moved to Jupyter's or IPython's profile_default/startup directory
-from IPython.core.magic import register_line_cell_magic
-from SQLCell.sqlcell import sql, kernel_vars
-
-sql = register_line_cell_magic(sql)
-
-kernel_vars.g = globals()