diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml new file mode 100644 index 0000000..1b907d3 --- /dev/null +++ b/.github/FUNDING.yml @@ -0,0 +1 @@ +github: kennethreitz diff --git a/Pipfile.lock b/Pipfile.lock index c349898..2c61be1 100644 --- a/Pipfile.lock +++ b/Pipfile.lock @@ -32,137 +32,164 @@ ], "version": "==1.0.1" }, + "greenlet": { + "hashes": [ + "sha256:0a77691f0080c9da8dfc81e23f4e3cffa5accf0f5b56478951016d7cfead9196", + "sha256:0ddd77586553e3daf439aa88b6642c5f252f7ef79a39271c25b1d4bf1b7cbb85", + "sha256:111cfd92d78f2af0bc7317452bd93a477128af6327332ebf3c2be7df99566683", + "sha256:122c63ba795fdba4fc19c744df6277d9cfd913ed53d1a286f12189a0265316dd", + "sha256:181300f826625b7fd1182205b830642926f52bd8cdb08b34574c9d5b2b1813f7", + "sha256:1a1ada42a1fd2607d232ae11a7b3195735edaa49ea787a6d9e6a53afaf6f3476", + "sha256:1bb80c71de788b36cefb0c3bb6bfab306ba75073dbde2829c858dc3ad70f867c", + "sha256:1d1d4473ecb1c1d31ce8fd8d91e4da1b1f64d425c1dc965edc4ed2a63cfa67b2", + "sha256:292e801fcb3a0b3a12d8c603c7cf340659ea27fd73c98683e75800d9fd8f704c", + "sha256:2c65320774a8cd5fdb6e117c13afa91c4707548282464a18cf80243cf976b3e6", + "sha256:4365eccd68e72564c776418c53ce3c5af402bc526fe0653722bc89efd85bf12d", + "sha256:5352c15c1d91d22902582e891f27728d8dac3bd5e0ee565b6a9f575355e6d92f", + "sha256:58ca0f078d1c135ecf1879d50711f925ee238fe773dfe44e206d7d126f5bc664", + "sha256:5d4030b04061fdf4cbc446008e238e44936d77a04b2b32f804688ad64197953c", + "sha256:5d69bbd9547d3bc49f8a545db7a0bd69f407badd2ff0f6e1a163680b5841d2b0", + "sha256:5f297cb343114b33a13755032ecf7109b07b9a0020e841d1c3cedff6602cc139", + "sha256:62afad6e5fd70f34d773ffcbb7c22657e1d46d7fd7c95a43361de979f0a45aef", + "sha256:647ba1df86d025f5a34043451d7c4a9f05f240bee06277a524daad11f997d1e7", + "sha256:719e169c79255816cdcf6dccd9ed2d089a72a9f6c42273aae12d55e8d35bdcf8", + "sha256:7cd5a237f241f2764324396e06298b5dee0df580cf06ef4ada0ff9bff851286c", + "sha256:875d4c60a6299f55df1c3bb870ebe6dcb7db28c165ab9ea6cdc5d5af36bb33ce", + "sha256:90b6a25841488cf2cb1c8623a53e6879573010a669455046df5f029d93db51b7", + "sha256:94620ed996a7632723a424bccb84b07e7b861ab7bb06a5aeb041c111dd723d36", + "sha256:b5f1b333015d53d4b381745f5de842f19fe59728b65f0fbb662dafbe2018c3a5", + "sha256:c5b22b31c947ad8b6964d4ed66776bcae986f73669ba50620162ba7c832a6b6a", + "sha256:c93d1a71c3fe222308939b2e516c07f35a849c5047f0197442a4d6fbcb4128ee", + "sha256:cdb90267650c1edb54459cdb51dab865f6c6594c3a47ebd441bc493360c7af70", + "sha256:cfd06e0f0cc8db2a854137bd79154b61ecd940dce96fad0cba23fe31de0b793c", + "sha256:d3789c1c394944084b5e57c192889985a9f23bd985f6d15728c745d380318128", + "sha256:da7d09ad0f24270b20f77d56934e196e982af0d0a2446120cb772be4e060e1a2", + "sha256:df3e83323268594fa9755480a442cabfe8d82b21aba815a71acf1bb6c1776218", + "sha256:df8053867c831b2643b2c489fe1d62049a98566b1646b194cc815f13e27b90df", + "sha256:e1128e022d8dce375362e063754e129750323b67454cac5600008aad9f54139e", + "sha256:e6e9fdaf6c90d02b95e6b0709aeb1aba5affbbb9ccaea5502f8638e4323206be", + "sha256:eac8803c9ad1817ce3d8d15d1bb82c2da3feda6bee1153eec5c58fa6e5d3f770", + "sha256:eb333b90036358a0e2c57373f72e7648d7207b76ef0bd00a4f7daad1f79f5203", + "sha256:ed1d1351f05e795a527abc04a0d82e9aecd3bdf9f46662c36ff47b0b00ecaf06", + "sha256:f3dc68272990849132d6698f7dc6df2ab62a88b0d36e54702a8fd16c0490e44f", + "sha256:f59eded163d9752fd49978e0bab7a1ff21b1b8d25c05f0995d140cc08ac83379", + "sha256:f5e2d36c86c7b03c94b8459c3bd2c9fe2c7dab4b258b8885617d44a22e453fb7", + "sha256:f6f65bf54215e4ebf6b01e4bb94c49180a589573df643735107056f7a910275b", + "sha256:f8450d5ef759dbe59f84f2c9f77491bb3d3c44bc1a573746daf086e70b14c243", + "sha256:f97d83049715fd9dec7911860ecf0e17b48d8725de01e45de07d8ac0bd5bc378" + ], + "markers": "python_version >= '3'", + "version": "==1.0.0" + }, "jdcal": { "hashes": [ - "sha256:b760160f8dc8cc51d17875c6b663fafe64be699e10ce34b6a95184b5aa0fdc9e" + "sha256:1abf1305fce18b4e8aa248cf8fe0c56ce2032392bc64bbd61b5dff2a19ec8bba", + "sha256:472872e096eb8df219c23f2689fc336668bdb43d194094b5cc1707e1640acfc8" ], - "version": "==1.3" - }, - "odfpy": { - "hashes": [ - "sha256:6bcaf3b23aa9e49ed8c8c177266539b211add4e02402748a994451482a10cb1b", - "sha256:ab1d67311b3c42dfad1063692c419c137fd6d5a6f0c6380d13758c2593a2b8c9" - ], - "version": "==1.3.6" + "version": "==1.4.1" }, "openpyxl": { "hashes": [ - "sha256:63165adcc806a5c281febb344f3594541f778f05b99a3a5e802941a3c0a85f71" + "sha256:626d38647c063d55803ef4971c4d43226538d4e95cb6260c094e363ee33e10c7" ], - "version": "==2.5.2" + "version": "==2.4.11" }, "psycopg2": { "hashes": [ - "sha256:027ae518d0e3b8fff41990e598bc7774c3d08a3a20e9ecc0b59fb2aaaf152f7f", - "sha256:092a80da1b052a181b6e6c765849c9b32d46c5dac3b81bf8c9b83e697f3cdbe8", - "sha256:0b9851e798bae024ed1a2a6377a8dab4b8a128a56ed406f572f9f06194e4b275", - "sha256:179c52eb870110a8c1b460c86d4f696d58510ea025602cd3f81453746fccb94f", - "sha256:19983b77ec1fc2a210092aa0333ee48811fd9fb5f194c6cd5b927ed409aea5f8", - "sha256:1d90379d01d0dc50ae9b40c863933d87ff82d51dd7d52cea5d1cb7019afd72cd", - "sha256:27467fd5af1dcc0a82d72927113b8f92da8f44b2efbdb8906bd76face95b596d", - "sha256:32702e3bd8bfe12b36226ba9846ed9e22336fc4bd710039d594b36bd432ae255", - "sha256:33f9e1032095e1436fa9ec424abcbd4c170da934fb70e391c5d78275d0307c75", - "sha256:36030ca7f4b4519ee4f52a74edc4ec73c75abfb6ea1d80ac7480953d1c0aa3c3", - "sha256:363fbbf4189722fc46779be1fad2597e2c40b3f577dc618f353a46391cf5d235", - "sha256:6f302c486132f8dd11f143e919e236ea4467d53bf18c451cac577e6988ecbd05", - "sha256:733166464598c239323142c071fa4c9b91c14359176e5ae7e202db6bcc1d2eb5", - "sha256:7cbc3b21ce2f681ca9ad2d8c0901090b23a30c955e980ebf1006d41f37068a95", - "sha256:888bba7841116e529f407f15c6d28fe3ef0760df8c45257442ec2f14f161c871", - "sha256:8966829cb0d21a08a3c5ac971a2eb67c3927ae27c247300a8476554cc0ce2ae8", - "sha256:8bf51191d60f6987482ef0cfe8511bbf4877a5aa7f313d7b488b53189cf26209", - "sha256:8eb94c0625c529215b53c08fb4e461546e2f3fc96a49c13d5474b5ad7aeab6cf", - "sha256:8ebba5314c609a05c6955e5773c7e0e57b8dd817e4f751f30de729be58fa5e78", - "sha256:932a4c101af007cb3132b1f8a9ffef23386acc53dad46536dc5ba43a3235ae02", - "sha256:ad75fe10bea19ad2188c5cb5fc4cdf53ee808d9b44578c94a3cd1e9fc2beb656", - "sha256:aeaba399254ca79c299d9fe6aa811d3c3eac61458dee10270de7f4e71c624998", - "sha256:b178e0923c93393e16646155794521e063ec17b7cc9f943f15b7d4b39776ea2c", - "sha256:b68e89bb086a9476fa85298caab43f92d0a6af135a5f433d1f6b6d82cafa7b55", - "sha256:d74cf9234ba76426add5e123449be08993a9b13ff434c6efa3a07caa305a619f", - "sha256:f3d3a88128f0c219bdc5b2d9ccd496517199660cea021c560a3252116df91cbd", - "sha256:fe6a7f87356116f5ea840c65b032af17deef0e1a5c34013a2962dd6f99b860dd" + "sha256:00195b5f6832dbf2876b8bf77f12bdce648224c89c880719c745b90515233301", + "sha256:068115e13c70dc5982dfc00c5d70437fe37c014c808acce119b5448361c03725", + "sha256:26e7fd115a6db75267b325de0fba089b911a4a12ebd3d0b5e7acb7028bc46821", + "sha256:2c93d4d16933fea5bbacbe1aaf8fa8c1348740b2e50b3735d1b0bf8154cbf0f3", + "sha256:56007a226b8e95aa980ada7abdea6b40b75ce62a433bd27cec7a8178d57f4051", + "sha256:56fee7f818d032f802b8eed81ef0c1232b8b42390df189cab9cfa87573fe52c5", + "sha256:6a3d9efb6f36f1fe6aa8dbb5af55e067db802502c55a9defa47c5a1dad41df84", + "sha256:a49833abfdede8985ba3f3ec641f771cca215479f41523e99dace96d5b8cce2a", + "sha256:ad2fe8a37be669082e61fb001c185ffb58867fdbb3e7a6b0b0d2ffe232353a3e", + "sha256:b8cae8b2f022efa1f011cc753adb9cbadfa5a184431d09b273fb49b4167561ad", + "sha256:d160744652e81c80627a909a0e808f3c6653a40af435744de037e3172cf277f5", + "sha256:d5062ae50b222da28253059880a871dc87e099c25cb68acf613d9d227413d6f7", + "sha256:f22ea9b67aea4f4a1718300908a2fb62b3e4276cf00bd829a97ab5894af42ea3", + "sha256:f974c96fca34ae9e4f49839ba6b78addf0346777b46c4da27a7bf54f48d3057d", + "sha256:fb23f6c71107c37fd667cb4ea363ddeb936b348bbd6449278eb92c189699f543" ], - "version": "==2.7.4" - }, - "pyyaml": { - "hashes": [ - "sha256:0c507b7f74b3d2dd4d1322ec8a94794927305ab4cebbe89cc47fe5e81541e6e8", - "sha256:16b20e970597e051997d90dc2cddc713a2876c47e3d92d59ee198700c5427736", - "sha256:3262c96a1ca437e7e4763e2843746588a965426550f3797a79fca9c6199c431f", - "sha256:326420cbb492172dec84b0f65c80942de6cedb5233c413dd824483989c000608", - "sha256:4474f8ea030b5127225b8894d626bb66c01cda098d47a2b0d3429b6700af9fd8", - "sha256:592766c6303207a20efc445587778322d7f73b161bd994f227adaa341ba212ab", - "sha256:5ac82e411044fb129bae5cfbeb3ba626acb2af31a8d17d175004b70862a741a7", - "sha256:5f84523c076ad14ff5e6c037fe1c89a7f73a3e04cf0377cb4d017014976433f3", - "sha256:827dc04b8fa7d07c44de11fabbc888e627fa8293b695e0f99cb544fdfa1bf0d1", - "sha256:b4c423ab23291d3945ac61346feeb9a0dc4184999ede5e7c43e1ffb975130ae6", - "sha256:bc6bced57f826ca7cb5125a10b23fd0f2fff3b7c4701d64c439a300ce665fff8", - "sha256:c01b880ec30b5a6e6aa67b09a2fe3fb30473008c85cd6a67359a1b15ed6d83a4", - "sha256:ca233c64c6e40eaa6c66ef97058cdc80e8d0157a443655baa1b2966e812807ca", - "sha256:e863072cdf4c72eebf179342c94e6989c67185842d9997960b3e69290b2fa269" - ], - "version": "==3.12" + "version": "==2.8.6" }, "sqlalchemy": { "hashes": [ - "sha256:7cb00cc9b9f92ef8b4391c8a2051f81eeafefe32d63c6b395fd51401e9a39edb" + "sha256:065ac7331b87494a86bf3dc4430c1ee7779d6dc532213c528394ddd00804e518", + "sha256:099e63ffad329989080c533896267c40f9cb38ed5704168f7dae3afdda121e10", + "sha256:0d8aab144cf8d31c1ac834802c7df4430248f74bd8b3ed3149f9c9eec0eafe50", + "sha256:230b210fc6d1af5d555d1d04ff9bd4259d6ab82b020369724ab4a1c805a32dd3", + "sha256:25aaf0bec9eadde9789e3c0178c718ae6923b57485fdeae85999bc3089d9b871", + "sha256:29816a338982c30dd7ee76c4e79f17d5991abb1b6561e9f1d72703d030a79c86", + "sha256:2e1b8d31c97a2b91aea8ed8299ad360a32d60728a89f2aac9c98eef07a633a0e", + "sha256:343c679899afdc4952ac659dc46f2075a2bd4fba87ca0df264be838eecd02096", + "sha256:386f215248c3fb2fab9bb77f631bc3c6cd38354ca2363d241784f8297d16b80a", + "sha256:457a1652bc1c5f832165ff341380b3742bfb98b9ceca24576350992713ad700f", + "sha256:4e554872766d2783abf0a11704536596e8794229fb0fa63d311a74caae58c6c5", + "sha256:4edff2b4101a1c442fb1b17d594a5fdf99145f27c5eaffae12c26aef2bb2bf65", + "sha256:690fbca2a208314504a2ab46d3e7dae320247fcb1967863b9782a70bf49fc600", + "sha256:6c6090d73820dcf04549f0b6e80f67b46c8191f0e40bf09c6d6f8ece2464e8b6", + "sha256:7bdb0f972bc35054c05088e91cec8fa810c3aa565b690bae75c005ee430e12e8", + "sha256:815a8cdf9c0fa504d0bfbe83fb3e596b7663fc828b73259a20299c01330467aa", + "sha256:a28c7b96bc5beef585172ca9d79068ae7fa2527feaa26bd63371851d7894c66f", + "sha256:a8763fe4de02f746666161b130cc3e5d1494a6f5475f5622f05251739fc22e55", + "sha256:b0266e133d819d33b555798822606e876187a96798e2d8c9b7f85e419d73ef94", + "sha256:bb97aeaa699c43da62e35856ab56e5154d062c09a3593a2c12c67d6a21059920", + "sha256:bce6eaf7b9a3a445911e225570b8fd26b7e98654ac9f308a8a52addb64a2a488", + "sha256:c4485040d86d4b3d9aa509fd3c492de3687d9bf52fb85d66b33912ad068a088c", + "sha256:c6f228b79fd757d9ca539c9958190b3a44308f743dc7d83575aa0891033f6c86", + "sha256:cde2cf3ee76e8c538f2f43f5cf9252ad53404fc350801191128bab68f335a8b2", + "sha256:cfa4a336de7d32ae30b54f7b8ec888fb5c6313a1b7419a9d7b3f49cdd83012a3", + "sha256:cfbf2cf8e8ef0a1d23bfd0fa387057e6e522d55e43821f1d115941d913ee7762", + "sha256:e26791ac43806dec1f18d328596db87f1b37f9d8271997dd1233054b4c377f51", + "sha256:e7d262415e4adf148441bd9f10ae4e5498d6649962fabc62a64ec7b4891d56c5", + "sha256:e9e95568eafae18ac40d00694b82dc3febe653f81eee83204ef248563f39696d", + "sha256:ec7c33e22beac16b4c5348c41cd94cfee056152e55a0efc62843deebfc53fcb4", + "sha256:f239778cf03cd46da4962636501f6dea55af9b4684cd7ceee104ad4f0290e878", + "sha256:f31757972677fbe9132932a69a4f23db59187a072cc26427f56a3082b46b6dac", + "sha256:fbdcf9019e92253fc6aa0bcd5937302664c3a4d53884c425c0caa994e56c4421", + "sha256:fc82688695eacf77befc3d839df2bc7ff314cd1d547f120835acdcbac1a480b8" ], - "version": "==1.2.6" + "version": "==1.4.9" }, "tablib": { "hashes": [ - "sha256:b8cf50a61d66655229993f2ee29220553fb2c80403479f8e6de77c0c24649d87" + "sha256:41aa40981cddd7ec4d1fabeae7c38d271601b306386bd05b5c3bcae13e5aeb20", + "sha256:f83cac08454f225a34a305daa20e2110d5e6335135d505f93bc66583a5f9c10d" ], - "version": "==0.12.1" - }, - "unicodecsv": { - "hashes": [ - "sha256:018c08037d48649a0412063ff4eda26eaa81eff1546dbffa51fa5293276ff7fc" - ], - "version": "==0.14.1" - }, - "xlrd": { - "hashes": [ - "sha256:83a1d2f1091078fb3f65876753b5302c5cfb6a41de64b9587b74cefa75157148", - "sha256:8a21885513e6d915fe33a8ee5fdfa675433b61405ba13e2a69e62ee36828d7e2" - ], - "version": "==1.1.0" - }, - "xlwt": { - "hashes": [ - "sha256:a082260524678ba48a297d922cc385f58278b8aa68741596a87de01a9c628b2e", - "sha256:c59912717a9b28f1a3c2a98fd60741014b06b043936dcecbc113eaaada156c88" - ], - "version": "==1.3.0" + "version": "==3.0.0" } }, "develop": { "attrs": { "hashes": [ - "sha256:1c7960ccfd6a005cd9f7ba884e6316b5e430a3f1a6c37c5f87d8b43f83b54ec9", - "sha256:a17a9573a6f475c99b551c0e0a812707ddda1ec9653bed04c13841404ed6f450" + "sha256:31b2eced602aa8423c2aea9c76a724617ed67cf9513173fd3a4f03e3a929c7e6", + "sha256:832aa3cde19744e49938b91fea06d69ecb9e649c93ba974535d08ad92164f700" ], - "version": "==17.4.0" + "version": "==20.3.0" }, "more-itertools": { "hashes": [ - "sha256:0dd8f72eeab0d2c3bd489025bb2f6a1b8342f9b198f6fc37b52d15cfa4531fea", - "sha256:11a625025954c20145b37ff6309cd54e39ca94f72f6bb9576d1195db6fa2442e", - "sha256:c9ce7eccdcb901a2c75d326ea134e0886abfbea5f93e91cc95de9507c0816c44" + "sha256:5652a9ac72209ed7df8d9c15daf4e1aa0e3d2ccd3c87f8265a0673cd9cbc9ced", + "sha256:c5d6da9ca3ff65220c3bfd2a8db06d698f05d4d2b9be57e1deb2be5a45019713" ], - "version": "==4.1.0" + "version": "==8.7.0" }, "pluggy": { "hashes": [ - "sha256:7f8ae7f5bdf75671a718d2daf0a64b7885f74510bcd98b1a0bb420eb9a9d0cff" + "sha256:7f8ae7f5bdf75671a718d2daf0a64b7885f74510bcd98b1a0bb420eb9a9d0cff", + "sha256:d345c8fe681115900d6da8d048ba67c25df42973bda370783cd58826442dcd7c", + "sha256:e160a7fcf25762bb60efc7e171d4497ff1d8d2d75a3d0df7a21b76821ecbf5c5" ], "version": "==0.6.0" }, "py": { "hashes": [ - "sha256:29c9fab495d7528e80ba1e343b958684f4ace687327e6f789a94bf3d1915f881", - "sha256:983f77f3331356039fdd792e9220b7b8ee1aa6bd2b25f567a963ff1de5a64f6a" + "sha256:21b81bda15b66ef5e1a777a21c4dcd9c20ad3efd0b3f817e7a809035269e1bd3", + "sha256:3b80836aa6d1feeaa108e046da6423ab8f6ceda6468545ae8d02d9d58d18818a" ], - "version": "==1.5.3" + "version": "==1.10.0" }, "pytest": { "hashes": [ @@ -173,10 +200,10 @@ }, "six": { "hashes": [ - "sha256:70e8a77beed4562e7f14fe23a786b54f6296e34344c23bc42f07b15018ff98e9", - "sha256:832dc0e10feb1aa2c68dcc57dbb658f1c7e65b9b61af69048abc87a2db00a0eb" + "sha256:30639c035cdb23534cd4aa2dd52c3bf48f06e5f4a941509c8bafd8ce11080259", + "sha256:8b74bedcbbbaca38ff6d7491d76f2b06b3592611af620f8426e82dddb04a5ced" ], - "version": "==1.11.0" + "version": "==1.15.0" } } } diff --git a/README.rst b/README.rst index e65e439..bfb723e 100644 --- a/README.rst +++ b/README.rst @@ -5,9 +5,6 @@ Records: SQL for Humans™ .. image:: https://img.shields.io/pypi/v/records.svg :target: https://pypi.python.org/pypi/records -.. image:: https://travis-ci.org/kennethreitz/records.svg?branch=master - :target: https://travis-ci.org/kennethreitz/records - .. image:: https://img.shields.io/badge/SayThanks.io-☼-1EAEDB.svg :target: https://saythanks.io/to/kennethreitz @@ -25,6 +22,7 @@ while providing an elegant interface to work with your query results. *Database support includes RedShift, Postgres, MySQL, SQLite, Oracle, and MS-SQL (drivers not included).* +---------- ☤ The Basics ------------ @@ -82,10 +80,10 @@ Other options include ``rows.as_dict()`` and ``rows.as_dict(ordered=True)``. - Safe parameterization: ``Database.query('life=:everything', everything=42)``. - Queries can be passed as strings or filenames, parameters supported. - Transactions: ``t = Database.transaction(); t.commit()``. -- Bulk actions: ``Database.bulk_query()`` & ``Databse.bulk_query_file()``. +- Bulk actions: ``Database.bulk_query()`` & ``Database.bulk_query_file()``. Records is proudly powered by `SQLAlchemy `_ -and `Tablib `_. +and `Tablib `_. ☤ Data Export Functionality --------------------------- @@ -146,8 +144,7 @@ You get the point. All other features of Tablib are also available, so you can sort results, add/remove columns/rows, remove duplicates, transpose the table, add separators, slice data by column, and more. -See the `Tablib Documentation `_ -for more details. +See the `Tablib Documentation `_ for more details. ☤ Installation -------------- diff --git a/records.py b/records.py index ad4e667..096e10f 100644 --- a/records.py +++ b/records.py @@ -10,8 +10,6 @@ import tablib from docopt import docopt from sqlalchemy import create_engine, exc, inspect, text -DATABASE_URL = os.environ.get('DATABASE_URL') - def isexception(obj): """Given an object, return a boolean indicating whether it is an instance @@ -52,9 +50,12 @@ class Record(object): return self.values()[key] # Support for string-based lookup. - if key in self.keys(): - i = self.keys().index(key) - if self.keys().count(key) > 1: + usekeys = self.keys() + if hasattr(usekeys, "_keys"): # sqlalchemy 2.x uses (result.RMKeyView which has wrapped _keys as list) + usekeys = usekeys._keys + if key in usekeys: + i = usekeys.index(key) + if usekeys.count(key) > 1: raise KeyError("Record contains multiple '{}' fields.".format(key)) return self.values()[i] @@ -147,7 +148,7 @@ class RecordCollection(object): if is_int: key = slice(key, key + 1) - while len(self) < key.stop or key.stop is None: + while key.stop is None or len(self) < key.stop: try: next(self) except StopIteration: @@ -253,7 +254,7 @@ class Database(object): def __init__(self, db_url=None, **kwargs): # If no db_url was provided, fallback to $DATABASE_URL. - self.db_url = db_url or DATABASE_URL + self.db_url = db_url or os.environ.get('DATABASE_URL') if not self.db_url: raise ValueError('You must provide a db_url.') @@ -261,7 +262,13 @@ class Database(object): # Create an engine. self._engine = create_engine(self.db_url, **kwargs) self.open = True - + + def get_engine(self): + # Return the engine if open + if not self.open: + raise exc.ResourceClosedError('Database closed.') + return self._engine + def close(self): """Closes the Database.""" self._engine.dispose() @@ -276,27 +283,27 @@ class Database(object): def __repr__(self): return ''.format(self.open) - def get_table_names(self, internal=False): + def get_table_names(self, internal=False, **kwargs): """Returns a list of table names for the connected database.""" # Setup SQLAlchemy for Database inspection. - return inspect(self._engine).get_table_names() + return inspect(self._engine).get_table_names(**kwargs) - def get_connection(self): + def get_connection(self, close_with_result=False): """Get a connection to this Database. Connections are retrieved from a pool. """ if not self.open: raise exc.ResourceClosedError('Database closed.') - return Connection(self._engine.connect()) + return Connection(self._engine.connect(close_with_result=close_with_result), close_with_result) def query(self, query, fetchall=False, **params): """Executes the given SQL query against the Database. Parameters can, optionally, be provided. Returns a RecordCollection, which can be iterated over to get result rows as dictionaries. """ - with self.get_connection() as conn: + with self.get_connection(True) as conn: return conn.query(query, fetchall, **params) def bulk_query(self, query, *multiparams): @@ -308,7 +315,7 @@ class Database(object): def query_file(self, path, fetchall=False, **params): """Like Database.query, but takes a filename to load a query from.""" - with self.get_connection() as conn: + with self.get_connection(True) as conn: return conn.query_file(path, fetchall, **params) def bulk_query_file(self, path, *multiparams): @@ -335,12 +342,16 @@ class Database(object): class Connection(object): """A Database connection.""" - def __init__(self, connection): + def __init__(self, connection, close_with_result=False): self._conn = connection self.open = not connection.closed + self._close_with_result = close_with_result def close(self): - self._conn.close() + # No need to close if this connection is used for a single result. + # The connection will close when the results are all consumed or GCed. + if not self._close_with_result: + self._conn.close() self.open = False def __enter__(self): @@ -359,10 +370,13 @@ class Connection(object): """ # Execute the given query. - cursor = self._conn.execute(text(query), **params) # TODO: PARAMS GO HERE + cursor = self._conn.execute(text(query).bindparams(**params)) # TODO: PARAMS GO HERE # Row-by-row Record generator. - row_gen = (Record(cursor.keys(), row) for row in cursor) + row_gen = iter(Record([], [])) + + if cursor.returns_rows: + row_gen = (Record(cursor.keys(), row) for row in cursor) # Convert psycopg2 results to RecordCollection. results = RecordCollection(row_gen) @@ -426,9 +440,9 @@ def _reduce_datetimes(row): row = list(row) - for i in range(len(row)): - if hasattr(row[i], 'isoformat'): - row[i] = row[i].isoformat() + for i, element in enumerate(row): + if hasattr(element, 'isoformat'): + row[i] = element.isoformat() return tuple(row) def cli(): diff --git a/setup.py b/setup.py index 2b73047..4d84e69 100644 --- a/setup.py +++ b/setup.py @@ -48,6 +48,7 @@ class PublishCommand(Command): requires = ['SQLAlchemy', 'openpyxl<2.5.0', # temporary fix to issue #142 'tablib>=0.11.4', + 'openpyxl>2.6.0', # https://github.com/kennethreitz-archive/records/pull/184#issuecomment-606207851 'docopt'] version = '0.5.3'