From 41f1027e8af3c0cfbf43f24b5bc44a8384cfa171 Mon Sep 17 00:00:00 2001
From: Kareem Zidane <kzidane@cs50.harvard.edu>
Date: Sat, 27 May 2017 09:56:49 +0200
Subject: [PATCH 01/11] ignored *.pyc and *.db

---
 .gitignore | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/.gitignore b/.gitignore
index f75ca9b..5a13495 100644
--- a/.gitignore
+++ b/.gitignore
@@ -2,4 +2,6 @@
 !.gitignore
 !.travis.yml
 dist/
+*.db
 *.egg-info/
+*.pyc

From a1bbaab4704985c3c3f3d79e5653a4fc4b873e4c Mon Sep 17 00:00:00 2001
From: Kareem Zidane <kzidane@cs50.harvard.edu>
Date: Sat, 27 May 2017 10:06:24 +0200
Subject: [PATCH 02/11] exiting with error code if sql test failed

---
 tests/sqltests.py | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/tests/sqltests.py b/tests/sqltests.py
index d2204a1..6cddabb 100644
--- a/tests/sqltests.py
+++ b/tests/sqltests.py
@@ -1,3 +1,4 @@
+import sys
 import unittest
 from cs50.sql import SQL
 
@@ -126,4 +127,4 @@ def tearDownClass(self):
         unittest.TestLoader().loadTestsFromTestCase(PostgresTests)
     ])
 
-    unittest.TextTestRunner(verbosity=2).run(suite)
+    sys.exit(not unittest.TextTestRunner(verbosity=2).run(suite).wasSuccessful())

From f91a93ef359ec61c83743a5ac8a72a02550df693 Mon Sep 17 00:00:00 2001
From: Kareem Zidane <kzidane@cs50.harvard.edu>
Date: Sat, 27 May 2017 10:36:23 +0200
Subject: [PATCH 03/11] suppressed unknown table in mysql's tearDownClass

---
 tests/sqltests.py | 8 +++++++-
 1 file changed, 7 insertions(+), 1 deletion(-)

diff --git a/tests/sqltests.py b/tests/sqltests.py
index 6cddabb..7a569ad 100644
--- a/tests/sqltests.py
+++ b/tests/sqltests.py
@@ -84,7 +84,13 @@ def tearDown(self):
 
     @classmethod
     def tearDownClass(self):
-        self.db.execute("DROP TABLE IF EXISTS cs50")
+        try:
+            self.db.execute("DROP TABLE IF EXISTS cs50")
+        except RuntimeError as e:
+
+            # suppress "unknown table"
+            if not str(e).startswith("(1051L"):
+                raise e
 
 class PostgresTests(SQLTests):
     @classmethod

From d3c25fce4a0ca48b360ba3016a522f0f6109df99 Mon Sep 17 00:00:00 2001
From: Kareem Zidane <kzidane@cs50.harvard.edu>
Date: Sat, 27 May 2017 10:36:59 +0200
Subject: [PATCH 04/11] dropped postgres password for travis

---
 tests/sqltests.py | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/tests/sqltests.py b/tests/sqltests.py
index 7a569ad..cb8bec5 100644
--- a/tests/sqltests.py
+++ b/tests/sqltests.py
@@ -95,7 +95,7 @@ def tearDownClass(self):
 class PostgresTests(SQLTests):
     @classmethod
     def setUpClass(self):
-        self.db = SQL("postgresql://postgres:postgres@localhost/cs50_sql_tests")
+        self.db = SQL("postgresql://postgres@localhost/cs50_sql_tests")
 
     def setUp(self):
         self.db.execute("CREATE TABLE cs50 (id SERIAL PRIMARY KEY, val VARCHAR(16))")

From c3253f9ee3c508d709906ffaff854d28e20a2242 Mon Sep 17 00:00:00 2001
From: Kareem Zidane <kzidane@cs50.harvard.edu>
Date: Sat, 27 May 2017 10:51:20 +0200
Subject: [PATCH 05/11] running sql tests against python 2 and 3 on travis

---
 .travis.yml       | 17 ++++++++++++++---
 tests/sqltests.py |  6 +++---
 2 files changed, 17 insertions(+), 6 deletions(-)

diff --git a/.travis.yml b/.travis.yml
index f097728..287bcab 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -1,9 +1,20 @@
 language: python
-python: '3.4'
+python:
+- '2.7'
+- '3.4'
 branches:
   except: "/^v\\d/"
-install: true
-script: true
+services:
+- mysql
+- postgresql
+install:
+- python setup.py install
+- pip install mysqlclient
+- pip install psycopg2
+before_script:
+- mysql -e 'CREATE DATABASE IF NOT EXISTS test;'
+- psql -c 'create database test;' -U postgres
+script: python test/sqltests.py
 deploy:
 - provider: script
   script: 'curl --fail --data "{ \"tag_name\": \"v$(python setup.py --version)\",
diff --git a/tests/sqltests.py b/tests/sqltests.py
index cb8bec5..3cf5311 100644
--- a/tests/sqltests.py
+++ b/tests/sqltests.py
@@ -74,7 +74,7 @@ def test_update_returns_affected_rows(self):
 class MySQLTests(SQLTests):
     @classmethod
     def setUpClass(self):
-        self.db = SQL("mysql://root@localhost/cs50_sql_tests")
+        self.db = SQL("mysql://root@localhost/test")
 
     def setUp(self):
         self.db.execute("CREATE TABLE cs50 (id INTEGER NOT NULL AUTO_INCREMENT, val VARCHAR(16), PRIMARY KEY (id))")
@@ -95,7 +95,7 @@ def tearDownClass(self):
 class PostgresTests(SQLTests):
     @classmethod
     def setUpClass(self):
-        self.db = SQL("postgresql://postgres@localhost/cs50_sql_tests")
+        self.db = SQL("postgresql://postgres@localhost/test")
 
     def setUp(self):
         self.db.execute("CREATE TABLE cs50 (id SERIAL PRIMARY KEY, val VARCHAR(16))")
@@ -114,7 +114,7 @@ def test_insert_returns_last_row_id(self):
 class SQLiteTests(SQLTests):
     @classmethod
     def setUpClass(self):
-        self.db = SQL("sqlite:///cs50_sql_tests.db")
+        self.db = SQL("sqlite:///test.db")
 
     def setUp(self):
         self.db.execute("CREATE TABLE cs50(id INTEGER PRIMARY KEY, val TEXT)")

From 2a07bc877fc0245347bfce1f7f67126fef602f89 Mon Sep 17 00:00:00 2001
From: Kareem Zidane <kzidane@cs50.harvard.edu>
Date: Sat, 27 May 2017 11:17:11 +0200
Subject: [PATCH 06/11] removed __pycache__

---
 src/cs50/__pycache__/__init__.cpython-34.pyc | Bin 1121 -> 0 bytes
 src/cs50/__pycache__/__init__.cpython-36.pyc | Bin 1090 -> 0 bytes
 src/cs50/__pycache__/cs50.cpython-34.pyc     | Bin 3748 -> 0 bytes
 src/cs50/__pycache__/cs50.cpython-36.pyc     | Bin 4010 -> 0 bytes
 src/cs50/__pycache__/sql.cpython-34.pyc      | Bin 3943 -> 0 bytes
 5 files changed, 0 insertions(+), 0 deletions(-)
 delete mode 100644 src/cs50/__pycache__/__init__.cpython-34.pyc
 delete mode 100644 src/cs50/__pycache__/__init__.cpython-36.pyc
 delete mode 100644 src/cs50/__pycache__/cs50.cpython-34.pyc
 delete mode 100644 src/cs50/__pycache__/cs50.cpython-36.pyc
 delete mode 100644 src/cs50/__pycache__/sql.cpython-34.pyc

diff --git a/src/cs50/__pycache__/__init__.cpython-34.pyc b/src/cs50/__pycache__/__init__.cpython-34.pyc
deleted file mode 100644
index c4a23a20dcccc32310bcd1ab62ed9833c61d226c..0000000000000000000000000000000000000000
GIT binary patch
literal 0
HcmV?d00001

literal 1121
zcmZuw&2G~`5T3OiCrwgXRj3j@`GjPo*lAHgf(jM&fRIoS3Kw!&Irb)Yy7s!e8<kY$
z)V>U_z>6UH%BdF~04~frX&VHq?3dY{+4<(rKf9al-`|{1-z|V2u(lkOk8!K_2nqfO
zaDdUBOkhMHu^_R5Tk!Es;y}`XYt%P@*>GjU$N@%R)_|D{%z-O_D*^=l41M<oox<RT
zAo`>tPhxYvT{a5GxYa%aLsBF{L@p$?SER^yjmMfbaSidPVN^rESv@$JD=qT3c`2l3
zGJ1?WZ4cM-#*5WqKNx;^=ke$w;|os-Pp6UgBvaZG6OU(O8OengM};?L-dwT7JI{0~
z=Gwz&uX&uZeBot<(h=t@3EH*yRO?c`2*X6gDkv9dUj#x<!>8fOVVLD*km{V@nWOdR
zY-tvhJUr;{AB6qGu>UMfqxdu{roE+~9=^m}RJi^!iHa$cqR=VkXvAR5#Z;9-2eHUk
z&Yp*<IPYoE!z_Agp>n39GSX?!B((B=AJZT<#W&0R<4nK_ZuJo161za))B?E;mjFX-
z$<Vh37p>cBeg)sI9L4ygYE9;x7g5eC$8gC8zOUieZYYJ&p?MQhniZL*6l+y=CRvft
zTqJYO9vDg&0Z5lP<YMpdb_Q#vu?f*E6Rz2HxGzz_a5caTBX&XVtz^7>G{h`$PH<>*
z8@^aTF0Fg*5gvxVJ&?vPPNt3R_MOVc!d1;>bZTU)GcG&Ez?LS=$}y?dH}{#!4WsFB
z5#6S>gBPle0A!1F$Tr#hH?=i$->q7d8qbtgZMv!r%3Vs&=22Z<JT)oBd81Ss&vMJa
zUA32z2}C*wCf2&H-KI;nochjhj%=JQ*+t#RF>R?_F`ltl*XnA+%nLP5tlX$9S&{tD
XebqWz7Uz}uqE!o_OS)Fu+THmB{Co}!

diff --git a/src/cs50/__pycache__/__init__.cpython-36.pyc b/src/cs50/__pycache__/__init__.cpython-36.pyc
deleted file mode 100644
index 7eb8ca45c821c5d4b679a69e7913c6416a688c86..0000000000000000000000000000000000000000
GIT binary patch
literal 0
HcmV?d00001

literal 1090
zcmZuw!EV$r5Ve!+Zo1t<RdIn+!~rQtn=GvgQdOa%9uN{*2^9|HVr66RCan{vwpV4V
z<<$NRzrc?m`O2xk02ju|wp|1h&3IzZyv&<%_O`bAzrTB*e>e#JL~G)Ke*~%yz)(an
zM@hTl1XG8)-1&&!BI?o3HKHBnUb%_K@M-6?%e*Ueg)!uups;%b`DlED!G_r2=b3%K
z6&IW%Q1uiHLx7ADhvLMgJ?c{L8Yh5HyVoe`PzTU_^Wb={v?$+|wUC<0^cev61CVwn
z$khHQI{ENE;OS+-7l9Ii&Ql#orgR{t0WT&pm5U%tt6;){xneZ9D0D97I)Jn{Jj+?R
z2#QMSlygR-euJKCU8{pQrXo{Oy@2>C5^@&r#Vf$LDC;QKCBF-Y=r2VZ7S%l79lh9%
zNBi;UWt^wkSy9b~EzS@!fs2aQGih4Qm=u-HfunV!2^TX}3ms*mT!Focb8#`$VhAjT
zYN1M|(>m4p&=Ojq-`6zGY`3l073PhOLDe>xC8qeyk%J{Vff+-eo$;lA+sSVzbd9GN
zpBjHU=e$ZwW<2{H*@4`~{0iewib)lhDNn0-Ma&|^jUl9{3QY*K&TLMLijq>$IcE=S
z1zj)*``E*mPyU{6v=*A#imlk{tv1z1;4HC5r}(TZAK7(@A6pi1N4ZnFUmc8=PP=~y
z9&mHV(n7&ybYbA3XIyBV>9qx^#8u~9ZrT7>+RBV)2{$|UeC38c+2kU<WwHYsst*R?
zK0E`^f7q;rhj<)zjZcULC&cv0sw3bJ2sxjpO`qYdM+jUxLZk(k53RXtd+XU+r2SuQ
zxlK3yo`?HhvlsRf7cQ6_z?t#vFiclWPFbd#W3yo|4YAWS-L$S*MgIqH{KK|CukC-V
Nd@w%t9pBmB`2!&k6t@5X

diff --git a/src/cs50/__pycache__/cs50.cpython-34.pyc b/src/cs50/__pycache__/cs50.cpython-34.pyc
deleted file mode 100644
index dd8d33b93ba87f8c14dcf7963161fb808172e8cb..0000000000000000000000000000000000000000
GIT binary patch
literal 0
HcmV?d00001

literal 3748
zcmb_fOK%)S5boL8m)GlOUW8{88SoObCP5(KL?lv35E61ETS{VM7{)uj_N>=4>+YV!
zYdHtxT(}`_`~+^CIB?~_fggZ0S5Cfg;l_!tYP`F4NSp*R-tFy2bxl>**I$+Ytd3M>
z|B_3;jtKFGIP|#aU&Yt_f+oPn5p5wh9kJ{PhsHV4&WYt5uc7aXWmkL>xFX1l2UyOF
zJuK&i!p9Y@g4lDY;}v={#okPjbOi+w6vYFmFYyp<nTKT_4vC<`!y(pI5v^en42um{
z=pR|2DuNLfsEUY<3r0mS#xo-%uvi=4ha;lqq@x`jM%HU4QNxCDG~b~4-jhS;D!yh8
zjS}Q7oFzA58k|GNCI8?VT!husXWN4k_u)+$ZyQR7n(f4_H^a8tX;mbik3-|Hwv}8>
znoR{0)?_o*(pWzVd>zO*v7N+9j0ST`>sZ$-ynWr;&fM%Q<Qp6DU8S4t_+GscZ_fI&
z7vH;Z@xliz)gTAxp<nu5!`C3VV%HJ7LU=eF0sEYJoP^V?<<h)S?WWE{;s>FN`1yNs
zR>!fOZJ77p<)+^0re)6yqtJTZFzMr$I6ISl7wf&{Bkv~+@%vApV})q339TI20(7ty
zZd#02TdHBxanD;**0)xNtBF;f$41q1x`5qjKJqse+m|&ZnjG$L@w`z?^b9+7*sueo
zo_EBy{@J{aeK?p_W*!U!z7NJZu_qYpnWbz`TDYe}tI`6$qGz46{g7#zj(mblqLUTQ
zxU(}hFp5>o*GhD(<d^5A70=s@gQQLUs^@Jbe*0jh<at5dz;TqJNLQ&L<%eEKQPXrF
znogXK%aClfP;{zp)#cfSgBg0rak7a6^A$SVOB5Tp4A1W)LO{GDHp)W3Mx8~=Q&<D`
zH*!LM$P%tNyNE3ZlCH407<?aai}xJI*>VLww@E>*wDS{Vb3yrm^kqAYl#H9ws%<Nq
zI^G=MJ&bx+Kr5T*NZNHJ)m9ST_1h}4va#;#OROtA*dH1h#ke)n;6mYYk;IHljM5TI
z9cb@Z6-XOPXw==h{Ir=F(D0+TEUQ{oSo4D!lAw3a$C0Y@%KSbZnx<x_v_SjkWa_FY
zNJY&}D*%>ffYxX&bzxpwU}C|QDrWCZ9djs14A?U5Asf#?rx`~himp?|U*4H?%5K%s
zFJtaVuqfgjtZ&iT{^sd}l@~gsz_h?y?!leIOm|e+JP#3^+gqW?+k)7r2(1JHw}gp1
zpwJ`11*ttgxW7NA;DXTnEFwNdPBlU>Q&-X;HYNmhUXs1A2eHZmuN5Q>VZ)a}oIt1_
zeKzY!2$07_KMVZHFF!y3&KFl!g14uy%&pY%Te~vVhl4(apy<<RY9$V;egXY7r&U@s
z%GZr`VhT8xR&M$2L|rFF*JnvSr(dMKFHyrcxPOh)A%Y2So5rdk^#hztIj%G6jAUpb
z_Ufa>#h{Ot?=jf^?n!6?8RK4Oe5|$T)`VrK05D+a`#FXRc+jDFB84Tf3+BGPrTegn
zGT7*6gw4;-2{y>(M=9Y^*oY*=`p7tj7uYD~hU1X2Q|W=?{M3F02Yn14Ucs_{6;0~G
zhBR-x9gq`EG78)Q3Q{M07ziZ-0=8X-ydk&@U@!?7j1g({pztPE_Xz@hJP_6Q@$fYU
z+dI#Mhh@>7ID!YneHn4jGhN)u;=cUvxT79=JYD?8M@$9L|IY{QI9khsPx_7}2NHd1
z0FbsPfq-P@4+BA;!wEkM2*r#T`aoFDlQD-da10Z0!XWWQD1V3u2muql=o3*K2|N{Q
zoHp9%bZ_<ueMrF0EZ=9vEM;r(J<>e50I6b698>}3QkMVoRJ~I`vmD~$_D83If+C|}
zjZi>oekpRq9nSX!{|%(f|9ZVcP`QyliUP{4Gpxq=k&#yWA!>)UEPo>lqhFVcNv9KQ
zYvi>BIXzOFk)Pd=(-r(uA$@IuMkAFPYk^v>J+iHaYk{m%pt8frIHGiOSf*DK7<um8
z3>O-M;sEskss+R4f=oJbB)M9bsOh5^bVHTQy>;K3SfN^%(_vlJXJjieR$jk(qjpeX
z4D!d$808neyP{X3-Ib^|g@i&3k-Da9(t@$@H!V}8LQZHx6PA)y#?dj!gu%B4X)}pN
zl&IV|0p=*8<TwNL@Oo`@83kxFflE|)6LIhxbd7p08l*EDz(fAU|0-OkXi7hth$pSW
zNrQi<z*fyTEo}NN<T(Om2L3^U8)Uii<*efU2x!T$Jx--i?!?%nGnpHk7&|$38oiVM
E0FF{prT_o{

diff --git a/src/cs50/__pycache__/cs50.cpython-36.pyc b/src/cs50/__pycache__/cs50.cpython-36.pyc
deleted file mode 100644
index b4444811a29651f884b781ed251824821529a236..0000000000000000000000000000000000000000
GIT binary patch
literal 0
HcmV?d00001

literal 4010
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
X6n^gV3bu@HbuoG=T8-AQhSmQ7zgoyZ

diff --git a/src/cs50/__pycache__/sql.cpython-34.pyc b/src/cs50/__pycache__/sql.cpython-34.pyc
deleted file mode 100644
index 8e567319c0fdc2669c45658d3518ccd7543a53ca..0000000000000000000000000000000000000000
GIT binary patch
literal 0
HcmV?d00001

literal 3943
zcmb_f&2t<_6@T-wpI%9kjBPn2cmh$#`eS9vl>jQ2v9ScWY*dccM)BY<HJWKjvznb*
zchB0=mXrggNZ^nvj@-C#;NRdcpg7KzQ!Z3-4Htf|XZFL62^7Uh{idh;eRcQmz1REq
z)?Bk!f9uiO8KS?@%wwZ}6HWaLB*ec#F_Amz8{`_}ag$tg+_%WJ#(kSyn|>T(&Z1vI
z(jrlz=isQax^CP49J*;UU^;$GbZ(-l4v2t7ZH4v2A~cVcL34=z3M+Oi6jq_HmRsEq
z?u{;QOMl>?cPkF|MgPdjQfDC3r%@;z-%(M25DT7mZr#7r3D_V%7TRlQx6#x#$kAEK
zh<XMc8}uBM$TMj>StC2M=%7h*i;NSKjM3}t+p$TWO<9FI;QpmS+mA4efAD;4(uavb
zAL6UqHb?(xI^}a)3O^H0l&H*40^y{)j(QUNh2I^K>_v&_Y!t8NMC#$zJxA@Q!#H$y
z^pY|eC3{Y`?`KdIMgepxC-gIa$5+A$BR>{F=J-kI1Zk3>AEk-o%e`S=B$?_=HScHH
zU~P3ZOas-KaU_*{t0GyAS#P!)q*CCMeN&zNtRH`ChBeSD@`~w+$D8eH-kfr|b1$&v
z)q*{5Y#at+z~1Ez&kOva5}ud0wuVU-^~Hviscc)htwg-*T0<G<hWHc7CJyV1#Z{T6
z@J+pUbrps>gQL9ec~KH&p2rM|3Dqs*tg&P?j8O{!=!_XAAUFjc2Eko4^+$t%UYwz+
z(QB4)0Dw;{%1r73jus!barCCaP_av>oY-{eF%WuyJJI?_AJAiyPAYU#rDI^<rh_`k
zRO_o!uf|N6N{Eha>ecmlgN}h}zzO`2f*pb>6V{;p$p&p_P0f9Z!Wu<zvQA-x-hWO;
z!2-DUTGX3kCLXC#Hc!0;3bh#bU*k}eWE|J1x2Ro!3aD%GYLDx*4zX@2q}5D?E@+{p
z$@Dy1nNHu()2FEmr@Gt8&k-^3=4;eDqvOUm@L&_VkCL01I;#i&U>GM21d~JAIXZ4=
zpPRf@$4&B<nHC|X!ycR;H_DKLp7z4%;sk0NhawmvrhFaZh^tI6$~zN=0B1XS{zJME
zIFAbTs^r#YQV;fttyV^FGl)cCuyZ|513y+bIuDhQ>tYw-5k5E?h|zB+ra9_wg`uN{
zgFz}YXE&9OI2<4_{T<|_r+z#X>M~r9eTPF)Ar^O{Y-bo8h^)g=I2w(FIADaANn$lp
z3PP*bzW?5})j}6?!Ign8{l3VAoUWUV2UfXqe%olL(bN{mCq%nCT><UCCi;!RC@eBx
zW@x;wGunG5ePU=%oqWLw9!3g-<<oJl>=x;GbazVxB6`Y)5l3*wkDZBQ#cDY>BZo<m
zghGlC=_|&*InSbOKSGLfrso$R8ui0(c3->xSA4Ofpzy&Yd8v#{1mkmnu0o%3+%N-s
zSh5yD2kxCQcl~AVUW0QtWL^}jY9qh@N-<pkUKDF;vA=w!m`)`#VyCpsKgTkympz<e
zoztxE7)4U*&A*a$fkPgV4(uKv@B>a+yl+cd7@s<${Ttze^|WUFY|Q$lO`CBE%5=c@
z&T5%Ik7dri(zj(!b&y7km(aPir6Dnc9335(9iak$z~#<YP9j7o!p^9^^61J+|H?`@
zS{(PBm7lDw{B&)li^;P0IiqH@I84g$1KhOE&Gz}c8L4p*&h4EvjU_YZl?(x_+}aN@
zBK*W%EMe^7J`o6|QkmCLX9brh*XCYc?docr*HtEWc|5l@t?b6BpShJE@hi8YBy($b
zl1%IgnOo{e<*lbeDwIAHt=%-Q^!;8cT{}*bJ<Mm!^d!#fBG&0lx%TETj@>#_&N8wF
zf7?c0(Mv+7R+fET9wisHyjFVsHa^rlAY?BXZ&*vl%B1l(x3|joGTx)R|4o|fl*+xy
z7I6xiX2o)PWL|cHaVT^4on)am@Uf?H_R$iZ7#z4o{`s9rPjI~}7Q$t`ZRTd@YHmDj
z8}b6ar%U{zc|DF)7Nq^bjr*8UY@tD;&Hu*=rm8pj7Q)cTZ8n$Ny);VNmaMZgZ!-A?
zlfu`cul>}gaORa8^-TyUmM3%R(vsnrqqYCt=goG5Z$Al20Pass&+Dh*FxGb|&wDcT
z<8r3vdARC%o(8+*i%-7rQuWGhzAsBg%k%)I8;*QS#4A=+kd{?7T819WD}t|Un|eEn
zIP%<iK9~rO96ZDAQ$29!#$egOy;Y6oe(_jc>~7rKxc%U<y7VQOblJ|!FqxF8S(pI-
zHtxY$+msH3a!aPqX58m<ki6p(ZH$o%uiWqHp%>oW+3ap?v3H|ocJ|#LtgXY}2OGx^
z@2}r_u+hd$`@G&ycaGz7UEsRxXPhXo<^+jL<@38yjA}Ufwli^<<rP%OzLH;uORk-8
za5s5Zbiq_^W73zegCnn%TPZny@>+Qb$xVq2q=YYn{oJ_Y)^|k~?EBEwh<zlH^jQ?T
zb-aQUck0{{hk-k<vyhCkBYnY^6)4o%MDmfgPZg-X#%8L8Gx~tt#-ujq)`2)G4!SCZ
z8penS*if<oA=mW8%Ul0G#+T95RS>G2|K6NsSqs*At7R<Nm+TAHlC=yVErQl`t6Qgy
zI+9e2ukNR24hlak<hP+izRRS|<RX(66E-trn!`~iLdjWy^JM{AqiVM1^;o+m7n$k1
oOt=8Zb4<Pil2_B69)3A#zE>&D>(`4YyTRE_|J-Q7U;StP3xl+(`v3p{


From 77dbb658ad1bb942954b8159e2cd75c227dde66f Mon Sep 17 00:00:00 2001
From: Kareem Zidane <kzidane@cs50.harvard.edu>
Date: Sat, 27 May 2017 11:17:31 +0200
Subject: [PATCH 07/11] fixed package path

---
 setup.py | 1 +
 1 file changed, 1 insertion(+)

diff --git a/setup.py b/setup.py
index 2342a33..6b7fce7 100644
--- a/setup.py
+++ b/setup.py
@@ -13,6 +13,7 @@
     install_requires=["SQLAlchemy"],
     keywords="cs50",
     name="cs50",
+    package_dir={"": "src"},
     packages=["cs50"],
     url="https://github.com/cs50/python-cs50",
     version="2.2.0"

From 4bc77abd3be2c20dc166e197f8cabf012e869948 Mon Sep 17 00:00:00 2001
From: Kareem Zidane <kzidane@cs50.harvard.edu>
Date: Sat, 27 May 2017 11:21:23 +0200
Subject: [PATCH 08/11] fixed sqltests.py path

---
 .travis.yml | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/.travis.yml b/.travis.yml
index 287bcab..a20df42 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -14,7 +14,7 @@ install:
 before_script:
 - mysql -e 'CREATE DATABASE IF NOT EXISTS test;'
 - psql -c 'create database test;' -U postgres
-script: python test/sqltests.py
+script: python tests/sqltests.py
 deploy:
 - provider: script
   script: 'curl --fail --data "{ \"tag_name\": \"v$(python setup.py --version)\",

From a45a3ec575b7fc0cf421d3c62ccabaf7663735f1 Mon Sep 17 00:00:00 2001
From: Kareem Zidane <kzidane@cs50.harvard.edu>
Date: Sat, 27 May 2017 11:55:19 +0200
Subject: [PATCH 09/11] fixed unknown table suppression in sql tests

---
 tests/sqltests.py | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)

diff --git a/tests/sqltests.py b/tests/sqltests.py
index 3cf5311..26778dc 100644
--- a/tests/sqltests.py
+++ b/tests/sqltests.py
@@ -1,6 +1,7 @@
 import sys
 import unittest
 from cs50.sql import SQL
+import warnings
 
 class SQLTests(unittest.TestCase):
     def test_delete_returns_affected_rows(self):
@@ -86,10 +87,9 @@ def tearDown(self):
     def tearDownClass(self):
         try:
             self.db.execute("DROP TABLE IF EXISTS cs50")
-        except RuntimeError as e:
-
+        except Warning as e:
             # suppress "unknown table"
-            if not str(e).startswith("(1051L"):
+            if not str(e).startswith("(1051"):
                 raise e
 
 class PostgresTests(SQLTests):

From f7cf36992f9805ad6c37854bbcf814623161cbb2 Mon Sep 17 00:00:00 2001
From: Kareem Zidane <kzidane@cs50.harvard.edu>
Date: Sat, 27 May 2017 12:03:29 +0200
Subject: [PATCH 10/11] simplified sqltests

---
 tests/sqltests.py | 34 ++++++++--------------------------
 1 file changed, 8 insertions(+), 26 deletions(-)

diff --git a/tests/sqltests.py b/tests/sqltests.py
index 26778dc..0ce8af0 100644
--- a/tests/sqltests.py
+++ b/tests/sqltests.py
@@ -72,14 +72,6 @@ def test_update_returns_affected_rows(self):
         self.assertEqual(self.db.execute("UPDATE cs50 SET val = 'foo' WHERE id > 1"), 2)
         self.assertEqual(self.db.execute("UPDATE cs50 SET val = 'foo' WHERE id = -50"), 0)
 
-class MySQLTests(SQLTests):
-    @classmethod
-    def setUpClass(self):
-        self.db = SQL("mysql://root@localhost/test")
-
-    def setUp(self):
-        self.db.execute("CREATE TABLE cs50 (id INTEGER NOT NULL AUTO_INCREMENT, val VARCHAR(16), PRIMARY KEY (id))")
-
     def tearDown(self):
         self.db.execute("DROP TABLE cs50")
 
@@ -92,24 +84,21 @@ def tearDownClass(self):
             if not str(e).startswith("(1051"):
                 raise e
 
-class PostgresTests(SQLTests):
+class MySQLTests(SQLTests):
     @classmethod
     def setUpClass(self):
-        self.db = SQL("postgresql://postgres@localhost/test")
+        self.db = SQL("mysql://root@localhost/test")
 
     def setUp(self):
-        self.db.execute("CREATE TABLE cs50 (id SERIAL PRIMARY KEY, val VARCHAR(16))")
-
-    def tearDown(self):
-        self.db.execute("DROP TABLE cs50")
+        self.db.execute("CREATE TABLE cs50 (id INTEGER NOT NULL AUTO_INCREMENT, val VARCHAR(16), PRIMARY KEY (id))")
 
+class PostgresTests(SQLTests):
     @classmethod
-    def tearDownClass(self):
-        self.db.execute("DROP TABLE IF EXISTS cs50")
+    def setUpClass(self):
+        self.db = SQL("postgresql://postgres@localhost/test")
 
-    def test_insert_returns_last_row_id(self):
-        self.assertEqual(self.db.execute("INSERT INTO cs50(val) VALUES('foo')"), 1)
-        self.assertEqual(self.db.execute("INSERT INTO cs50(val) VALUES('bar')"), 2)
+    def setUp(self):
+        self.db.execute("CREATE TABLE cs50 (id SERIAL PRIMARY KEY, val VARCHAR(16))")
 
 class SQLiteTests(SQLTests):
     @classmethod
@@ -119,13 +108,6 @@ def setUpClass(self):
     def setUp(self):
         self.db.execute("CREATE TABLE cs50(id INTEGER PRIMARY KEY, val TEXT)")
 
-    def tearDown(self):
-        self.db.execute("DROP TABLE cs50")
-
-    @classmethod
-    def tearDownClass(self):
-        self.db.execute("DROP TABLE IF EXISTS cs50")
-
 if __name__ == "__main__":
     suite = unittest.TestSuite([
         unittest.TestLoader().loadTestsFromTestCase(SQLiteTests),

From 5e2b03eee32a6c48f645cb586daabbb1715d7be7 Mon Sep 17 00:00:00 2001
From: Kareem Zidane <kzidane@cs50.harvard.edu>
Date: Sat, 27 May 2017 14:50:00 +0200
Subject: [PATCH 11/11] added test for multi-insert statements

---
 tests/sqltests.py | 10 +++++++++-
 1 file changed, 9 insertions(+), 1 deletion(-)

diff --git a/tests/sqltests.py b/tests/sqltests.py
index 0ce8af0..8e518b4 100644
--- a/tests/sqltests.py
+++ b/tests/sqltests.py
@@ -1,9 +1,12 @@
+from cs50.sql import SQL
 import sys
 import unittest
-from cs50.sql import SQL
 import warnings
 
 class SQLTests(unittest.TestCase):
+    def multi_inserts_enabled(self):
+        return True
+
     def test_delete_returns_affected_rows(self):
         rows = [
             {"id": 1, "val": "foo"},
@@ -24,6 +27,8 @@ def test_delete_returns_affected_rows(self):
     def test_insert_returns_last_row_id(self):
         self.assertEqual(self.db.execute("INSERT INTO cs50(val) VALUES('foo')"), 1)
         self.assertEqual(self.db.execute("INSERT INTO cs50(val) VALUES('bar')"), 2)
+        if self.multi_inserts_enabled():
+            self.assertEqual(self.db.execute("INSERT INTO cs50(val) VALUES('baz'); INSERT INTO cs50(val) VALUES('qux')"), 4)
 
     def test_select_all(self):
         self.assertEqual(self.db.execute("SELECT * FROM cs50"), [])
@@ -108,6 +113,9 @@ def setUpClass(self):
     def setUp(self):
         self.db.execute("CREATE TABLE cs50(id INTEGER PRIMARY KEY, val TEXT)")
 
+    def multi_inserts_enabled(self):
+        return False
+
 if __name__ == "__main__":
     suite = unittest.TestSuite([
         unittest.TestLoader().loadTestsFromTestCase(SQLiteTests),